diff options
Diffstat (limited to 'devtools/client/inspector/boxmodel/test')
27 files changed, 2677 insertions, 0 deletions
diff --git a/devtools/client/inspector/boxmodel/test/browser.ini b/devtools/client/inspector/boxmodel/test/browser.ini new file mode 100644 index 0000000000..3296d1579a --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser.ini @@ -0,0 +1,40 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + doc_boxmodel_iframe1.html + doc_boxmodel_iframe2.html + head.js + !/devtools/client/inspector/test/head.js + !/devtools/client/inspector/test/shared-head.js + !/devtools/client/shared/test/shared-head.js + !/devtools/client/shared/test/telemetry-test-helpers.js + !/devtools/client/shared/test/highlighter-test-actor.js + +[browser_boxmodel.js] +[browser_boxmodel_edit-position-visible-position-change.js] +[browser_boxmodel_editablemodel.js] +[browser_boxmodel_editablemodel_allproperties.js] +disabled=too many intermittent failures (bug 1009322) +[browser_boxmodel_editablemodel_bluronclick.js] +[browser_boxmodel_editablemodel_border.js] +[browser_boxmodel_editablemodel_pseudo.js] +[browser_boxmodel_editablemodel_stylerules.js] +[browser_boxmodel_guides.js] +[browser_boxmodel_jump-to-rule-on-hover.js] +[browser_boxmodel_layout-accordion-state.js] +[browser_boxmodel_navigation.js] +[browser_boxmodel_offsetparent.js] +[browser_boxmodel_positions.js] +[browser_boxmodel_properties.js] +[browser_boxmodel_pseudo-element.js] +[browser_boxmodel_rotate-labels-on-sides.js] +[browser_boxmodel_show-tooltip-for-unassociated-rule.js] +[browser_boxmodel_sync.js] +[browser_boxmodel_tooltips.js] +skip-if = true # Bug 1336198 +[browser_boxmodel_update-after-navigation.js] +skip-if = (os == 'linux' || os == 'win') && bits == 64 #Bug 1582395 +[browser_boxmodel_update-after-reload.js] +[browser_boxmodel_update-in-iframes.js] +disabled=Bug 1020038 boxmodel-view updates for iframe elements changes diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel.js new file mode 100644 index 0000000000..f5017a5f70 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel.js @@ -0,0 +1,201 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model displays the right values and that it updates when +// the node's style is changed + +// Expected values: +var res1 = [ + { + selector: ".boxmodel-element-size", + value: "160" + "\u00D7" + "160.117", + }, + { + selector: ".boxmodel-size > .boxmodel-width", + value: "100", + }, + { + selector: ".boxmodel-size > .boxmodel-height", + value: "100.117", + }, + { + selector: ".boxmodel-position.boxmodel-top > span", + value: "42", + }, + { + selector: ".boxmodel-position.boxmodel-left > span", + value: "42", + }, + { + selector: ".boxmodel-margin.boxmodel-top > span", + value: "30", + }, + { + selector: ".boxmodel-margin.boxmodel-left > span", + value: "auto", + }, + { + selector: ".boxmodel-margin.boxmodel-bottom > span", + value: "30", + }, + { + selector: ".boxmodel-margin.boxmodel-right > span", + value: "auto", + }, + { + selector: ".boxmodel-padding.boxmodel-top > span", + value: "20", + }, + { + selector: ".boxmodel-padding.boxmodel-left > span", + value: "20", + }, + { + selector: ".boxmodel-padding.boxmodel-bottom > span", + value: "20", + }, + { + selector: ".boxmodel-padding.boxmodel-right > span", + value: "20", + }, + { + selector: ".boxmodel-border.boxmodel-top > span", + value: "10", + }, + { + selector: ".boxmodel-border.boxmodel-left > span", + value: "10", + }, + { + selector: ".boxmodel-border.boxmodel-bottom > span", + value: "10", + }, + { + selector: ".boxmodel-border.boxmodel-right > span", + value: "10", + }, +]; + +var res2 = [ + { + selector: ".boxmodel-element-size", + value: "190" + "\u00D7" + "210", + }, + { + selector: ".boxmodel-size > .boxmodel-width", + value: "100", + }, + { + selector: ".boxmodel-size > .boxmodel-height", + value: "150", + }, + { + selector: ".boxmodel-position.boxmodel-top > span", + value: "50", + }, + { + selector: ".boxmodel-position.boxmodel-left > span", + value: "42", + }, + { + selector: ".boxmodel-margin.boxmodel-top > span", + value: "30", + }, + { + selector: ".boxmodel-margin.boxmodel-left > span", + value: "auto", + }, + { + selector: ".boxmodel-margin.boxmodel-bottom > span", + value: "30", + }, + { + selector: ".boxmodel-margin.boxmodel-right > span", + value: "auto", + }, + { + selector: ".boxmodel-padding.boxmodel-top > span", + value: "20", + }, + { + selector: ".boxmodel-padding.boxmodel-left > span", + value: "20", + }, + { + selector: ".boxmodel-padding.boxmodel-bottom > span", + value: "20", + }, + { + selector: ".boxmodel-padding.boxmodel-right > span", + value: "50", + }, + { + selector: ".boxmodel-border.boxmodel-top > span", + value: "10", + }, + { + selector: ".boxmodel-border.boxmodel-left > span", + value: "10", + }, + { + selector: ".boxmodel-border.boxmodel-bottom > span", + value: "10", + }, + { + selector: ".boxmodel-border.boxmodel-right > span", + value: "10", + }, +]; + +add_task(async function () { + const style = + "div { position: absolute; top: 42px; left: 42px; " + + "height: 100.111px; width: 100px; border: 10px solid black; " + + "padding: 20px; margin: 30px auto;}"; + const html = "<style>" + style + "</style><div></div>"; + + await addTab("data:text/html," + encodeURIComponent(html)); + const { inspector, boxmodel } = await openLayoutView(); + await selectNode("div", inspector); + + await testInitialValues(inspector, boxmodel); + await testChangingValues(inspector, boxmodel); +}); + +function testInitialValues(inspector, boxmodel) { + info("Test that the initial values of the box model are correct"); + const doc = boxmodel.document; + + for (let i = 0; i < res1.length; i++) { + const elt = doc.querySelector(res1[i].selector); + is( + elt.textContent, + res1[i].value, + res1[i].selector + " has the right value." + ); + } +} + +async function testChangingValues(inspector, boxmodel) { + info("Test that changing the document updates the box model"); + const doc = boxmodel.document; + + const onUpdated = waitForUpdate(inspector); + await setContentPageElementAttribute( + "div", + "style", + "height:150px;padding-right:50px;top:50px" + ); + await onUpdated; + + for (let i = 0; i < res2.length; i++) { + const elt = doc.querySelector(res2[i].selector); + is( + elt.textContent, + res2[i].value, + res2[i].selector + " has the right value after style update." + ); + } +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_edit-position-visible-position-change.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_edit-position-visible-position-change.js new file mode 100644 index 0000000000..0cae75aaaf --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_edit-position-visible-position-change.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the 'Edit Position' button is still visible after +// layout is changed. +// see bug 1398722 + +const TEST_URI = ` + <div id="mydiv" style="background:tomato; + position:absolute; + top:10px; + left:10px; + width:100px; + height:100px"> +`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + await selectNode("#mydiv", inspector); + let editPositionButton = boxmodel.document.querySelector( + ".layout-geometry-editor" + ); + + ok( + isNodeVisible(editPositionButton), + "Edit Position button is visible initially" + ); + + const positionLeftTextbox = boxmodel.document.querySelector( + ".boxmodel-editable[title=position-left]" + ); + ok(isNodeVisible(positionLeftTextbox), "Position-left edit box exists"); + + info("Change the value of position-left and submit"); + const onUpdate = waitForUpdate(inspector); + EventUtils.synthesizeMouseAtCenter( + positionLeftTextbox, + {}, + boxmodel.document.defaultView + ); + EventUtils.synthesizeKey("8", {}, boxmodel.document.defaultView); + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + await onUpdate; + editPositionButton = boxmodel.document.querySelector( + ".layout-geometry-editor" + ); + ok( + isNodeVisible(editPositionButton), + "Edit Position button is still visible after layout change" + ); +}); diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel.js new file mode 100644 index 0000000000..262a28cc5f --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel.js @@ -0,0 +1,279 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that editing the box-model values works as expected and test various +// key bindings + +const TEST_URI = + "<style>" + + "div { margin: 10px; padding: 3px }" + + "#div1 { margin-top: 5px }" + + "#div2 { border-bottom: 1em solid black; }" + + "#div3 { padding: 2em; }" + + "#div4 { margin: 1px; }" + + "</style>" + + "<div id='div1'></div><div id='div2'></div>" + + "<div id='div3'></div><div id='div4'></div>"; + +add_task(async function () { + // Make sure the toolbox is tall enough to have empty space below the + // boxmodel-container. + await pushPref("devtools.toolbox.footer.height", 500); + + const tab = await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + const browser = tab.linkedBrowser; + await testEditingMargins(inspector, boxmodel, browser); + await testKeyBindings(inspector, boxmodel, browser); + await testEscapeToUndo(inspector, boxmodel, browser); + await testDeletingValue(inspector, boxmodel, browser); + await testRefocusingOnClick(inspector, boxmodel, browser); +}); + +async function testEditingMargins(inspector, boxmodel, browser) { + info( + "Test that editing margin dynamically updates the document, pressing " + + "escape cancels the changes" + ); + + is( + await getStyle(browser, "#div1", "margin-top"), + "", + "Should be no margin-top on the element." + ); + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-top > span" + ); + await waitForElementTextContent(span, "5"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "5px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("3", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is( + await getStyle(browser, "#div1", "margin-top"), + "3px", + "Should have updated the margin." + ); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is( + await getStyle(browser, "#div1", "margin-top"), + "", + "Should be no margin-top on the element." + ); + + await waitForElementTextContent(span, "5"); +} + +async function testKeyBindings(inspector, boxmodel, browser) { + info( + "Test that arrow keys work correctly and pressing enter commits the " + + "changes" + ); + + is( + await getStyle(browser, "#div1", "margin-left"), + "", + "Should be no margin-top on the element." + ); + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-left > span" + ); + is(span.textContent, "10", "Should have the right value in the box model."); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "10px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("VK_UP", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "11px", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "margin-left"), + "11px", + "Should have updated the margin." + ); + + EventUtils.synthesizeKey("VK_DOWN", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "10px", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "margin-left"), + "10px", + "Should have updated the margin." + ); + + EventUtils.synthesizeKey( + "VK_UP", + { shiftKey: true }, + boxmodel.document.defaultView + ); + await waitForUpdate(inspector); + + is(editor.value, "20px", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "margin-left"), + "20px", + "Should have updated the margin." + ); + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div1", "margin-left"), + "20px", + "Should be the right margin-top on the element." + ); + + await waitForElementTextContent(span, "20"); +} + +async function testEscapeToUndo(inspector, boxmodel, browser) { + info( + "Test that deleting the value removes the property but escape undoes " + + "that" + ); + + is( + await getStyle(browser, "#div1", "margin-left"), + "20px", + "Should be the right margin-top on the element." + ); + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-left > span" + ); + is(span.textContent, "20", "Should have the right value in the box model."); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "20px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("VK_DELETE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "margin-left"), + "", + "Should have updated the margin." + ); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is( + await getStyle(browser, "#div1", "margin-left"), + "20px", + "Should be the right margin-top on the element." + ); + is(span.textContent, "20", "Should have the right value in the box model."); +} + +async function testDeletingValue(inspector, boxmodel, browser) { + info("Test that deleting the value removes the property"); + + await setStyle(browser, "#div1", "marginRight", "15px"); + await waitForUpdate(inspector); + + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-right > span" + ); + is(span.textContent, "15", "Should have the right value in the box model."); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "15px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("VK_DELETE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "margin-right"), + "", + "Should have updated the margin." + ); + + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div1", "margin-right"), + "", + "Should be the right margin-top on the element." + ); + await waitForElementTextContent(span, "10"); +} + +async function testRefocusingOnClick(inspector, boxmodel, browser) { + info("Test that clicking in the editor input does not remove focus"); + + await selectNode("#div4", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-top > span" + ); + is(span.textContent, "1", "Should have the right value in the box model."); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + + info("Click in the already opened editor input"); + EventUtils.synthesizeMouseAtCenter(editor, {}, boxmodel.document.defaultView); + is( + editor, + boxmodel.document.activeElement, + "Inplace editor input should still have focus." + ); + + info("Check the input can still be used as expected"); + EventUtils.synthesizeKey("VK_UP", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "2px", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div4", "margin-top"), + "2px", + "Should have updated the margin." + ); + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div4", "margin-top"), + "2px", + "Should be the right margin-top on the element." + ); + await waitForElementTextContent(span, "2"); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_allproperties.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_allproperties.js new file mode 100644 index 0000000000..a465d50e4f --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_allproperties.js @@ -0,0 +1,191 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test editing box model values when all values are set + +const TEST_URI = + "<style>" + + "div { margin: 10px; padding: 3px }" + + "#div1 { margin-top: 5px }" + + "#div2 { border-bottom: 1em solid black; }" + + "#div3 { padding: 2em; }" + + "</style>" + + "<div id='div1'></div><div id='div2'></div><div id='div3'></div>"; + +add_task(async function () { + const tab = await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + const browser = tab.linkedBrowser; + await testEditing(inspector, boxmodel, browser); + await testEditingAndCanceling(inspector, boxmodel, browser); + await testDeleting(inspector, boxmodel, browser); + await testDeletingAndCanceling(inspector, boxmodel, browser); +}); + +async function testEditing(inspector, boxmodel, browser) { + info("When all properties are set on the node editing one should work"); + + await setStyle(browser, "#div1", "padding", "5px"); + await waitForUpdate(inspector); + + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-bottom > span" + ); + await waitForElementTextContent(span, "5"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "5px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("7", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "7", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "padding-bottom"), + "7px", + "Should have updated the padding" + ); + + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div1", "padding-bottom"), + "7px", + "Should be the right padding." + ); + await waitForElementTextContent(span, "7"); +} + +async function testEditingAndCanceling(inspector, boxmodel, browser) { + info( + "When all properties are set on the node editing one and then " + + "cancelling with ESCAPE should work" + ); + + await setStyle(browser, "#div1", "padding", "5px"); + await waitForUpdate(inspector); + + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-left > span" + ); + await waitForElementTextContent(span, "5"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "5px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("8", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "8", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "padding-left"), + "8px", + "Should have updated the padding" + ); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is( + await getStyle(browser, "#div1", "padding-left"), + "5px", + "Should be the right padding." + ); + await waitForElementTextContent(span, "5"); +} + +async function testDeleting(inspector, boxmodel, browser) { + info("When all properties are set on the node deleting one should work"); + + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-left > span" + ); + await waitForElementTextContent(span, "5"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "5px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("VK_DELETE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "padding-left"), + "", + "Should have updated the padding" + ); + + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div1", "padding-left"), + "", + "Should be the right padding." + ); + await waitForElementTextContent(span, "3"); +} + +async function testDeletingAndCanceling(inspector, boxmodel, browser) { + info( + "When all properties are set on the node deleting one then cancelling " + + "should work" + ); + + await setStyle(browser, "#div1", "padding", "5px"); + await waitForUpdate(inspector); + + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-left > span" + ); + await waitForElementTextContent(span, "5"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "5px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("VK_DELETE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "padding-left"), + "", + "Should have updated the padding" + ); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is( + await getStyle(browser, "#div1", "padding-left"), + "5px", + "Should be the right padding." + ); + await waitForElementTextContent(span, "5"); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_bluronclick.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_bluronclick.js new file mode 100644 index 0000000000..3e40eda951 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_bluronclick.js @@ -0,0 +1,97 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that inplace editors can be blurred by clicking outside of the editor. + +const TEST_URI = `<style> + #div1 { + margin: 10px; + padding: 3px; + } + </style> + <div id="div1"></div>`; + +add_task(async function () { + // Make sure the toolbox is tall enough to have empty space below the + // boxmodel-container. + await pushPref("devtools.toolbox.footer.height", 500); + + await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + await selectNode("#div1", inspector); + await testClickingOutsideEditor(boxmodel); + await testClickingBelowContainer(boxmodel); +}); + +async function testClickingOutsideEditor(boxmodel) { + info("Test that clicking outside the editor blurs it"); + const span = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-top > span" + ); + await waitForElementTextContent(span, "10"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + + info("Click next to the opened editor input."); + const onBlur = once(editor, "blur"); + const rect = editor.getBoundingClientRect(); + EventUtils.synthesizeMouse( + editor, + rect.width + 10, + rect.height / 2, + {}, + boxmodel.document.defaultView + ); + await onBlur; + + is( + boxmodel.document.querySelector(".styleinspector-propertyeditor"), + null, + "Inplace editor has been removed." + ); +} + +async function testClickingBelowContainer(boxmodel) { + info("Test that clicking below the box-model container blurs it"); + const span = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-top > span" + ); + await waitForElementTextContent(span, "10"); + + info( + "Test that clicking below the boxmodel-container blurs the opened editor" + ); + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + + const onBlur = once(editor, "blur"); + const container = boxmodel.document.querySelector(".boxmodel-container"); + // Using getBoxQuads here because getBoundingClientRect (and therefore synthesizeMouse) + // use an erroneous height of ~50px for the boxmodel-container. + const bounds = container + .getBoxQuads({ relativeTo: boxmodel.document })[0] + .getBounds(); + EventUtils.synthesizeMouseAtPoint( + bounds.left + 10, + bounds.top + bounds.height + 10, + {}, + boxmodel.document.defaultView + ); + await onBlur; + + is( + boxmodel.document.querySelector(".styleinspector-propertyeditor"), + null, + "Inplace editor has been removed." + ); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_border.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_border.js new file mode 100644 index 0000000000..98372cabbc --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_border.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that editing the border value in the box model applies the border style + +const TEST_URI = + "<style>" + + "div { margin: 10px; padding: 3px }" + + "#div1 { margin-top: 5px }" + + "#div2 { border-bottom: 1em solid black; }" + + "#div3 { padding: 2em; }" + + "</style>" + + "<div id='div1'></div><div id='div2'></div><div id='div3'></div>"; + +add_task(async function () { + const tab = await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + const browser = tab.linkedBrowser; + is( + await getStyle(browser, "#div1", "border-top-width"), + "", + "Should have the right border" + ); + is( + await getStyle(browser, "#div1", "border-top-style"), + "", + "Should have the right border" + ); + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-border.boxmodel-top > span" + ); + await waitForElementTextContent(span, "0"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "0", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("1", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "1", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "border-top-width"), + "1px", + "Should have the right border" + ); + is( + await getStyle(browser, "#div1", "border-top-style"), + "solid", + "Should have the right border" + ); + + EventUtils.synthesizeKey("VK_ESCAPE", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is( + await getStyle(browser, "#div1", "border-top-width"), + "", + "Should be the right padding." + ); + is( + await getStyle(browser, "#div1", "border-top-style"), + "", + "Should have the right border" + ); + await waitForElementTextContent(span, "0"); +}); diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_pseudo.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_pseudo.js new file mode 100644 index 0000000000..b2f96fc522 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_pseudo.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that pseudo elements have no side effect on the box model widget for their +// container. See bug 1350499. + +const TEST_URI = `<style> + .test::before { + content: 'before'; + margin-top: 5px; + padding-top: 5px; + width: 5px; + } + </style> + <div style='width:200px;'> + <div class=test></div> + </div>`; + +add_task(async function () { + const tab = await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + const browser = tab.linkedBrowser; + + await selectNode(".test", inspector); + + // No margin-top defined. + info("Test that margins are not impacted by a pseudo element"); + is( + await getStyle(browser, ".test", "margin-top"), + "", + "margin-top is correct" + ); + await checkValueInBoxModel( + ".boxmodel-margin.boxmodel-top", + "0", + boxmodel.document + ); + + // No padding-top defined. + info("Test that paddings are not impacted by a pseudo element"); + is( + await getStyle(browser, ".test", "padding-top"), + "", + "padding-top is correct" + ); + await checkValueInBoxModel( + ".boxmodel-padding.boxmodel-top", + "0", + boxmodel.document + ); + + // Width should be driven by the parent div. + info("Test that dimensions are not impacted by a pseudo element"); + is(await getStyle(browser, ".test", "width"), "", "width is correct"); + await checkValueInBoxModel( + ".boxmodel-content.boxmodel-width", + "200", + boxmodel.document + ); +}); + +async function checkValueInBoxModel(selector, expectedValue, doc) { + const span = doc.querySelector(selector + " > span"); + await waitForElementTextContent(span, expectedValue); + + EventUtils.synthesizeMouseAtCenter(span, {}, doc.defaultView); + const editor = doc.querySelector(".styleinspector-propertyeditor"); + ok(editor, "Should have opened the editor."); + is(editor.value, expectedValue, "Should have the right value in the editor."); + + const onBlur = once(editor, "blur"); + EventUtils.synthesizeKey("VK_RETURN", {}, doc.defaultView); + await onBlur; +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_stylerules.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_stylerules.js new file mode 100644 index 0000000000..06b2467d1d --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_stylerules.js @@ -0,0 +1,153 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that units are displayed correctly when editing values in the box model +// and that values are retrieved and parsed correctly from the back-end + +const TEST_URI = + "<style>" + + "div { margin: 10px; padding: 3px }" + + "#div1 { margin-top: 5px }" + + "#div2 { border-bottom: 1em solid black; }" + + "#div3 { padding: 2em; }" + + "</style>" + + "<div id='div1'></div><div id='div2'></div><div id='div3'></div>"; + +add_task(async function () { + const tab = await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const browser = tab.linkedBrowser; + const { inspector, boxmodel } = await openLayoutView(); + + await testUnits(inspector, boxmodel, browser); + await testValueComesFromStyleRule(inspector, boxmodel, browser); + await testShorthandsAreParsed(inspector, boxmodel, browser); +}); + +async function testUnits(inspector, boxmodel, browser) { + info("Test that entering units works"); + + is( + await getStyle(browser, "#div1", "padding-top"), + "", + "Should have the right padding" + ); + await selectNode("#div1", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-top > span" + ); + await waitForElementTextContent(span, "3"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "3px", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("1", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + EventUtils.synthesizeKey("e", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is( + await getStyle(browser, "#div1", "padding-top"), + "", + "An invalid value is handled cleanly" + ); + + EventUtils.synthesizeKey("m", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "1em", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div1", "padding-top"), + "1em", + "Should have updated the padding." + ); + + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div1", "padding-top"), + "1em", + "Should be the right padding." + ); + await waitForElementTextContent(span, "16"); +} + +async function testValueComesFromStyleRule(inspector, boxmodel, browser) { + info("Test that we pick up the value from a higher style rule"); + + is( + await getStyle(browser, "#div2", "border-bottom-width"), + "", + "Should have the right border-bottom-width" + ); + await selectNode("#div2", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-border.boxmodel-bottom > span" + ); + await waitForElementTextContent(span, "16"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "1em", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("0", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + + is(editor.value, "0", "Should have the right value in the editor."); + is( + await getStyle(browser, "#div2", "border-bottom-width"), + "0px", + "Should have updated the border." + ); + + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div2", "border-bottom-width"), + "0px", + "Should be the right border-bottom-width." + ); + await waitForElementTextContent(span, "0"); +} + +async function testShorthandsAreParsed(inspector, boxmodel, browser) { + info("Test that shorthand properties are parsed correctly"); + + is( + await getStyle(browser, "#div3", "padding-right"), + "", + "Should have the right padding" + ); + await selectNode("#div3", inspector); + + const span = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-right > span" + ); + await waitForElementTextContent(span, "32"); + + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + ok(editor, "Should have opened the editor."); + is(editor.value, "2em", "Should have the right value in the editor."); + + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + is( + await getStyle(browser, "#div3", "padding-right"), + "", + "Should be the right padding." + ); + await waitForElementTextContent(span, "32"); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_guides.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_guides.js new file mode 100644 index 0000000000..341b8f525a --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_guides.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that hovering over regions in the box-model shows the highlighter with +// the right options. +// Tests that actually check the highlighter is displayed and correct are in the +// devtools/inspector/test folder. This test only cares about checking that the +// box model view does call the highlighter, and it does so by mocking it. + +const STYLE = + "div { position: absolute; top: 50px; left: 50px; " + + "height: 10px; width: 10px; border: 10px solid black; " + + "padding: 10px; margin: 10px;}"; +const HTML = "<style>" + STYLE + "</style><div></div>"; +const TEST_URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML); + +add_task(async function () { + await addTab(TEST_URL); + const { inspector, boxmodel } = await openLayoutView(); + await selectNode("div", inspector); + + let elt = boxmodel.document.querySelector(".boxmodel-margins"); + await testGuideOnLayoutHover(elt, "margin", inspector); + + elt = boxmodel.document.querySelector(".boxmodel-borders"); + await testGuideOnLayoutHover(elt, "border", inspector); + + elt = boxmodel.document.querySelector(".boxmodel-paddings"); + await testGuideOnLayoutHover(elt, "padding", inspector); + + elt = boxmodel.document.querySelector(".boxmodel-content"); + await testGuideOnLayoutHover(elt, "content", inspector); +}); + +async function testGuideOnLayoutHover(elt, expectedRegion, inspector) { + const { waitForHighlighterTypeShown } = getHighlighterTestHelpers(inspector); + const onHighlighterShown = waitForHighlighterTypeShown( + inspector.highlighters.TYPES.BOXMODEL + ); + + info("Synthesizing mouseover on the boxmodel-view"); + EventUtils.synthesizeMouse( + elt, + 50, + 2, + { type: "mouseover" }, + elt.ownerDocument.defaultView + ); + + info("Waiting for the node-highlight event from the toolbox"); + const { nodeFront, options } = await onHighlighterShown; + + // Wait for the next event tick to make sure the remaining part of the + // test is executed after finishing synthesizing mouse event. + await new Promise(executeSoon); + + is( + nodeFront, + inspector.selection.nodeFront, + "The right nodeFront was highlighted" + ); + is( + options.region, + expectedRegion, + "Region " + expectedRegion + " was highlighted" + ); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_jump-to-rule-on-hover.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_jump-to-rule-on-hover.js new file mode 100644 index 0000000000..74f382259e --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_jump-to-rule-on-hover.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that hovering over a box model value will jump to its source CSS rule in the +// rules view when the shift key is pressed. + +const TEST_URI = `<style> + #box { + margin: 5px; + } + </style> + <div id="box"></div>`; + +add_task(async function () { + await pushPref("devtools.layout.boxmodel.highlightProperty", true); + await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + await selectNode("#box", inspector); + + info( + "Test that hovering over margin-top value highlights the property in rules view." + ); + const ruleView = await inspector.getPanel("ruleview").view; + const el = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-top > span" + ); + + info("Wait for mouse to hover over margin-top element."); + const onHighlightProperty = ruleView.once("scrolled-to-element"); + EventUtils.synthesizeMouseAtCenter( + el, + { type: "mousemove", shiftKey: true }, + boxmodel.document.defaultView + ); + await onHighlightProperty; + + info("Check that margin-top is visible in the rule view."); + const { rules, styleWindow } = ruleView; + const marginTop = rules[1].textProps[0].computed[0]; + ok( + isInViewport(marginTop.element, styleWindow), + "margin-top is visible in the rule view." + ); +}); + +function isInViewport(element, win) { + const { top, left, bottom, right } = element.getBoundingClientRect(); + return ( + top >= 0 && + bottom <= win.innerHeight && + left >= 0 && + right <= win.innerWidth + ); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js new file mode 100644 index 0000000000..d6802f4e5d --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js @@ -0,0 +1,103 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that the box model's accordion state is persistent through hide/show in the +// layout view. + +const TEST_URI = ` + <style> + #div1 { + margin: 10px; + padding: 3px; + } + </style> + <div id="div1"></div> +`; + +const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened"; +const ACCORDION_HEADER_SELECTOR = ".accordion-header"; +const ACCORDION_CONTENT_SELECTOR = ".accordion-content"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel, toolbox } = await openLayoutView(); + const { document: doc } = boxmodel; + + await testAccordionStateAfterClickingHeader(doc); + await testAccordionStateAfterSwitchingSidebars(inspector, doc); + await testAccordionStateAfterReopeningLayoutView(toolbox); + + Services.prefs.clearUserPref(BOXMODEL_OPENED_PREF); +}); + +function testAccordionStateAfterClickingHeader(doc) { + const item = doc.querySelector("#layout-section-boxmodel"); + const header = item.querySelector(ACCORDION_HEADER_SELECTOR); + const content = item.querySelector(ACCORDION_CONTENT_SELECTOR); + + info("Checking initial state of the box model panel."); + ok( + !content.hidden && content.childElementCount > 0, + "The box model panel content is visible." + ); + ok( + Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF), + `${BOXMODEL_OPENED_PREF} is pref on by default.` + ); + + info("Clicking the box model header to hide the box model panel."); + header.click(); + + info("Checking the new state of the box model panel."); + ok(content.hidden, "The box model panel content is hidden."); + ok( + !Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF), + `${BOXMODEL_OPENED_PREF} is pref off.` + ); +} + +function testAccordionStateAfterSwitchingSidebars(inspector, doc) { + info( + "Checking the box model accordion state is persistent after switching sidebars." + ); + + info("Selecting the computed view."); + inspector.sidebar.select("computedview"); + + info("Selecting the layout view."); + inspector.sidebar.select("layoutview"); + + info("Checking the state of the box model panel."); + const item = doc.querySelector("#layout-section-boxmodel"); + const content = item.querySelector(ACCORDION_CONTENT_SELECTOR); + + ok(content.hidden, "The box model panel content is hidden."); + ok( + !Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF), + `${BOXMODEL_OPENED_PREF} is pref off.` + ); +} + +async function testAccordionStateAfterReopeningLayoutView(toolbox) { + info( + "Checking the box model accordion state is persistent after closing and " + + "re-opening the layout view." + ); + + info("Closing the toolbox."); + await toolbox.destroy(); + + info("Re-opening the layout view."); + const { boxmodel } = await openLayoutView(); + const item = boxmodel.document.querySelector("#layout-section-boxmodel"); + const content = item.querySelector(ACCORDION_CONTENT_SELECTOR); + + info("Checking the state of the box model panel."); + ok(content.hidden, "The box model panel content is hidden."); + ok( + !Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF), + `${BOXMODEL_OPENED_PREF} is pref off.` + ); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_navigation.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_navigation.js new file mode 100644 index 0000000000..7274092a1a --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_navigation.js @@ -0,0 +1,200 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that keyboard and mouse navigation updates aria-active and focus +// of elements. + +const TEST_URI = ` + <style> + div { position: absolute; top: 42px; left: 42px; + height: 100.111px; width: 100px; border: 10px solid black; + padding: 20px; margin: 30px auto;} + </style><div></div> +`; + +add_task(async function () { + await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + await selectNode("div", inspector); + + await testInitialFocus(inspector, boxmodel); + await testChangingLevels(inspector, boxmodel); + await testTabbingThroughItems(inspector, boxmodel); + await testChangingLevelsByClicking(inspector, boxmodel); +}); + +function testInitialFocus(inspector, boxmodel) { + info("Test that the focus is(on margin layout."); + const doc = boxmodel.document; + const container = doc.querySelector(".boxmodel-container"); + container.focus(); + EventUtils.synthesizeKey("KEY_Enter"); + + is( + container.dataset.activeDescendantClassName, + "boxmodel-main devtools-monospace", + "Should be set to the position layout." + ); +} + +function testChangingLevels(inspector, boxmodel) { + info("Test that using arrow keys updates level."); + const doc = boxmodel.document; + const container = doc.querySelector(".boxmodel-container"); + container.focus(); + EventUtils.synthesizeKey("KEY_Enter"); + EventUtils.synthesizeKey("KEY_Escape"); + + EventUtils.synthesizeKey("KEY_ArrowDown"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-margins", + "Should be set to the margin layout." + ); + + EventUtils.synthesizeKey("KEY_ArrowDown"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-borders", + "Should be set to the border layout." + ); + + EventUtils.synthesizeKey("KEY_ArrowDown"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-paddings", + "Should be set to the padding layout." + ); + + EventUtils.synthesizeKey("KEY_ArrowDown"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-contents", + "Should be set to the content layout." + ); + + EventUtils.synthesizeKey("KEY_ArrowUp"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-paddings", + "Should be set to the padding layout." + ); + + EventUtils.synthesizeKey("KEY_ArrowUp"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-borders", + "Should be set to the border layout." + ); + + EventUtils.synthesizeKey("KEY_ArrowUp"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-margins", + "Should be set to the margin layout." + ); + + EventUtils.synthesizeKey("KEY_ArrowUp"); + is( + container.dataset.activeDescendantClassName, + "boxmodel-main devtools-monospace", + "Should be set to the position layout." + ); +} + +function testTabbingThroughItems(inspector, boxmodel) { + info("Test that using Tab key moves focus to next/previous input field."); + const doc = boxmodel.document; + const container = doc.querySelector(".boxmodel-container"); + container.focus(); + EventUtils.synthesizeKey("KEY_Enter"); + + const editBoxes = [...doc.querySelectorAll("[data-box].boxmodel-editable")]; + + const editBoxesInfo = [ + { name: "position-top", itemId: "position-top-id" }, + { name: "position-right", itemId: "position-right-id" }, + { name: "position-bottom", itemId: "position-bottom-id" }, + { name: "position-left", itemId: "position-left-id" }, + { name: "margin-top", itemId: "margin-top-id" }, + { name: "margin-right", itemId: "margin-right-id" }, + { name: "margin-bottom", itemId: "margin-bottom-id" }, + { name: "margin-left", itemId: "margin-left-id" }, + { name: "border-top-width", itemId: "border-top-width-id" }, + { name: "border-right-width", itemId: "border-right-width-id" }, + { name: "border-bottom-width", itemId: "border-bottom-width-id" }, + { name: "border-left-width", itemId: "border-left-width-id" }, + { name: "padding-top", itemId: "padding-top-id" }, + { name: "padding-right", itemId: "padding-right-id" }, + { name: "padding-bottom", itemId: "padding-bottom-id" }, + { name: "padding-left", itemId: "padding-left-id" }, + { name: "width", itemId: "width-id" }, + { name: "height", itemId: "height-id" }, + ]; + + // Check whether tabbing through box model items works + // Note that the test checks whether wrapping around the box model works + // by letting the loop run beyond the number of indexes to start with + // the first item again. + for (let i = 0; i <= editBoxesInfo.length; i++) { + const itemIndex = i % editBoxesInfo.length; + const editBoxInfo = editBoxesInfo[itemIndex]; + is( + editBoxes[itemIndex].parentElement.id, + editBoxInfo.itemId, + `${editBoxInfo.name} item is current` + ); + is( + editBoxes[itemIndex].previousElementSibling?.localName, + "input", + `Input shown for ${editBoxInfo.name} item` + ); + + // Pressing Tab should not be synthesized for the last item to + // wrap to the very last item again when tabbing in reversed order. + if (i < editBoxesInfo.length) { + EventUtils.synthesizeKey("KEY_Tab"); + } + } + + // Check whether reversed tabbing through box model items works + for (let i = editBoxesInfo.length; i >= 0; i--) { + const itemIndex = i % editBoxesInfo.length; + const editBoxInfo = editBoxesInfo[itemIndex]; + is( + editBoxes[itemIndex].parentElement.id, + editBoxInfo.itemId, + `${editBoxInfo.name} item is current` + ); + is( + editBoxes[itemIndex].previousElementSibling?.localName, + "input", + `Input shown for ${editBoxInfo.name} item` + ); + EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); + } +} + +function testChangingLevelsByClicking(inspector, boxmodel) { + info("Test that clicking on levels updates level."); + const doc = boxmodel.document; + const container = doc.querySelector(".boxmodel-container"); + container.focus(); + + const marginLayout = doc.querySelector(".boxmodel-margins"); + const borderLayout = doc.querySelector(".boxmodel-borders"); + const paddingLayout = doc.querySelector(".boxmodel-paddings"); + const contentLayout = doc.querySelector(".boxmodel-contents"); + const layouts = [contentLayout, paddingLayout, borderLayout, marginLayout]; + + layouts.forEach(layout => { + layout.click(); + is( + container.dataset.activeDescendantClassName, + layout.className, + `Should be set to ${layout.getAttribute("data-box")} layout.` + ); + }); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_offsetparent.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_offsetparent.js new file mode 100644 index 0000000000..000d548bdd --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_offsetparent.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model displays the right values for the offset parent and that it +// updates when the node's style is changed + +const TEST_URI = ` + <div id="relative_parent" style="position: relative"> + <div id="absolute_child" style="position: absolute"></div> + </div> + <div id="static"></div> + <div id="no_parent" style="position: absolute"></div> + <div id="fixed" style="position: fixed"></div> +`; + +const OFFSET_PARENT_SELECTOR = + ".computed-property-value-container .objectBox-node"; + +const res1 = [ + { + selector: "#absolute_child", + offsetParentValue: "div#relative_parent", + }, + { + selector: "#no_parent", + offsetParentValue: "body", + }, + { + selector: "#relative_parent", + offsetParentValue: "body", + }, + { + selector: "#static", + offsetParentValue: null, + }, + { + selector: "#fixed", + offsetParentValue: null, + }, +]; + +const updates = [ + { + selector: "#absolute_child", + update: "position: static", + }, +]; + +const res2 = [ + { + selector: "#absolute_child", + offsetParentValue: null, + }, +]; + +add_task(async function () { + await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + await testInitialValues(inspector, boxmodel); + await testChangingValues(inspector, boxmodel); +}); + +async function testInitialValues(inspector, boxmodel) { + info( + "Test that the initial values of the box model offset parent are correct" + ); + const viewdoc = boxmodel.document; + + for (const { selector, offsetParentValue } of res1) { + await selectNode(selector, inspector); + + const elt = viewdoc.querySelector(OFFSET_PARENT_SELECTOR); + is( + elt && elt.textContent, + offsetParentValue, + selector + " has the right value." + ); + } +} + +async function testChangingValues(inspector, boxmodel) { + info("Test that changing the document updates the box model"); + const viewdoc = boxmodel.document; + + for (const { selector, update } of updates) { + const onUpdated = waitForUpdate(inspector); + await setContentPageElementAttribute(selector, "style", update); + await onUpdated; + } + + for (const { selector, offsetParentValue } of res2) { + await selectNode(selector, inspector); + + const elt = viewdoc.querySelector(OFFSET_PARENT_SELECTOR); + is( + elt && elt.textContent, + offsetParentValue, + selector + " has the right value after style update." + ); + } +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_positions.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_positions.js new file mode 100644 index 0000000000..ca182d7130 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_positions.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model displays the right values for positions. + +const TEST_URI = ` + <style type='text/css'> + div { + position: absolute; + left: 0; + margin: 0; + padding: 0; + display: none; + height: 100px; + width: 100px; + border: 10px solid black; + } + </style> + <div>Test Node</div> +`; + +// Expected values: +const res1 = [ + { + selector: ".boxmodel-position.boxmodel-top > span", + value: "auto", + }, + { + selector: ".boxmodel-position.boxmodel-right > span", + value: "auto", + }, + { + selector: ".boxmodel-position.boxmodel-bottom > span", + value: "auto", + }, + { + selector: ".boxmodel-position.boxmodel-left > span", + value: "0", + }, +]; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + const node = await getNodeFront("div", inspector); + const children = await inspector.markup.walker.children(node); + const beforeElement = children.nodes[0]; + + await selectNode(beforeElement, inspector); + await testPositionValues(inspector, boxmodel); +}); + +function testPositionValues(inspector, boxmodel) { + info("Test that the position values of the box model are correct"); + const doc = boxmodel.document; + + for (let i = 0; i < res1.length; i++) { + const elt = doc.querySelector(res1[i].selector); + is( + elt.textContent, + res1[i].value, + res1[i].selector + " has the right value." + ); + } +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_properties.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_properties.js new file mode 100644 index 0000000000..6ebf5ab761 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_properties.js @@ -0,0 +1,129 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model properties list displays the right values +// and that it updates when the node's style is changed. + +const TEST_URI = ` + <style type='text/css'> + div { + box-sizing: border-box; + display: block; + float: left; + line-height: 20px; + position: relative; + z-index: 2; + height: 100px; + width: 100px; + border: 10px solid black; + padding: 20px; + margin: 30px auto; + } + </style> + <div>Test Node</div> +`; + +const res1 = [ + { + property: "box-sizing", + value: "border-box", + }, + { + property: "display", + value: "block", + }, + { + property: "float", + value: "left", + }, + { + property: "line-height", + value: "20px", + }, + { + property: "position", + value: "relative", + }, + { + property: "z-index", + value: "2", + }, +]; + +const res2 = [ + { + property: "box-sizing", + value: "content-box", + }, + { + property: "display", + value: "block", + }, + { + property: "float", + value: "right", + }, + { + property: "line-height", + value: "10px", + }, + { + property: "position", + value: "static", + }, + { + property: "z-index", + value: "5", + }, +]; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + await selectNode("div", inspector); + + await testInitialValues(inspector, boxmodel); + await testChangingValues(inspector, boxmodel); +}); + +function testInitialValues(inspector, boxmodel) { + info("Test that the initial values of the box model are correct"); + const doc = boxmodel.document; + + for (const { property, value } of res1) { + const elt = doc.querySelector(getPropertySelector(property)); + is(elt.textContent, value, property + " has the right value."); + } +} + +async function testChangingValues(inspector, boxmodel) { + info("Test that changing the document updates the box model"); + const doc = boxmodel.document; + + const onUpdated = waitForUpdate(inspector); + await setContentPageElementAttribute( + "div", + "style", + "box-sizing:content-box;float:right;" + + "line-height:10px;position:static;z-index:5;" + ); + await onUpdated; + + for (const { property, value } of res2) { + const elt = doc.querySelector(getPropertySelector(property)); + is( + elt.textContent, + value, + property + " has the right value after style update." + ); + } +} + +function getPropertySelector(propertyName) { + return ( + `.boxmodel-container .computed-property-view` + + `[data-property-name=${propertyName}] .computed-property-value` + ); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js new file mode 100644 index 0000000000..046ee067ba --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js @@ -0,0 +1,122 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model displays the right values for a pseudo-element. + +const TEST_URI = ` + <style type='text/css'> + div { + box-sizing: border-box; + display: block; + float: left; + line-height: 20px; + position: relative; + z-index: 2; + height: 100px; + width: 100px; + border: 10px solid black; + padding: 20px; + margin: 30px auto; + } + + div::before { + content: 'before'; + display: block; + width: 32px; + height: 32px; + margin: 0 auto 6px; + } + </style> + <div>Test Node</div> +`; + +// Expected values: +const res1 = [ + { + selector: ".boxmodel-element-size", + value: "32" + "\u00D7" + "32", + }, + { + selector: ".boxmodel-size > .boxmodel-width", + value: "32", + }, + { + selector: ".boxmodel-size > .boxmodel-height", + value: "32", + }, + { + selector: ".boxmodel-margin.boxmodel-top > span", + value: "0", + }, + { + selector: ".boxmodel-margin.boxmodel-left > span", + value: "4", // (100 - (10 * 2) - (20 * 2) - 32) / 2 + }, + { + selector: ".boxmodel-margin.boxmodel-bottom > span", + value: "6", + }, + { + selector: ".boxmodel-margin.boxmodel-right > span", + value: "4", // (100 - (10 * 2) - (20 * 2) - 32) / 2 + }, + { + selector: ".boxmodel-padding.boxmodel-top > span", + value: "0", + }, + { + selector: ".boxmodel-padding.boxmodel-left > span", + value: "0", + }, + { + selector: ".boxmodel-padding.boxmodel-bottom > span", + value: "0", + }, + { + selector: ".boxmodel-padding.boxmodel-right > span", + value: "0", + }, + { + selector: ".boxmodel-border.boxmodel-top > span", + value: "0", + }, + { + selector: ".boxmodel-border.boxmodel-left > span", + value: "0", + }, + { + selector: ".boxmodel-border.boxmodel-bottom > span", + value: "0", + }, + { + selector: ".boxmodel-border.boxmodel-right > span", + value: "0", + }, +]; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + const node = await getNodeFront("div", inspector); + const children = await inspector.markup.walker.children(node); + const beforeElement = children.nodes[0]; + + await selectNode(beforeElement, inspector); + await testInitialValues(inspector, boxmodel); +}); + +function testInitialValues(inspector, boxmodel) { + info("Test that the initial values of the box model are correct"); + const doc = boxmodel.document; + + for (let i = 0; i < res1.length; i++) { + const elt = doc.querySelector(res1[i].selector); + is( + elt.textContent, + res1[i].value, + res1[i].selector + " has the right value." + ); + } +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_rotate-labels-on-sides.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_rotate-labels-on-sides.js new file mode 100644 index 0000000000..f84ca9d8b0 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_rotate-labels-on-sides.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that longer values are rotated on the side + +const res1 = [ + { selector: ".boxmodel-margin.boxmodel-top > span", value: 30 }, + { selector: ".boxmodel-margin.boxmodel-left > span", value: "auto" }, + { selector: ".boxmodel-margin.boxmodel-bottom > span", value: 30 }, + { selector: ".boxmodel-margin.boxmodel-right > span", value: "auto" }, + { selector: ".boxmodel-padding.boxmodel-top > span", value: 20 }, + { selector: ".boxmodel-padding.boxmodel-left > span", value: 2000000 }, + { selector: ".boxmodel-padding.boxmodel-bottom > span", value: 20 }, + { selector: ".boxmodel-padding.boxmodel-right > span", value: 20 }, + { selector: ".boxmodel-border.boxmodel-top > span", value: 10 }, + { selector: ".boxmodel-border.boxmodel-left > span", value: 10 }, + { selector: ".boxmodel-border.boxmodel-bottom > span", value: 10 }, + { selector: ".boxmodel-border.boxmodel-right > span", value: 10 }, +]; + +const TEST_URI = encodeURIComponent( + [ + "<style>", + "div { border:10px solid black; padding: 20px 20px 20px 2000000px; " + + "margin: 30px auto; }", + "</style>", + "<div></div>", + ].join("") +); +const LONG_TEXT_ROTATE_LIMIT = 3; + +add_task(async function () { + await addTab("data:text/html," + TEST_URI); + const { inspector, boxmodel } = await openLayoutView(); + await selectNode("div", inspector); + + for (let i = 0; i < res1.length; i++) { + const elt = boxmodel.document.querySelector(res1[i].selector); + const isLong = elt.textContent.length > LONG_TEXT_ROTATE_LIMIT; + const classList = elt.parentNode.classList; + const canBeRotated = + classList.contains("boxmodel-left") || + classList.contains("boxmodel-right"); + const isRotated = classList.contains("boxmodel-rotate"); + + is( + canBeRotated && isLong, + isRotated, + res1[i].selector + " correctly rotated." + ); + } +}); diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_show-tooltip-for-unassociated-rule.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_show-tooltip-for-unassociated-rule.js new file mode 100644 index 0000000000..ffb911b342 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_show-tooltip-for-unassociated-rule.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const L10N = new LocalizationHelper( + "devtools/client/locales/inspector.properties" +); + +// Test that hovering over a box model value with no associated rule will show a tooltip +// saying: "No associated rule". + +const TEST_URI = `<style> + #box {} + </style> + <div id="box"></div>`; + +add_task(async function () { + await pushPref("devtools.layout.boxmodel.highlightProperty", true); + await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + const { rulePreviewTooltip } = boxmodel; + await selectNode("#box", inspector); + + info( + "Test that hovering over margin-top shows tooltip showing 'No associated rule'." + ); + const el = boxmodel.document.querySelector( + ".boxmodel-margin.boxmodel-top > span" + ); + + info("Wait for mouse to hover over margin-top element."); + const onRulePreviewTooltipShown = rulePreviewTooltip._tooltip.once( + "shown", + () => { + ok(true, "Tooltip shown."); + is( + rulePreviewTooltip.message.textContent, + L10N.getStr("rulePreviewTooltip.noAssociatedRule"), + `Tooltip shows ${L10N.getStr("rulePreviewTooltip.noAssociatedRule")}` + ); + } + ); + EventUtils.synthesizeMouseAtCenter( + el, + { type: "mousemove", shiftKey: true }, + boxmodel.document.defaultView + ); + await onRulePreviewTooltipShown; +}); diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_sync.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_sync.js new file mode 100644 index 0000000000..fed1e85519 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_sync.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test editing box model syncs with the rule view. + +const TEST_URI = "<p>hello</p>"; + +add_task(async function () { + await addTab("data:text/html," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + info("When a property is edited, it should sync in the rule view"); + + await selectNode("p", inspector); + + info("Modify padding-bottom in box model view"); + const span = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-bottom > span" + ); + EventUtils.synthesizeMouseAtCenter(span, {}, boxmodel.document.defaultView); + const editor = boxmodel.document.querySelector( + ".styleinspector-propertyeditor" + ); + + const onRuleViewRefreshed = once(inspector, "rule-view-refreshed"); + EventUtils.synthesizeKey("7", {}, boxmodel.document.defaultView); + await waitForUpdate(inspector); + await onRuleViewRefreshed; + is(editor.value, "7", "Should have the right value in the editor."); + EventUtils.synthesizeKey("VK_RETURN", {}, boxmodel.document.defaultView); + + info("Check that the property was synced with the rule view"); + const ruleView = selectRuleView(inspector); + const ruleEditor = getRuleViewRuleEditor(ruleView, 0); + const textProp = ruleEditor.rule.textProps[0]; + is(textProp.value, "7px", "The property has the right value"); +}); diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_tooltips.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_tooltips.js new file mode 100644 index 0000000000..de1ab24936 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_tooltips.js @@ -0,0 +1,166 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the regions in the box model view have tooltips, and that individual +// values too. Also test that values that are set from a css rule have tooltips +// referencing the rule. + +const TEST_URI = + "<style>" + + "#div1 { color: red; margin: 3em; }\n" + + "#div2 { border-bottom: 1px solid black; background: red; }\n" + + "html, body, #div3 { box-sizing: border-box; padding: 0 2em; }" + + "</style>" + + "<div id='div1'></div><div id='div2'></div><div id='div3'></div>"; + +// Test data for the tooltips over individual values. +// Each entry should contain: +// - selector: The selector for the node to be selected before starting to test +// - values: An array containing objects for each of the values that are defined +// by css rules. Each entry should contain: +// - name: the name of the property that is set by the css rule +// - ruleSelector: the selector of the rule +// - styleSheetLocation: the fileName:lineNumber +const VALUES_TEST_DATA = [ + { + selector: "#div1", + values: [ + { + name: "margin-top", + ruleSelector: "#div1", + styleSheetLocation: "inline:1", + }, + { + name: "margin-right", + ruleSelector: "#div1", + styleSheetLocation: "inline:1", + }, + { + name: "margin-bottom", + ruleSelector: "#div1", + styleSheetLocation: "inline:1", + }, + { + name: "margin-left", + ruleSelector: "#div1", + styleSheetLocation: "inline:1", + }, + ], + }, + { + selector: "#div2", + values: [ + { + name: "border-bottom-width", + ruleSelector: "#div2", + styleSheetLocation: "inline:2", + }, + ], + }, + { + selector: "#div3", + values: [ + { + name: "padding-top", + ruleSelector: "html, body, #div3", + styleSheetLocation: "inline:3", + }, + { + name: "padding-right", + ruleSelector: "html, body, #div3", + styleSheetLocation: "inline:3", + }, + { + name: "padding-bottom", + ruleSelector: "html, body, #div3", + styleSheetLocation: "inline:3", + }, + { + name: "padding-left", + ruleSelector: "html, body, #div3", + styleSheetLocation: "inline:3", + }, + ], + }, +]; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, boxmodel } = await openLayoutView(); + + info("Checking the regions tooltips"); + + ok( + boxmodel.document.querySelector(".boxmodel-margins").hasAttribute("title"), + "The margin region has a tooltip" + ); + is( + boxmodel.document.querySelector(".boxmodel-margins").getAttribute("title"), + "margin", + "The margin region has the correct tooltip content" + ); + + ok( + boxmodel.document.querySelector(".boxmodel-borders").hasAttribute("title"), + "The border region has a tooltip" + ); + is( + boxmodel.document.querySelector(".boxmodel-borders").getAttribute("title"), + "border", + "The border region has the correct tooltip content" + ); + + ok( + boxmodel.document.querySelector(".boxmodel-paddings").hasAttribute("title"), + "The padding region has a tooltip" + ); + is( + boxmodel.document.querySelector(".boxmodel-paddings").getAttribute("title"), + "padding", + "The padding region has the correct tooltip content" + ); + + ok( + boxmodel.document.querySelector(".boxmodel-content").hasAttribute("title"), + "The content region has a tooltip" + ); + is( + boxmodel.document.querySelector(".boxmodel-content").getAttribute("title"), + "content", + "The content region has the correct tooltip content" + ); + + for (const { selector, values } of VALUES_TEST_DATA) { + info("Selecting " + selector + " and checking the values tooltips"); + await selectNode(selector, inspector); + + info("Iterate over all values"); + for (const key in boxmodel.map) { + if (key === "position") { + continue; + } + + const name = boxmodel.map[key].property; + const expectedTooltipData = values.find(o => o.name === name); + const el = boxmodel.document.querySelector(boxmodel.map[key].selector); + + ok(el.hasAttribute("title"), "The " + name + " value has a tooltip"); + + if (expectedTooltipData) { + info("The " + name + " value comes from a css rule"); + const expectedTooltip = + name + + "\n" + + expectedTooltipData.ruleSelector + + "\n" + + expectedTooltipData.styleSheetLocation; + is(el.getAttribute("title"), expectedTooltip, "The tooltip is correct"); + } else { + info("The " + name + " isn't set by a css rule"); + is(el.getAttribute("title"), name, "The tooltip is correct"); + } + } + } +}); diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-navigation.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-navigation.js new file mode 100644 index 0000000000..4cd83590b0 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-navigation.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model view continues to work after a page navigation and that +// it also works after going back + +const IFRAME1 = URL_ROOT_SSL + "doc_boxmodel_iframe1.html"; +const IFRAME2 = URL_ROOT_SSL + "doc_boxmodel_iframe2.html"; + +add_task(async function () { + const tab = await addTab(IFRAME1); + const browser = tab.linkedBrowser; + const { inspector, boxmodel } = await openLayoutView(); + + await testFirstPage(inspector, boxmodel, browser); + + info("Navigate to the second page"); + let onMarkupLoaded = waitForMarkupLoaded(inspector); + await navigateTo(IFRAME2); + await onMarkupLoaded; + + await testSecondPage(inspector, boxmodel, browser); + + info("Go back to the first page"); + onMarkupLoaded = waitForMarkupLoaded(inspector); + gBrowser.goBack(); + await onMarkupLoaded; + + await testBackToFirstPage(inspector, boxmodel, browser); +}); + +async function testFirstPage(inspector, boxmodel, browser) { + info("Test that the box model view works on the first page"); + + await selectNode("p", inspector); + + info("Checking that the box model view shows the right value"); + const paddingElt = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-top > span" + ); + is(paddingElt.textContent, "50"); + + info("Listening for box model view changes and modifying the padding"); + const onUpdated = waitForUpdate(inspector); + await setStyle(browser, "p", "padding", "20px"); + await onUpdated; + ok(true, "Box model view got updated"); + + info("Checking that the box model view shows the right value after update"); + is(paddingElt.textContent, "20"); +} + +async function testSecondPage(inspector, boxmodel, browser) { + info("Test that the box model view works on the second page"); + + await selectNode("p", inspector); + + info("Checking that the box model view shows the right value"); + const sizeElt = boxmodel.document.querySelector(".boxmodel-size > span"); + is(sizeElt.textContent, "100" + "\u00D7" + "100"); + + info("Listening for box model view changes and modifying the size"); + const onUpdated = waitForUpdate(inspector); + await setStyle(browser, "p", "width", "200px"); + await onUpdated; + ok(true, "Box model view got updated"); + + info("Checking that the box model view shows the right value after update"); + is(sizeElt.textContent, "200" + "\u00D7" + "100"); +} + +async function testBackToFirstPage(inspector, boxmodel, browser) { + info("Test that the box model view works on the first page after going back"); + + await selectNode("p", inspector); + + info( + "Checking that the box model view shows the right value, which is the" + + "modified value from step one because of the bfcache" + ); + const paddingElt = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-top > span" + ); + is(paddingElt.textContent, "20"); + + info("Listening for box model view changes and modifying the padding"); + const onUpdated = waitForUpdate(inspector); + await setStyle(browser, "p", "padding", "100px"); + await onUpdated; + ok(true, "Box model view got updated"); + + info("Checking that the box model view shows the right value after update"); + is(paddingElt.textContent, "100"); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-reload.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-reload.js new file mode 100644 index 0000000000..f6d309c8e2 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-reload.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model view continues to work after the page is reloaded + +add_task(async function () { + const tab = await addTab(URL_ROOT + "doc_boxmodel_iframe1.html"); + const browser = tab.linkedBrowser; + const { inspector, boxmodel } = await openLayoutView(); + + info("Test that the box model view works on the first page"); + await assertBoxModelView(inspector, boxmodel, browser); + + info("Reload the page"); + const onMarkupLoaded = waitForMarkupLoaded(inspector); + await reloadBrowser(); + await onMarkupLoaded; + + info("Test that the box model view works on the reloaded page"); + await assertBoxModelView(inspector, boxmodel, browser); +}); + +async function assertBoxModelView(inspector, boxmodel, browser) { + await selectNode("p", inspector); + + info("Checking that the box model view shows the right value"); + const paddingElt = boxmodel.document.querySelector( + ".boxmodel-padding.boxmodel-top > span" + ); + is(paddingElt.textContent, "50"); + + info("Listening for box model view changes and modifying the padding"); + const onUpdated = waitForUpdate(inspector); + await setStyle(browser, "p", "padding", "20px"); + await onUpdated; + ok(true, "Box model view got updated"); + + info("Checking that the box model view shows the right value after update"); + is(paddingElt.textContent, "20"); +} diff --git a/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-in-iframes.js b/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-in-iframes.js new file mode 100644 index 0000000000..eee72f696c --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_update-in-iframes.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the box model view for elements within iframes also updates when they +// change + +add_task(async function () { + const tab = await addTab(URL_ROOT + "doc_boxmodel_iframe1.html"); + const browser = tab.linkedBrowser; + const { inspector, boxmodel } = await openLayoutView(); + + await testResizingInIframe(inspector, boxmodel, browser); + await testReflowsAfterIframeDeletion(inspector, boxmodel, browser); +}); + +async function testResizingInIframe(inspector, boxmodel, browser) { + info("Test that resizing an element in an iframe updates its box model"); + + info("Selecting the nested test node"); + await selectNodeInFrames(["iframe", "iframe", "div"], inspector); + + info("Checking that the box model view shows the right value"); + const sizeElt = boxmodel.document.querySelector(".boxmodel-size > span"); + is(sizeElt.textContent, "400\u00D7200"); + + info("Listening for box model view changes and modifying its size"); + const onUpdated = waitForUpdate(inspector); + await setStyleInNestedIframe(browser, "div", "width", "200px"); + await onUpdated; + ok(true, "Box model view got updated"); + + info("Checking that the box model view shows the right value after update"); + is(sizeElt.textContent, "200\u00D7200"); +} + +async function testReflowsAfterIframeDeletion(inspector, boxmodel, browser) { + info( + "Test reflows are still sent to the box model view after deleting an " + + "iframe" + ); + + info("Deleting the iframe2"); + const onInspectorUpdated = inspector.once("inspector-updated"); + await removeNestedIframe(browser); + await onInspectorUpdated; + + info("Selecting the test node in iframe1"); + await selectNodeInFrames(["iframe", "p"], inspector); + + info("Checking that the box model view shows the right value"); + const sizeElt = boxmodel.document.querySelector(".boxmodel-size > span"); + is(sizeElt.textContent, "100\u00D7100"); + + info("Listening for box model view changes and modifying its size"); + const onUpdated = waitForUpdate(inspector); + await setStyleInIframe(browser, "p", "width", "200px"); + await onUpdated; + ok(true, "Box model view got updated"); + + info("Checking that the box model view shows the right value after update"); + is(sizeElt.textContent, "200\u00D7100"); +} + +async function setStyleInIframe(browser, selector, propertyName, value) { + const context = await getBrowsingContextInFrames(browser, ["iframe"]); + return setStyle(context, selector, propertyName, value); +} + +async function setStyleInNestedIframe(browser, selector, propertyName, value) { + const context = await getBrowsingContextInFrames(browser, [ + "iframe", + "iframe", + ]); + return setStyle(context, selector, propertyName, value); +} + +async function removeNestedIframe(browser) { + const context = await getBrowsingContextInFrames(browser, ["iframe"]); + await SpecialPowers.spawn(context, [], () => + content.document.querySelector("iframe").remove() + ); +} diff --git a/devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe1.html b/devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe1.html new file mode 100644 index 0000000000..eef48ce079 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe1.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<p style="padding:50px;color:#f06;">Root page</p> +<iframe src="doc_boxmodel_iframe2.html"></iframe> diff --git a/devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe2.html b/devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe2.html new file mode 100644 index 0000000000..0fa6dc02e9 --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe2.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<p style="width:100px;height:100px;background:red;box-sizing:border-box">iframe 1</p> +<iframe src="data:text/html,<div style='width:400px;height:200px;background:yellow;box-sizing:border-box'>iframe 2</div>"></iframe> diff --git a/devtools/client/inspector/boxmodel/test/head.js b/devtools/client/inspector/boxmodel/test/head.js new file mode 100644 index 0000000000..f15424acfb --- /dev/null +++ b/devtools/client/inspector/boxmodel/test/head.js @@ -0,0 +1,122 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint no-unused-vars: [2, {"vars": "local"}] */ + +"use strict"; + +// Import the inspector's head.js first (which itself imports shared-head.js). +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js", + this +); + +Services.prefs.setIntPref("devtools.toolbox.footer.height", 350); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.toolbox.footer.height"); +}); + +/** + * Is the given node visible in the page (rendered in the frame tree). + * @param {DOMNode} + * @return {Boolean} + */ +function isNodeVisible(node) { + return !!node.getClientRects().length; +} + +/** + * Wait for the boxmodel-view-updated event. + * + * @param {InspectorPanel} inspector + * The instance of InspectorPanel currently loaded in the toolbox. + * @param {Boolean} waitForSelectionUpdate + * Should the boxmodel-view-updated event come from a new selection. + * @return {Promise} a promise + */ +async function waitForUpdate(inspector, waitForSelectionUpdate) { + /** + * While the highlighter is visible (mouse over the fields of the box model editor), + * reflow events are prevented; see ReflowActor -> setIgnoreLayoutChanges() + * The box model view updates in response to reflow events. + * To ensure reflow events are fired, hide the highlighter. + */ + await inspector.highlighters.hideHighlighterType( + inspector.highlighters.TYPES.BOXMODEL + ); + + return new Promise(resolve => { + inspector.on("boxmodel-view-updated", function onUpdate(reasons) { + // Wait for another update event if we are waiting for a selection related event. + if (waitForSelectionUpdate && !reasons.includes("new-selection")) { + return; + } + + inspector.off("boxmodel-view-updated", onUpdate); + resolve(); + }); + }); +} + +/** + * Wait for both boxmode-view-updated and markuploaded events. + * + * @return {Promise} a promise that resolves when both events have been received. + */ +function waitForMarkupLoaded(inspector) { + return Promise.all([ + waitForUpdate(inspector), + inspector.once("markuploaded"), + ]); +} + +function getStyle(browser, selector, propertyName) { + return SpecialPowers.spawn( + browser, + [selector, propertyName], + async function (_selector, _propertyName) { + return content.document + .querySelector(_selector) + .style.getPropertyValue(_propertyName); + } + ); +} + +function setStyle(browser, selector, propertyName, value) { + return SpecialPowers.spawn( + browser, + [selector, propertyName, value], + async function (_selector, _propertyName, _value) { + content.document.querySelector(_selector).style[_propertyName] = _value; + } + ); +} + +/** + * The box model doesn't participate in the inspector's update mechanism, so simply + * calling the default selectNode isn't enough to guarantee that the box model view has + * finished updating. We also need to wait for the "boxmodel-view-updated" event. + */ +var _selectNode = selectNode; +selectNode = async function (node, inspector, reason) { + const onUpdate = waitForUpdate(inspector, true); + await _selectNode(node, inspector, reason); + await onUpdate; +}; + +/** + * Wait until the provided element's text content matches the provided text. + * Based on the waitFor helper, see documentation in + * devtools/client/shared/test/shared-head.js + * + * @param {DOMNode} element + * The element to check. + * @param {String} expectedText + * The text that is expected to be set as textContent of the element. + */ +async function waitForElementTextContent(element, expectedText) { + await waitFor( + () => element.textContent === expectedText, + `Couldn't get "${expectedText}" as the text content of the given element` + ); + ok(true, `Found the expected text (${expectedText}) for the given element`); +} |