summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/boxmodel/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/inspector/boxmodel/test
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/inspector/boxmodel/test')
-rw-r--r--devtools/client/inspector/boxmodel/test/browser.toml72
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel.js201
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_edit-position-visible-position-change.js56
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel.js279
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_allproperties.js191
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_bluronclick.js97
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_border.js75
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_pseudo.js76
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_editablemodel_stylerules.js153
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_guides.js69
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_jump-to-rule-on-hover.js56
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_layout-accordion-state.js103
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_navigation.js200
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_offsetparent.js104
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_positions.js67
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_properties.js129
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js122
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_rotate-labels-on-sides.js54
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_show-tooltip-for-unassociated-rule.js50
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_sync.js39
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_tooltips.js166
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-navigation.js96
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_update-after-reload.js42
-rw-r--r--devtools/client/inspector/boxmodel/test/browser_boxmodel_update-in-iframes.js84
-rw-r--r--devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe1.html3
-rw-r--r--devtools/client/inspector/boxmodel/test/doc_boxmodel_iframe2.html3
-rw-r--r--devtools/client/inspector/boxmodel/test/head.js122
27 files changed, 2709 insertions, 0 deletions
diff --git a/devtools/client/inspector/boxmodel/test/browser.toml b/devtools/client/inspector/boxmodel/test/browser.toml
new file mode 100644
index 0000000000..4da0e53eeb
--- /dev/null
+++ b/devtools/client/inspector/boxmodel/test/browser.toml
@@ -0,0 +1,72 @@
+[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"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_boxmodel_editablemodel.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_boxmodel_editablemodel_allproperties.js"]
+disabled = "too many intermittent failures (bug 1009322)"
+
+["browser_boxmodel_editablemodel_bluronclick.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_boxmodel_editablemodel_border.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_boxmodel_editablemodel_pseudo.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_boxmodel_editablemodel_stylerules.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_boxmodel_guides.js"]
+
+["browser_boxmodel_jump-to-rule-on-hover.js"]
+
+["browser_boxmodel_layout-accordion-state.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["browser_boxmodel_navigation.js"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["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"]
+fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and/or labeled
+
+["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`);
+}