summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/shared/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/inspector/shared/test')
-rw-r--r--devtools/client/inspector/shared/test/browser.toml44
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js85
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js115
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js160
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js381
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js50
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js99
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js150
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js80
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js178
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js68
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js73
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js90
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js53
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js63
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js115
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js63
-rw-r--r--devtools/client/inspector/shared/test/doc_content_style_changes.html28
-rw-r--r--devtools/client/inspector/shared/test/head.js218
19 files changed, 2113 insertions, 0 deletions
diff --git a/devtools/client/inspector/shared/test/browser.toml b/devtools/client/inspector/shared/test/browser.toml
new file mode 100644
index 0000000000..3cb858d97f
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser.toml
@@ -0,0 +1,44 @@
+[DEFAULT]
+tags = "devtools"
+subsuite = "devtools"
+support-files = [
+ "doc_content_style_changes.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_styleinspector_context-menu-copy-color_01.js"]
+
+["browser_styleinspector_context-menu-copy-color_02.js"]
+
+["browser_styleinspector_context-menu-copy-urls.js"]
+
+["browser_styleinspector_output-parser.js"]
+
+["browser_styleinspector_refresh_when_active.js"]
+
+["browser_styleinspector_refresh_when_style_changes.js"]
+
+["browser_styleinspector_tooltip-background-image.js"]
+
+["browser_styleinspector_tooltip-closes-on-new-selection.js"]
+
+["browser_styleinspector_tooltip-longhand-fontfamily.js"]
+
+["browser_styleinspector_tooltip-multiple-background-images.js"]
+
+["browser_styleinspector_tooltip-shorthand-fontfamily.js"]
+
+["browser_styleinspector_tooltip-size.js"]
+
+["browser_styleinspector_transform-highlighter-01.js"]
+
+["browser_styleinspector_transform-highlighter-02.js"]
+
+["browser_styleinspector_transform-highlighter-03.js"]
+
+["browser_styleinspector_transform-highlighter-04.js"]
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
new file mode 100644
index 0000000000..c1809dd543
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test "Copy color" item of the context menu #1: Test _isColorPopup.
+
+const TEST_URI = `
+ <div style="color:rgb(18, 58, 188);margin:0px;background:span[data-color];">
+ Test "Copy color" context menu option
+ </div>
+`;
+
+add_task(async function () {
+ // Test is slow on Linux EC2 instances - Bug 1137765
+ requestLongerTimeout(2);
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector } = await openInspector();
+ await testView("ruleview", inspector);
+ await testView("computedview", inspector);
+});
+
+async function testView(viewId, inspector) {
+ info("Testing " + viewId);
+
+ await inspector.sidebar.select(viewId);
+ const view =
+ inspector.getPanel(viewId).view || inspector.getPanel(viewId).computedView;
+ await selectNode("div", inspector);
+
+ testIsColorValueNode(view);
+ await clearCurrentNodeSelection(inspector);
+}
+
+/**
+ * A function testing that isColorValueNode correctly detects nodes part of
+ * color values.
+ */
+function testIsColorValueNode(view) {
+ info("Testing that child nodes of color nodes are detected.");
+ const root = rootElement(view);
+ const colorNode = root.querySelector("span[data-color]");
+
+ ok(colorNode, "Color node found");
+ for (const node of iterateNodes(colorNode)) {
+ ok(isColorValueNode(node), "Node is part of color value.");
+ }
+}
+
+/**
+ * Check if a node is part of color value i.e. it has parent with a 'data-color'
+ * attribute.
+ */
+function isColorValueNode(node) {
+ let container = node.nodeType == node.TEXT_NODE ? node.parentElement : node;
+
+ const isColorNode = el => el.dataset && "color" in el.dataset;
+
+ while (!isColorNode(container)) {
+ container = container.parentNode;
+ if (!container) {
+ info("No color. Node is not part of color value.");
+ return false;
+ }
+ }
+
+ info("Found a color. Node is part of color value.");
+
+ return true;
+}
+
+/**
+ * A generator that iterates recursively trough all child nodes of baseNode.
+ */
+function* iterateNodes(baseNode) {
+ yield baseNode;
+
+ for (const child of baseNode.childNodes) {
+ yield* iterateNodes(child);
+ }
+}
+
+/**
+ * Returns the root element for the given view, rule or computed.
+ */
+var rootElement = view => (view.element ? view.element : view.styleDocument);
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js
new file mode 100644
index 0000000000..546f3520d0
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test "Copy color" item of the context menu #2: Test that correct color is
+// copied if the color changes.
+
+const TEST_URI = `
+ <style type="text/css">
+ div {
+ color: #123ABC;
+ }
+ </style>
+ <div>Testing the color picker tooltip!</div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+
+ const { inspector, view } = await openRuleView();
+
+ await testCopyToClipboard(inspector, view);
+ await testManualEdit(inspector, view);
+ await testColorPickerEdit(inspector, view);
+});
+
+async function testCopyToClipboard(inspector, view) {
+ info("Testing that color is copied to clipboard");
+
+ await selectNode("div", inspector);
+
+ const element = getRuleViewProperty(
+ view,
+ "div",
+ "color"
+ ).valueSpan.querySelector(".ruleview-colorswatch");
+
+ const menu = view.contextMenu._openMenu({ target: element });
+ const allMenuItems = buildContextMenuItems(menu);
+ const menuitemCopyColor = allMenuItems.find(
+ item =>
+ item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyColor")
+ );
+
+ ok(menuitemCopyColor.visible, "Copy color is visible");
+
+ await waitForClipboardPromise(() => menuitemCopyColor.click(), "#123ABC");
+ ok(true, "expected color was copied to clipboard");
+
+ // close context menu
+ const onContextMenuClose = menu.once("close");
+ menu.hide(element.ownerDocument);
+ await onContextMenuClose;
+}
+
+async function testManualEdit(inspector, view) {
+ info("Testing manually edited colors");
+ await selectNode("div", inspector);
+
+ const colorTextProp = getTextProperty(view, 1, { color: "#123ABC" });
+ const newColor = "#C9184E";
+ await setProperty(view, colorTextProp, newColor);
+
+ const colorValueElement = await waitFor(() => {
+ const el = getRuleViewProperty(view, "div", "color").valueSpan.firstChild;
+ if (el?.dataset?.color !== newColor) {
+ return false;
+ }
+ return el;
+ });
+
+ ok(!!colorValueElement, "data-color was updated");
+
+ const contextMenu = view.contextMenu;
+ contextMenu.currentTarget = colorValueElement;
+ contextMenu._isColorPopup();
+
+ is(contextMenu._colorToCopy, newColor, "_colorToCopy has the new value");
+}
+
+async function testColorPickerEdit(inspector, view) {
+ info("Testing colors edited via color picker");
+ await selectNode("div", inspector);
+
+ const swatchElement = getRuleViewProperty(
+ view,
+ "div",
+ "color"
+ ).valueSpan.querySelector(".ruleview-colorswatch");
+
+ info("Opening the color picker");
+ const picker = view.tooltips.getTooltip("colorPicker");
+ const onColorPickerReady = picker.once("ready");
+ swatchElement.click();
+ await onColorPickerReady;
+
+ const newColor = "#53B759";
+ const { colorUtils } = require("resource://devtools/shared/css/color.js");
+
+ const { r, g, b, a } = new colorUtils.CssColor(newColor).getRGBATuple();
+ await simulateColorPickerChange(view, picker, [r, g, b, a]);
+
+ is(
+ swatchElement.parentNode.dataset.color,
+ newColor,
+ "data-color was updated"
+ );
+
+ const contextMenu = view.contextMenu;
+ contextMenu.currentTarget = swatchElement;
+ contextMenu._isColorPopup();
+
+ is(contextMenu._colorToCopy, newColor, "_colorToCopy has the new value");
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js
new file mode 100644
index 0000000000..69042cc747
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* Tests both Copy URL and Copy Data URL context menu items */
+
+const TEST_DATA_URI =
+ "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=";
+
+// Invalid URL still needs to be reachable otherwise getImageDataUrl will
+// timeout. DevTools chrome:// URLs aren't content accessible, so use some
+// random resource:// URL here.
+const INVALID_IMAGE_URI = "resource://devtools/client/definitions.js";
+const ERROR_MESSAGE = STYLE_INSPECTOR_L10N.getStr(
+ "styleinspector.copyImageDataUrlError"
+);
+
+add_task(async function () {
+ const TEST_URI = `<style type="text/css">
+ .valid-background {
+ background-image: url(${TEST_DATA_URI});
+ }
+ .invalid-background {
+ background-image: url(${INVALID_IMAGE_URI});
+ }
+ </style>
+ <div class="valid-background">Valid background image</div>
+ <div class="invalid-background">Invalid background image</div>`;
+
+ await addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI));
+
+ await startTest();
+});
+
+async function startTest() {
+ info("Opening rule view");
+ let { inspector, view } = await openRuleView();
+
+ info("Test valid background image URL in rule view");
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "data-uri",
+ ".valid-background",
+ TEST_DATA_URI
+ );
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "url",
+ ".valid-background",
+ TEST_DATA_URI
+ );
+
+ info("Test invalid background image URL in rue view");
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "data-uri",
+ ".invalid-background",
+ ERROR_MESSAGE
+ );
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "url",
+ ".invalid-background",
+ INVALID_IMAGE_URI
+ );
+
+ info("Opening computed view");
+ view = selectComputedView(inspector);
+
+ info("Test valid background image URL in computed view");
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "data-uri",
+ ".valid-background",
+ TEST_DATA_URI
+ );
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "url",
+ ".valid-background",
+ TEST_DATA_URI
+ );
+
+ info("Test invalid background image URL in computed view");
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "data-uri",
+ ".invalid-background",
+ ERROR_MESSAGE
+ );
+ await testCopyUrlToClipboard(
+ { view, inspector },
+ "url",
+ ".invalid-background",
+ INVALID_IMAGE_URI
+ );
+}
+
+async function testCopyUrlToClipboard(
+ { view, inspector },
+ type,
+ selector,
+ expected
+) {
+ info("Select node in inspector panel");
+ await selectNode(selector, inspector);
+
+ info(
+ "Retrieve background-image link for selected node in current " +
+ "styleinspector view"
+ );
+ const property = await getBackgroundImageProperty(view, selector);
+ const imageLink = property.valueSpan.querySelector(".theme-link");
+ ok(imageLink, "Background-image link element found");
+
+ info("Simulate right click on the background-image URL");
+ const allMenuItems = openStyleContextMenuAndGetAllItems(view, imageLink);
+ const menuitemCopyUrl = allMenuItems.find(
+ item =>
+ item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyUrl")
+ );
+ const menuitemCopyImageDataUrl = allMenuItems.find(
+ item =>
+ item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyImageDataUrl")
+ );
+
+ info("Context menu is displayed");
+ ok(menuitemCopyUrl.visible, '"Copy URL" menu entry is displayed');
+ ok(
+ menuitemCopyImageDataUrl.visible,
+ '"Copy Image Data-URL" menu entry is displayed'
+ );
+
+ if (type == "data-uri") {
+ info("Click Copy Data URI and wait for clipboard");
+ await waitForClipboardPromise(() => {
+ return menuitemCopyImageDataUrl.click();
+ }, expected);
+ } else {
+ info("Click Copy URL and wait for clipboard");
+ await waitForClipboardPromise(() => {
+ return menuitemCopyUrl.click();
+ }, expected);
+ }
+
+ info("Hide context menu");
+}
+
+async function getBackgroundImageProperty(view, selector) {
+ const isRuleView = view instanceof CssRuleView;
+ if (isRuleView) {
+ return getRuleViewProperty(view, selector, "background-image", {
+ wait: true,
+ });
+ }
+ return getComputedViewProperty(view, "background-image");
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js b/devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js
new file mode 100644
index 0000000000..1aa2879ee2
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js
@@ -0,0 +1,381 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test expected outputs of the output-parser's parseCssProperty function.
+
+// This is more of a unit test than a mochitest-browser test, but can't be
+// tested with an xpcshell test as the output-parser requires the DOM to work.
+
+const OutputParser = require("resource://devtools/client/shared/output-parser.js");
+
+const COLOR_CLASS = "color-class";
+const URL_CLASS = "url-class";
+const CUBIC_BEZIER_CLASS = "bezier-class";
+const ANGLE_CLASS = "angle-class";
+const LINEAR_EASING_CLASS = "linear-easing-class";
+
+const TEST_DATA = [
+ {
+ name: "width",
+ value: "100%",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ is(fragment.textContent, "100%");
+ },
+ },
+ {
+ name: "width",
+ value: "blue",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ },
+ },
+ {
+ name: "content",
+ value: "'red url(test.png) repeat top left'",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ },
+ },
+ {
+ name: "content",
+ value: '"blue"',
+ test: fragment => {
+ is(countAll(fragment), 0);
+ },
+ },
+ {
+ name: "margin-left",
+ value: "url(something.jpg)",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ },
+ },
+ {
+ name: "background-color",
+ value: "transparent",
+ test: fragment => {
+ is(countAll(fragment), 2);
+ is(countColors(fragment), 1);
+ is(fragment.textContent, "transparent");
+ },
+ },
+ {
+ name: "color",
+ value: "red",
+ test: fragment => {
+ is(countColors(fragment), 1);
+ is(fragment.textContent, "red");
+ },
+ },
+ {
+ name: "color",
+ value: "#F06",
+ test: fragment => {
+ is(countColors(fragment), 1);
+ is(fragment.textContent, "#F06");
+ },
+ },
+ {
+ name: "border",
+ value: "80em dotted pink",
+ test: fragment => {
+ is(countAll(fragment), 2);
+ is(countColors(fragment), 1);
+ is(getColor(fragment), "pink");
+ },
+ },
+ {
+ name: "color",
+ value: "red !important",
+ test: fragment => {
+ is(countColors(fragment), 1);
+ is(fragment.textContent, "red !important");
+ },
+ },
+ {
+ name: "background",
+ value: "red url(test.png) repeat top left",
+ test: fragment => {
+ is(countColors(fragment), 1);
+ is(countUrls(fragment), 1);
+ is(getColor(fragment), "red");
+ is(getUrl(fragment), "test.png");
+ is(countAll(fragment), 3);
+ },
+ },
+ {
+ name: "background",
+ value: "blue url(test.png) repeat top left !important",
+ test: fragment => {
+ is(countColors(fragment), 1);
+ is(countUrls(fragment), 1);
+ is(getColor(fragment), "blue");
+ is(getUrl(fragment), "test.png");
+ is(countAll(fragment), 3);
+ },
+ },
+ {
+ name: "list-style-image",
+ value: 'url("images/arrow.gif")',
+ test: fragment => {
+ is(countAll(fragment), 1);
+ is(getUrl(fragment), "images/arrow.gif");
+ },
+ },
+ {
+ name: "list-style-image",
+ value: 'url("images/arrow.gif")!important',
+ test: fragment => {
+ is(countAll(fragment), 1);
+ is(getUrl(fragment), "images/arrow.gif");
+ is(fragment.textContent, 'url("images/arrow.gif")!important');
+ },
+ },
+ {
+ name: "background",
+ value:
+ "linear-gradient(to right, rgba(183,222,237,1) 0%, " +
+ "rgba(33,180,226,1) 30%, rgba(31,170,217,.5) 44%, " +
+ "#F06 75%, red 100%)",
+ test: fragment => {
+ is(countAll(fragment), 10);
+ const allSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
+ is(allSwatches.length, 5);
+ is(allSwatches[0].textContent, "rgba(183,222,237,1)");
+ is(allSwatches[1].textContent, "rgba(33,180,226,1)");
+ is(allSwatches[2].textContent, "rgba(31,170,217,.5)");
+ is(allSwatches[3].textContent, "#F06");
+ is(allSwatches[4].textContent, "red");
+ },
+ },
+ {
+ name: "background",
+ value:
+ "radial-gradient(circle closest-side at center, orange 0%, red 100%)",
+ test: fragment => {
+ is(countAll(fragment), 4);
+ const colorSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
+ is(colorSwatches.length, 2);
+ is(colorSwatches[0].textContent, "orange");
+ is(colorSwatches[1].textContent, "red");
+ },
+ },
+ {
+ name: "background",
+ value: "white url(http://test.com/wow_such_image.png) no-repeat top left",
+ test: fragment => {
+ is(countAll(fragment), 3);
+ is(countUrls(fragment), 1);
+ is(countColors(fragment), 1);
+ },
+ },
+ {
+ name: "background",
+ value:
+ 'url("http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t")',
+ test: fragment => {
+ is(countAll(fragment), 1);
+ is(
+ getUrl(fragment),
+ "http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t"
+ );
+ },
+ },
+ {
+ name: "background-image",
+ value: "url(this-is-an-incredible-image.jpeg)",
+ test: fragment => {
+ is(countAll(fragment), 1);
+ is(getUrl(fragment), "this-is-an-incredible-image.jpeg");
+ },
+ },
+ {
+ name: "background",
+ value:
+ 'red url( "http://wow.com/cool/../../../you\'re(doingit)wrong" ) repeat center',
+ test: fragment => {
+ is(countAll(fragment), 3);
+ is(countColors(fragment), 1);
+ is(getUrl(fragment), "http://wow.com/cool/../../../you're(doingit)wrong");
+ },
+ },
+ {
+ name: "background-image",
+ value:
+ "url(../../../look/at/this/folder/structure/../" +
+ "../red.blue.green.svg )",
+ test: fragment => {
+ is(countAll(fragment), 1);
+ is(
+ getUrl(fragment),
+ "../../../look/at/this/folder/structure/../" + "../red.blue.green.svg"
+ );
+ },
+ },
+ {
+ name: "transition-timing-function",
+ value: "linear",
+ test: fragment => {
+ is(countCubicBeziers(fragment), 1);
+ is(getCubicBezier(fragment), "linear");
+ },
+ },
+ {
+ name: "animation-timing-function",
+ value: "ease-in-out",
+ test: fragment => {
+ is(countCubicBeziers(fragment), 1);
+ is(getCubicBezier(fragment), "ease-in-out");
+ },
+ },
+ {
+ name: "animation-timing-function",
+ value: "cubic-bezier(.1, 0.55, .9, -3.45)",
+ test: fragment => {
+ is(countCubicBeziers(fragment), 1);
+ is(getCubicBezier(fragment), "cubic-bezier(.1, 0.55, .9, -3.45)");
+ },
+ },
+ {
+ name: "animation-timing-function",
+ value: "CUBIC-BEZIER(.1, 0.55, .9, -3.45)",
+ test: fragment => {
+ is(countCubicBeziers(fragment), 1);
+ is(getCubicBezier(fragment), "CUBIC-BEZIER(.1, 0.55, .9, -3.45)");
+ },
+ },
+ {
+ name: "animation",
+ value: "move 3s cubic-bezier(.1, 0.55, .9, -3.45)",
+ test: fragment => {
+ is(countCubicBeziers(fragment), 1);
+ is(getCubicBezier(fragment), "cubic-bezier(.1, 0.55, .9, -3.45)");
+ },
+ },
+ {
+ name: "transition",
+ value: "top 1s ease-in",
+ test: fragment => {
+ is(countCubicBeziers(fragment), 1);
+ is(getCubicBezier(fragment), "ease-in");
+ },
+ },
+ {
+ name: "animation-timing-function",
+ value: "linear(0, 1 50% 100%)",
+ test: fragment => {
+ is(countLinears(fragment), 1);
+ is(getLinear(fragment), "linear(0, 1 50% 100%)");
+ },
+ },
+ {
+ name: "animation-timing-function",
+ value: "LINEAR(0, 1 50% 100%)",
+ test: fragment => {
+ is(countLinears(fragment), 1);
+ is(getLinear(fragment), "LINEAR(0, 1 50% 100%)");
+ },
+ },
+ {
+ name: "transition",
+ value: "top 3s steps(4, end)",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ },
+ },
+ {
+ name: "transition",
+ value: "top 3s step-start",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ },
+ },
+ {
+ name: "transition",
+ value: "top 3s step-end",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ },
+ },
+ {
+ name: "background",
+ value: "rgb(255, var(--g-value), 192)",
+ test: fragment => {
+ is(fragment.textContent, "rgb(255, var(--g-value), 192)");
+ },
+ },
+ {
+ name: "background",
+ value: "rgb(255, var(--g-value, 0), 192)",
+ test: fragment => {
+ is(fragment.textContent, "rgb(255, var(--g-value, 0), 192)");
+ },
+ },
+ {
+ name: "--url",
+ value: "url(())",
+ test: fragment => {
+ is(countAll(fragment), 0);
+ is(fragment.textContent, "url(())");
+ },
+ },
+];
+
+add_task(async function () {
+ const cssProperties = getClientCssProperties();
+ const parser = new OutputParser(document, cssProperties);
+ for (let i = 0; i < TEST_DATA.length; i++) {
+ const data = TEST_DATA[i];
+ info(
+ "Output-parser test data " +
+ i +
+ ". {" +
+ data.name +
+ " : " +
+ data.value +
+ ";}"
+ );
+ data.test(
+ parser.parseCssProperty(data.name, data.value, {
+ colorClass: COLOR_CLASS,
+ urlClass: URL_CLASS,
+ bezierClass: CUBIC_BEZIER_CLASS,
+ angleClass: ANGLE_CLASS,
+ linearEasingClass: LINEAR_EASING_CLASS,
+ })
+ );
+ }
+});
+
+function countAll(fragment) {
+ return fragment.querySelectorAll("*").length;
+}
+function countColors(fragment) {
+ return fragment.querySelectorAll("." + COLOR_CLASS).length;
+}
+function countUrls(fragment) {
+ return fragment.querySelectorAll("." + URL_CLASS).length;
+}
+function countCubicBeziers(fragment) {
+ return fragment.querySelectorAll("." + CUBIC_BEZIER_CLASS).length;
+}
+function countLinears(fragment) {
+ return fragment.querySelectorAll("." + LINEAR_EASING_CLASS).length;
+}
+function getColor(fragment, index) {
+ return fragment.querySelectorAll("." + COLOR_CLASS)[index || 0].textContent;
+}
+function getUrl(fragment, index) {
+ return fragment.querySelectorAll("." + URL_CLASS)[index || 0].textContent;
+}
+function getCubicBezier(fragment, index) {
+ return fragment.querySelectorAll("." + CUBIC_BEZIER_CLASS)[index || 0]
+ .textContent;
+}
+function getLinear(fragment, index = 0) {
+ return fragment.querySelectorAll("." + LINEAR_EASING_CLASS)[index]
+ .textContent;
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js
new file mode 100644
index 0000000000..de2f3cc3e6
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule and computed view refreshes when they are active.
+
+const TEST_URI = `
+ <div id="one" style="color:red;">one</div>
+ <div id="two" style="color:blue;">two</div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, view } = await openRuleView();
+
+ await selectNode("#one", inspector);
+
+ is(
+ getRuleViewPropertyValue(view, "element", "color"),
+ "red",
+ "The rule-view shows the properties for test node one"
+ );
+
+ info("Switching to the computed-view");
+ const onComputedViewReady = inspector.once("computed-view-refreshed");
+ selectComputedView(inspector);
+ await onComputedViewReady;
+ const cView = inspector.getPanel("computedview").computedView;
+
+ is(
+ getComputedViewPropertyValue(cView, "color"),
+ "rgb(255, 0, 0)",
+ "The computed-view shows the properties for test node one"
+ );
+
+ info("Selecting test node two");
+ await selectNode("#two", inspector);
+
+ is(
+ getComputedViewPropertyValue(cView, "color"),
+ "rgb(0, 0, 255)",
+ "The computed-view shows the properties for test node two"
+ );
+ is(
+ getRuleViewPropertyValue(view, "element", "color"),
+ "blue",
+ "The rule-view shows the properties for test node two"
+ );
+});
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js
new file mode 100644
index 0000000000..9b92e65c07
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the rule and computed views refresh when style changes that impact the
+// current selection occur.
+// This test does not need to worry about the correctness of the styles and rules
+// displayed in these views (other tests do this) but only cares that they do catch the
+// change.
+
+const TEST_URI = TEST_URL_ROOT + "doc_content_style_changes.html";
+
+const TEST_DATA = [
+ {
+ target: "#test",
+ className: "green-class",
+ force: true,
+ },
+ {
+ target: "#test",
+ className: "green-class",
+ force: false,
+ },
+ {
+ target: "#parent",
+ className: "purple-class",
+ force: true,
+ },
+ {
+ target: "#parent",
+ className: "purple-class",
+ force: false,
+ },
+ {
+ target: "#sibling",
+ className: "blue-class",
+ force: true,
+ },
+ {
+ target: "#sibling",
+ className: "blue-class",
+ force: false,
+ },
+];
+
+add_task(async function () {
+ const tab = await addTab(TEST_URI);
+
+ const { inspector } = await openRuleView();
+ await selectNode("#test", inspector);
+
+ info("Run the test on the rule-view");
+ await runViewTest(inspector, tab, "rule");
+
+ info("Switch to the computed view");
+ const onComputedViewReady = inspector.once("computed-view-refreshed");
+ selectComputedView(inspector);
+ await onComputedViewReady;
+
+ info("Run the test again on the computed view");
+ await runViewTest(inspector, tab, "computed");
+});
+
+async function runViewTest(inspector, tab, viewName) {
+ for (const { target, className, force } of TEST_DATA) {
+ info(
+ (force ? "Adding" : "Removing") +
+ ` class ${className} on ${target} and expecting a ${viewName}-view refresh`
+ );
+
+ await toggleClassAndWaitForViewChange(
+ { target, className, force },
+ inspector,
+ tab,
+ `${viewName}-view-refreshed`
+ );
+ }
+}
+
+async function toggleClassAndWaitForViewChange(
+ whatToMutate,
+ inspector,
+ tab,
+ eventName
+) {
+ const onRefreshed = inspector.once(eventName);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [whatToMutate],
+ function ({ target, className, force }) {
+ content.document.querySelector(target).classList.toggle(className, force);
+ }
+ );
+
+ await onRefreshed;
+ ok(true, "The view was refreshed after the class was changed");
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js
new file mode 100644
index 0000000000..638648d78e
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that background-image URLs have image preview tooltips in the rule-view
+// and computed-view
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ padding: 1em;
+ background-image: url(data:image/png;base64,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);
+ background-repeat: repeat-y;
+ background-position: right top;
+ }
+ .test-element {
+ font-family: verdana;
+ color: #333;
+ background: url(chrome://global/skin/icons/help.svg) no-repeat left center;
+ padding-left: 70px;
+ }
+ </style>
+ <div class="test-element">test element</div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let { inspector, view } = await openRuleView();
+
+ info("Testing the background-image property on the body rule");
+ await testBodyRuleView(view);
+
+ info("Selecting the test div node");
+ await selectNode(".test-element", inspector);
+ info("Testing the the background property on the .test-element rule");
+ await testDivRuleView(view);
+
+ info(
+ "Testing that image preview tooltips show even when there are " +
+ "fields being edited"
+ );
+ await testTooltipAppearsEvenInEditMode(view);
+
+ info("Switching over to the computed-view");
+ const onComputedViewReady = inspector.once("computed-view-refreshed");
+ view = selectComputedView(inspector);
+ await onComputedViewReady;
+
+ info("Testing that the background-image computed style has a tooltip too");
+ await testComputedView(view);
+});
+
+async function testBodyRuleView(view) {
+ info("Testing tooltips in the rule view");
+
+ // XXX we have an intermittent here (Bug 1743594) where the rule view is still empty
+ // at this point. We're currently investigating what's going on and a proper way to
+ // wait in openRuleView, but for now, let's fix the intermittent by waiting until the
+ // rule view has the expected content.
+ const property = await waitFor(() =>
+ getRuleViewProperty(view, "body", "background-image")
+ );
+
+ // Get the background-image property inside the rule view
+ const { valueSpan } = property;
+ const uriSpan = valueSpan.querySelector(".theme-link");
+
+ const previewTooltip = await assertShowPreviewTooltip(view, uriSpan);
+
+ const images = previewTooltip.panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(
+ images[0]
+ .getAttribute("src")
+ .includes("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe"),
+ "The image URL seems fine"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan);
+}
+
+async function testDivRuleView(view) {
+ // Get the background property inside the rule view
+ const { valueSpan } = getRuleViewProperty(
+ view,
+ ".test-element",
+ "background"
+ );
+ const uriSpan = valueSpan.querySelector(".theme-link");
+
+ const previewTooltip = await assertShowPreviewTooltip(view, uriSpan);
+
+ const images = previewTooltip.panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(
+ images[0].getAttribute("src").startsWith("data:"),
+ "Tooltip contains a data-uri image as expected"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan);
+}
+
+async function testTooltipAppearsEvenInEditMode(view) {
+ info("Switching to edit mode in the rule view");
+ const editor = await turnToEditMode(view);
+
+ info("Now trying to show the preview tooltip");
+ const { valueSpan } = getRuleViewProperty(
+ view,
+ ".test-element",
+ "background"
+ );
+ const uriSpan = valueSpan.querySelector(".theme-link");
+
+ const previewTooltip = await assertShowPreviewTooltip(view, uriSpan);
+
+ is(
+ view.styleDocument.activeElement,
+ editor.input,
+ "Tooltip was shown in edit mode, and inplace-editor still focused"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan);
+}
+
+function turnToEditMode(ruleView) {
+ const brace = ruleView.styleDocument.querySelector(".ruleview-ruleclose");
+ return focusEditableField(ruleView, brace);
+}
+
+async function testComputedView(view) {
+ const { valueSpan } = getComputedViewProperty(view, "background-image");
+ const uriSpan = valueSpan.querySelector(".theme-link");
+
+ // Scroll to ensure the line is visible as we see the box model by default
+ valueSpan.scrollIntoView();
+
+ const previewTooltip = await assertShowPreviewTooltip(view, uriSpan);
+
+ const images = previewTooltip.panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+
+ ok(
+ images[0].getAttribute("src").startsWith("data:"),
+ "Tooltip contains a data-uri in the computed-view too"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan);
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js
new file mode 100644
index 0000000000..3fe58aa63d
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that if a tooltip is visible when a new selection is made, it closes
+
+const TEST_URI = "<div class='one'>el 1</div><div class='two'>el 2</div>";
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let { inspector, view } = await openRuleView();
+ await selectNode(".one", inspector);
+
+ info("Testing rule view tooltip closes on new selection");
+ await testRuleView(view, inspector);
+
+ info("Testing computed view tooltip closes on new selection");
+ view = selectComputedView(inspector);
+ await testComputedView(view, inspector);
+});
+
+async function testRuleView(ruleView, inspector) {
+ info("Showing the tooltip");
+
+ const tooltip = ruleView.tooltips.getTooltip("previewTooltip");
+ const tooltipContent = ruleView.styleDocument.createElementNS(
+ XHTML_NS,
+ "div"
+ );
+ tooltip.panel.appendChild(tooltipContent);
+ tooltip.setContentSize({ width: 100, height: 30 });
+
+ // Stop listening for mouse movements because it's not needed for this test,
+ // and causes intermittent failures on Linux. When this test runs in the suite
+ // sometimes a mouseleave event is dispatched at the start, which causes the
+ // tooltip to hide in the middle of being shown, which causes timeouts later.
+ tooltip.stopTogglingOnHover();
+
+ const onShown = tooltip.once("shown");
+ tooltip.show(ruleView.styleDocument.firstElementChild);
+ await onShown;
+
+ info("Selecting a new node");
+ const onHidden = tooltip.once("hidden");
+ await selectNode(".two", inspector);
+ await onHidden;
+
+ ok(true, "Rule view tooltip closed after a new node got selected");
+}
+
+async function testComputedView(computedView, inspector) {
+ info("Showing the tooltip");
+
+ const tooltip = computedView.tooltips.getTooltip("previewTooltip");
+ const tooltipContent = computedView.styleDocument.createElementNS(
+ XHTML_NS,
+ "div"
+ );
+ tooltip.panel.appendChild(tooltipContent);
+ await tooltip.setContentSize({ width: 100, height: 30 });
+
+ // Stop listening for mouse movements because it's not needed for this test,
+ // and causes intermittent failures on Linux. When this test runs in the suite
+ // sometimes a mouseleave event is dispatched at the start, which causes the
+ // tooltip to hide in the middle of being shown, which causes timeouts later.
+ tooltip.stopTogglingOnHover();
+
+ const onShown = tooltip.once("shown");
+ tooltip.show(computedView.styleDocument.firstElementChild);
+ await onShown;
+
+ info("Selecting a new node");
+ const onHidden = tooltip.once("hidden");
+ await selectNode(".one", inspector);
+ await onHidden;
+
+ ok(true, "Computed view tooltip closed after a new node got selected");
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js
new file mode 100644
index 0000000000..8a31e94918
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js
@@ -0,0 +1,178 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the fontfamily tooltip on longhand properties
+
+const TEST_URI = `
+ <style type="text/css">
+ #testElement {
+ font-family: cursive;
+ color: #333;
+ padding-left: 70px;
+ }
+ </style>
+ <div id="testElement">test element</div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let { inspector, view } = await openRuleView();
+ await selectNode("#testElement", inspector);
+ await testRuleView(view, inspector.selection.nodeFront);
+
+ info("Opening the computed view");
+ const onComputedViewReady = inspector.once("computed-view-refreshed");
+ view = selectComputedView(inspector);
+ await onComputedViewReady;
+
+ await testComputedView(view, inspector.selection.nodeFront);
+
+ await testExpandedComputedViewProperty(view, inspector.selection.nodeFront);
+});
+
+async function testRuleView(ruleView, nodeFront) {
+ info("Testing font-family tooltips in the rule view");
+
+ const tooltip = ruleView.tooltips.getTooltip("previewTooltip");
+ const panel = tooltip.panel;
+
+ // Check that the rule view has a tooltip and that a XUL panel has
+ // been created
+ ok(tooltip, "Tooltip instance exists");
+ ok(panel, "XUL panel exists");
+
+ // Get the font family property inside the rule view
+ const { valueSpan } = getRuleViewProperty(
+ ruleView,
+ "#testElement",
+ "font-family"
+ );
+
+ // And verify that the tooltip gets shown on this property
+ valueSpan.scrollIntoView(true);
+ let previewTooltip = await assertShowPreviewTooltip(ruleView, valueSpan);
+
+ let images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(
+ images[0].getAttribute("src").startsWith("data:"),
+ "Tooltip contains a data-uri image as expected"
+ );
+
+ let dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(
+ images[0].getAttribute("src"),
+ dataURL,
+ "Tooltip contains the correct data-uri image"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan);
+
+ // Do the tooltip test again, but now when hovering on the span that
+ // encloses each and every font family.
+ const fontFamilySpan = valueSpan.querySelector(".ruleview-font-family");
+ fontFamilySpan.scrollIntoView(true);
+
+ previewTooltip = await assertShowPreviewTooltip(ruleView, fontFamilySpan);
+
+ images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(
+ images[0].getAttribute("src").startsWith("data:"),
+ "Tooltip contains a data-uri image as expected"
+ );
+
+ dataURL = await getFontFamilyDataURL(fontFamilySpan.textContent, nodeFront);
+ is(
+ images[0].getAttribute("src"),
+ dataURL,
+ "Tooltip contains the correct data-uri image"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, fontFamilySpan);
+}
+
+async function testComputedView(computedView, nodeFront) {
+ info("Testing font-family tooltips in the computed view");
+
+ const tooltip = computedView.tooltips.getTooltip("previewTooltip");
+ const panel = tooltip.panel;
+ const { valueSpan } = getComputedViewProperty(computedView, "font-family");
+
+ valueSpan.scrollIntoView(true);
+ const previewTooltip = await assertShowPreviewTooltip(
+ computedView,
+ valueSpan
+ );
+
+ const images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(
+ images[0].getAttribute("src").startsWith("data:"),
+ "Tooltip contains a data-uri image as expected"
+ );
+
+ const dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(
+ images[0].getAttribute("src"),
+ dataURL,
+ "Tooltip contains the correct data-uri image"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan);
+}
+
+async function testExpandedComputedViewProperty(computedView, nodeFront) {
+ info(
+ "Testing font-family tooltips in expanded properties of the " +
+ "computed view"
+ );
+
+ info("Expanding the font-family property to reveal matched selectors");
+ const propertyView = getPropertyView(computedView, "font-family");
+ propertyView.matchedExpanded = true;
+ await propertyView.refreshMatchedSelectors();
+
+ const valueSpan = propertyView.matchedSelectorsContainer.querySelector(
+ ".bestmatch .computed-other-property-value"
+ );
+
+ const tooltip = computedView.tooltips.getTooltip("previewTooltip");
+ const panel = tooltip.panel;
+
+ valueSpan.scrollIntoView(true);
+ const previewTooltip = await assertShowPreviewTooltip(
+ computedView,
+ valueSpan
+ );
+
+ const images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(
+ images[0].getAttribute("src").startsWith("data:"),
+ "Tooltip contains a data-uri image as expected"
+ );
+
+ const dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(
+ images[0].getAttribute("src"),
+ dataURL,
+ "Tooltip contains the correct data-uri image"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan);
+}
+
+function getPropertyView(computedView, name) {
+ let propertyView = null;
+ computedView.propertyViews.some(function (view) {
+ if (view.name == name) {
+ propertyView = view;
+ return true;
+ }
+ return false;
+ });
+ return propertyView;
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js
new file mode 100644
index 0000000000..5caab37555
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test for bug 1026921: Ensure the URL of hovered url() node is used instead
+// of the first found from the declaration as there might be multiple urls.
+
+const YELLOW_DOT =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gYcDCwCr0o5ngAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAANSURBVAjXY/j/n6EeAAd9An7Z55GEAAAAAElFTkSuQmCC";
+const BLUE_DOT =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gYcDCwlCkCM9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAANSURBVAjXY2Bg+F8PAAKCAX/tPkrkAAAAAElFTkSuQmCC";
+const TEST_STYLE = `h1 {background: url(${YELLOW_DOT}), url(${BLUE_DOT});}`;
+const TEST_URI = `<style>${TEST_STYLE}</style><h1>test element</h1>`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector } = await openInspector();
+
+ await testRuleViewUrls(inspector);
+ await testComputedViewUrls(inspector);
+});
+
+async function testRuleViewUrls(inspector) {
+ info("Testing tooltips in the rule view");
+ const view = selectRuleView(inspector);
+ await selectNode("h1", inspector);
+
+ const { valueSpan } = getRuleViewProperty(view, "h1", "background");
+ await performChecks(view, valueSpan);
+}
+
+async function testComputedViewUrls(inspector) {
+ info("Testing tooltips in the computed view");
+
+ const onComputedViewReady = inspector.once("computed-view-refreshed");
+ const view = selectComputedView(inspector);
+ await onComputedViewReady;
+
+ const { valueSpan } = getComputedViewProperty(view, "background-image");
+
+ await performChecks(view, valueSpan);
+}
+
+/**
+ * A helper that checks url() tooltips contain correct images
+ */
+async function performChecks(view, propertyValue) {
+ function checkTooltip(panel, imageSrc) {
+ const images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ is(images[0].getAttribute("src"), imageSrc, "The image URL is correct");
+ }
+
+ const links = propertyValue.querySelectorAll(".theme-link");
+
+ info("Checking first link tooltip");
+ let previewTooltip = await assertShowPreviewTooltip(view, links[0]);
+ const panel = view.tooltips.getTooltip("previewTooltip").panel;
+ checkTooltip(panel, YELLOW_DOT);
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, links[0]);
+
+ info("Checking second link tooltip");
+ previewTooltip = await assertShowPreviewTooltip(view, links[1]);
+ checkTooltip(panel, BLUE_DOT);
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, links[1]);
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js
new file mode 100644
index 0000000000..ef98a20a37
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the fontfamily tooltip on shorthand properties
+
+const TEST_URI = `
+ <style type="text/css">
+ #testElement {
+ font: italic bold .8em/1.2 Arial;
+ }
+ </style>
+ <div id="testElement">test element</div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, view } = await openRuleView();
+
+ await selectNode("#testElement", inspector);
+ await testRuleView(view, inspector.selection.nodeFront);
+});
+
+async function testRuleView(ruleView, nodeFront) {
+ info("Testing font-family tooltips in the rule view");
+
+ const tooltip = ruleView.tooltips.getTooltip("previewTooltip");
+ const panel = tooltip.panel;
+
+ // Check that the rule view has a tooltip and that a XUL panel has
+ // been created
+ ok(tooltip, "Tooltip instance exists");
+ ok(panel, "XUL panel exists");
+
+ // Get the computed font family property inside the font rule view
+ const propertyList = ruleView.element.querySelectorAll(
+ ".ruleview-propertylist"
+ );
+ const fontExpander =
+ propertyList[1].querySelectorAll(".ruleview-expander")[0];
+ fontExpander.click();
+
+ const rule = getRuleViewRule(ruleView, "#testElement");
+ const computedlist = rule.querySelectorAll(".ruleview-computed");
+ let valueSpan;
+ for (const computed of computedlist) {
+ const propertyName = computed.querySelector(".ruleview-propertyname");
+ if (propertyName.textContent == "font-family") {
+ valueSpan = computed.querySelector(".ruleview-propertyvalue");
+ break;
+ }
+ }
+
+ // And verify that the tooltip gets shown on this property
+ const previewTooltip = await assertShowPreviewTooltip(ruleView, valueSpan);
+
+ const images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(
+ images[0].getAttribute("src").startsWith("data:"),
+ "Tooltip contains a data-uri image as expected"
+ );
+
+ const dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(
+ images[0].getAttribute("src"),
+ dataURL,
+ "Tooltip contains the correct data-uri image"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan);
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js
new file mode 100644
index 0000000000..4458477a0c
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Checking tooltips dimensions, to make sure their big enough to display their
+// content
+
+const TEST_URI = `
+ <style type="text/css">
+ div {
+ width: 300px;height: 300px;border-radius: 50%;
+ background: red url(chrome://global/skin/icons/help.svg);
+ }
+ </style>
+ <div></div>
+`;
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, view } = await openRuleView();
+ await selectNode("div", inspector);
+ await testImageDimension(view);
+ await testPickerDimension(view);
+});
+
+async function testImageDimension(ruleView) {
+ info("Testing background-image tooltip dimensions");
+
+ const tooltip = ruleView.tooltips.getTooltip("previewTooltip");
+ const panel = tooltip.panel;
+ const { valueSpan } = getRuleViewProperty(ruleView, "div", "background");
+ const uriSpan = valueSpan.querySelector(".theme-link");
+
+ // Make sure there is a hover tooltip for this property, this also will fill
+ // the tooltip with its content
+ const previewTooltip = await assertShowPreviewTooltip(ruleView, uriSpan);
+
+ // Let's not test for a specific size, but instead let's make sure it's at
+ // least as big as the image
+ const imageRect = panel.querySelector("img").getBoundingClientRect();
+ const panelRect = panel.getBoundingClientRect();
+
+ Assert.greaterOrEqual(
+ panelRect.width,
+ imageRect.width,
+ "The panel is wide enough to show the image"
+ );
+ Assert.greaterOrEqual(
+ panelRect.height,
+ imageRect.height,
+ "The panel is high enough to show the image"
+ );
+
+ await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan);
+}
+
+async function testPickerDimension(ruleView) {
+ info("Testing color-picker tooltip dimensions");
+
+ const { valueSpan } = getRuleViewProperty(ruleView, "div", "background");
+ const swatch = valueSpan.querySelector(".ruleview-colorswatch");
+ const cPicker = ruleView.tooltips.getTooltip("colorPicker");
+
+ const onReady = cPicker.once("ready");
+ swatch.click();
+ await onReady;
+
+ // The colorpicker spectrum's iframe has a fixed width height, so let's
+ // make sure the tooltip is at least as big as that
+ const spectrumRect = cPicker.spectrum.element.getBoundingClientRect();
+ const panelRect = cPicker.tooltip.container.getBoundingClientRect();
+
+ Assert.greaterOrEqual(
+ panelRect.width,
+ spectrumRect.width,
+ "The panel is wide enough to show the picker"
+ );
+ Assert.greaterOrEqual(
+ panelRect.height,
+ spectrumRect.height,
+ "The panel is high enough to show the picker"
+ );
+
+ const onHidden = cPicker.tooltip.once("hidden");
+ const onRuleViewChanged = ruleView.once("ruleview-changed");
+ cPicker.hide();
+ await onHidden;
+ await onRuleViewChanged;
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js
new file mode 100644
index 0000000000..b4297a66c2
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the css transform highlighter is created only when asked and only one
+// instance exists across the inspector
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ transform: skew(16deg);
+ }
+ </style>
+ Test the css transform highlighter
+`;
+
+const TYPE = "CssTransformHighlighter";
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, view } = await openRuleView();
+
+ let overlay = view.highlighters;
+
+ ok(!overlay.highlighters[TYPE], "No highlighter exists in the rule-view");
+ const h = await overlay._getHighlighter(TYPE);
+ ok(
+ overlay.highlighters[TYPE],
+ "The highlighter has been created in the rule-view"
+ );
+ is(h, overlay.highlighters[TYPE], "The right highlighter has been created");
+ const h2 = await overlay._getHighlighter(TYPE);
+ is(
+ h,
+ h2,
+ "The same instance of highlighter is returned everytime in the rule-view"
+ );
+
+ const onComputedViewReady = inspector.once("computed-view-refreshed");
+ const cView = selectComputedView(inspector);
+ await onComputedViewReady;
+ overlay = cView.highlighters;
+
+ ok(overlay.highlighters[TYPE], "The highlighter exists in the computed-view");
+ const h3 = await overlay._getHighlighter(TYPE);
+ is(
+ h,
+ h3,
+ "The same instance of highlighter is returned everytime " +
+ "in the computed-view"
+ );
+});
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js
new file mode 100644
index 0000000000..f1ddc68892
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the css transform highlighter is created when hovering over a
+// transform property
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ transform: skew(16deg);
+ color: yellow;
+ }
+ </style>
+ Test the css transform highlighter
+`;
+
+var TYPE = "CssTransformHighlighter";
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, view } = await openRuleView();
+ let hs = view.highlighters;
+
+ ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (1)");
+
+ info("Faking a mousemove on a non-transform property");
+ let { valueSpan } = getRuleViewProperty(view, "body", "color");
+ hs.onMouseMove({ target: valueSpan });
+ ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (2)");
+
+ info("Faking a mousemove on a transform property");
+ ({ valueSpan } = getRuleViewProperty(view, "body", "transform"));
+ let onHighlighterShown = hs.once("css-transform-highlighter-shown");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterShown;
+
+ const onComputedViewReady = inspector.once("computed-view-refreshed");
+ const cView = selectComputedView(inspector);
+ await onComputedViewReady;
+ hs = cView.highlighters;
+
+ info("Remove the created transform highlighter");
+ hs.highlighters[TYPE].finalize();
+ hs.highlighters[TYPE] = null;
+
+ info("Faking a mousemove on a non-transform property");
+ ({ valueSpan } = getComputedViewProperty(cView, "color"));
+ hs.onMouseMove({ target: valueSpan });
+ ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (3)");
+
+ info("Faking a mousemove on a transform property");
+ ({ valueSpan } = getComputedViewProperty(cView, "transform"));
+ onHighlighterShown = hs.once("css-transform-highlighter-shown");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterShown;
+
+ ok(
+ hs.highlighters[TYPE],
+ "The highlighter has been created in the computed-view"
+ );
+});
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js
new file mode 100644
index 0000000000..ee95db2432
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the css transform highlighter is shown when hovering over transform
+// properties
+
+// Note that in this test, we mock the highlighter front, merely testing the
+// behavior of the style-inspector UI for now
+
+const TEST_URI = `
+ <style type="text/css">
+ html {
+ transform: scale(.9);
+ }
+ body {
+ transform: skew(16deg);
+ color: purple;
+ }
+ </style>
+ Test the css transform highlighter
+`;
+
+const TYPE = "CssTransformHighlighter";
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, view } = await openRuleView();
+
+ // Mock the highlighter front to get the reference of the NodeFront
+ const HighlighterFront = {
+ isShown: false,
+ nodeFront: null,
+ nbOfTimesShown: 0,
+ show(nodeFront) {
+ this.nodeFront = nodeFront;
+ this.isShown = true;
+ this.nbOfTimesShown++;
+ return Promise.resolve(true);
+ },
+ hide() {
+ this.nodeFront = null;
+ this.isShown = false;
+ return Promise.resolve();
+ },
+ finalize() {},
+ };
+
+ // Inject the mock highlighter in the rule-view
+ const hs = view.highlighters;
+ hs.highlighters[TYPE] = HighlighterFront;
+
+ let { valueSpan } = getRuleViewProperty(view, "body", "transform");
+
+ info("Checking that the HighlighterFront's show/hide methods are called");
+ let onHighlighterShown = hs.once("css-transform-highlighter-shown");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterShown;
+ ok(HighlighterFront.isShown, "The highlighter is shown");
+ let onHighlighterHidden = hs.once("css-transform-highlighter-hidden");
+ hs.onMouseOut();
+ await onHighlighterHidden;
+ ok(!HighlighterFront.isShown, "The highlighter is hidden");
+
+ info(
+ "Checking that hovering several times over the same property doesn't" +
+ " show the highlighter several times"
+ );
+ const nb = HighlighterFront.nbOfTimesShown;
+ onHighlighterShown = hs.once("css-transform-highlighter-shown");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterShown;
+ is(HighlighterFront.nbOfTimesShown, nb + 1, "The highlighter was shown once");
+ hs.onMouseMove({ target: valueSpan });
+ hs.onMouseMove({ target: valueSpan });
+ is(
+ HighlighterFront.nbOfTimesShown,
+ nb + 1,
+ "The highlighter was shown once, after several mousemove"
+ );
+
+ info("Checking that the right NodeFront reference is passed");
+ await selectNode("html", inspector);
+ ({ valueSpan } = getRuleViewProperty(view, "html", "transform"));
+ onHighlighterShown = hs.once("css-transform-highlighter-shown");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterShown;
+ is(
+ HighlighterFront.nodeFront.tagName,
+ "HTML",
+ "The right NodeFront is passed to the highlighter (1)"
+ );
+
+ await selectNode("body", inspector);
+ ({ valueSpan } = getRuleViewProperty(view, "body", "transform"));
+ onHighlighterShown = hs.once("css-transform-highlighter-shown");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterShown;
+ is(
+ HighlighterFront.nodeFront.tagName,
+ "BODY",
+ "The right NodeFront is passed to the highlighter (2)"
+ );
+
+ info(
+ "Checking that the highlighter gets hidden when hovering a " +
+ "non-transform property"
+ );
+ ({ valueSpan } = getRuleViewProperty(view, "body", "color"));
+ onHighlighterHidden = hs.once("css-transform-highlighter-hidden");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterHidden;
+ ok(!HighlighterFront.isShown, "The highlighter is hidden");
+});
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js
new file mode 100644
index 0000000000..1bf76d517b
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the css transform highlighter is shown only when hovering over a
+// transform declaration that isn't overriden or disabled
+
+// Note that unlike the other browser_styleinspector_transform-highlighter-N.js
+// tests, this one only tests the rule-view as only this view features disabled
+// and overriden properties
+
+const TEST_URI = `
+ <style type="text/css">
+ div {
+ background: purple;
+ width:300px;height:300px;
+ transform: rotate(16deg);
+ }
+ .test {
+ transform: skew(25deg);
+ }
+ </style>
+ <div class="test"></div>
+`;
+
+const TYPE = "CssTransformHighlighter";
+
+add_task(async function () {
+ await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ const { inspector, view } = await openRuleView();
+ await selectNode(".test", inspector);
+
+ const hs = view.highlighters;
+
+ info("Faking a mousemove on the overriden property");
+ let { valueSpan } = getRuleViewProperty(view, "div", "transform");
+ hs.onMouseMove({ target: valueSpan });
+ ok(
+ !hs.highlighters[TYPE],
+ "No highlighter was created for the overriden property"
+ );
+
+ info("Disabling the applied property");
+ const classRuleEditor = getRuleViewRuleEditor(view, 1);
+ const propEditor = classRuleEditor.rule.textProps[0].editor;
+ propEditor.enable.click();
+ await classRuleEditor.rule._applyingModifications;
+
+ info("Faking a mousemove on the disabled property");
+ ({ valueSpan } = getRuleViewProperty(view, ".test", "transform"));
+ hs.onMouseMove({ target: valueSpan });
+ ok(
+ !hs.highlighters[TYPE],
+ "No highlighter was created for the disabled property"
+ );
+
+ info("Faking a mousemove on the now unoverriden property");
+ ({ valueSpan } = getRuleViewProperty(view, "div", "transform"));
+ const onHighlighterShown = hs.once("css-transform-highlighter-shown");
+ hs.onMouseMove({ target: valueSpan });
+ await onHighlighterShown;
+});
diff --git a/devtools/client/inspector/shared/test/doc_content_style_changes.html b/devtools/client/inspector/shared/test/doc_content_style_changes.html
new file mode 100644
index 0000000000..c439f2bf4f
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_style_changes.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+#test {
+ color: red;
+}
+/* Adding/removing the green-class on #test should refresh the rule-view when #test is
+ selected */
+#test.green-class {
+ color: green;
+}
+/* Adding/removing the purple-class on #parent should refresh the rule-view when #test is
+ selected */
+#parent.purple-class #test {
+ color: purple;
+}
+/* Adding/removing the blue-class on #sibling should refresh the rule-view when #test is
+ selected*/
+#sibling.blue-class + #test {
+ color: blue;
+}
+</style>
+<div id="parent">
+ <div>
+ <div id="sibling"></div>
+ <div id="test">test</div>
+ </div>
+</div>
diff --git a/devtools/client/inspector/shared/test/head.js b/devtools/client/inspector/shared/test/head.js
new file mode 100644
index 0000000000..f87fed0f03
--- /dev/null
+++ b/devtools/client/inspector/shared/test/head.js
@@ -0,0 +1,218 @@
+/* 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
+);
+
+var {
+ CssRuleView,
+} = require("resource://devtools/client/inspector/rules/rules.js");
+var {
+ getInplaceEditorForSpan: inplaceEditor,
+} = require("resource://devtools/client/shared/inplace-editor.js");
+const {
+ getColor: getThemeColor,
+} = require("resource://devtools/client/shared/theme.js");
+
+const TEST_URL_ROOT =
+ "http://example.com/browser/devtools/client/inspector/shared/test/";
+const TEST_URL_ROOT_SSL =
+ "https://example.com/browser/devtools/client/inspector/shared/test/";
+const ROOT_TEST_DIR = getRootDirectory(gTestPath);
+const STYLE_INSPECTOR_L10N = new LocalizationHelper(
+ "devtools/shared/locales/styleinspector.properties"
+);
+
+// Clean-up all prefs that might have been changed during a test run
+// (safer here because if the test fails, then the pref is never reverted)
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.defaultColorUnit");
+});
+
+/**
+ * The functions found below are here to ease test development and maintenance.
+ * Most of these functions are stateless and will require some form of context
+ * (the instance of the current toolbox, or inspector panel for instance).
+ *
+ * Most of these functions are async too and return promises.
+ *
+ * All tests should follow the following pattern:
+ *
+ * add_task(async function() {
+ * await addTab(TEST_URI);
+ * let {toolbox, inspector} = await openInspector();
+ * await inspector.sidebar.select(viewId);
+ * let view = inspector.getPanel(viewId).view;
+ * await selectNode("#test", inspector);
+ * await someAsyncTestFunction(view);
+ * });
+ *
+ * add_task is the way to define the testcase in the test file. It accepts
+ * a single argument: a function returning a promise (usually async function).
+ *
+ * There is no need to clean tabs up at the end of a test as this is done
+ * automatically.
+ *
+ * It is advised not to store any references on the global scope. There
+ * shouldn't be a need to anyway. Thanks to async functions, test steps, even
+ * though asynchronous, can be described in a nice flat way, and
+ * if/for/while/... control flow can be used as in sync code, making it
+ * possible to write the outline of the test case all in add_task, and delegate
+ * actual processing and assertions to other functions.
+ */
+
+/* *********************************************
+ * UTILS
+ * *********************************************
+ * General test utilities.
+ * Add new tabs, open the toolbox and switch to the various panels, select
+ * nodes, get node references, ...
+ */
+
+/**
+ * Polls a given function waiting for it to return true.
+ *
+ * @param {Function} validatorFn
+ * A validator function that returns a boolean.
+ * This is called every few milliseconds to check if the result is true.
+ * When it is true, the promise resolves.
+ * @param {String} name
+ * Optional name of the test. This is used to generate
+ * the success and failure messages.
+ * @return a promise that resolves when the function returned true or rejects
+ * if the timeout is reached
+ */
+function waitForSuccess(validatorFn, name = "untitled") {
+ return new Promise(resolve => {
+ function wait(validator) {
+ if (validator()) {
+ ok(true, "Validator function " + name + " returned true");
+ resolve();
+ } else {
+ setTimeout(() => wait(validator), 200);
+ }
+ }
+ wait(validatorFn);
+ });
+}
+
+/**
+ * Get the dataURL for the font family tooltip.
+ *
+ * @param {String} font
+ * The font family value.
+ * @param {object} nodeFront
+ * The NodeActor that will used to retrieve the dataURL for the
+ * font family tooltip contents.
+ */
+var getFontFamilyDataURL = async function (font, nodeFront) {
+ const fillStyle = getThemeColor("body-color");
+
+ const { data } = await nodeFront.getFontFamilyDataURL(font, fillStyle);
+ const dataURL = await data.string();
+ return dataURL;
+};
+
+/* *********************************************
+ * RULE-VIEW
+ * *********************************************
+ * Rule-view related test utility functions
+ * This object contains functions to get rules, get properties, ...
+ */
+
+/**
+ * Simulate a color change in a given color picker tooltip, and optionally wait
+ * for a given element in the page to have its style changed as a result
+ *
+ * @param {RuleView} ruleView
+ * The related rule view instance
+ * @param {SwatchColorPickerTooltip} colorPicker
+ * @param {Array} newRgba
+ * The new color to be set [r, g, b, a]
+ * @param {Object} expectedChange
+ * Optional object that needs the following props:
+ * - {DOMNode} element The element in the page that will have its
+ * style changed.
+ * - {String} name The style name that will be changed
+ * - {String} value The expected style value
+ * The style will be checked like so: getComputedStyle(element)[name] === value
+ */
+var simulateColorPickerChange = async function (
+ ruleView,
+ colorPicker,
+ newRgba,
+ expectedChange
+) {
+ const onRuleViewChanged = ruleView.once("ruleview-changed");
+ info("Getting the spectrum colorpicker object");
+ const spectrum = await colorPicker.spectrum;
+ info("Setting the new color");
+ spectrum.rgb = newRgba;
+ info("Applying the change");
+ spectrum.updateUI();
+ spectrum.onChange();
+ info("Waiting for rule-view to update");
+ await onRuleViewChanged;
+
+ if (expectedChange) {
+ info("Waiting for the style to be applied on the page");
+ await waitForSuccess(() => {
+ const { element, name, value } = expectedChange;
+ return content.getComputedStyle(element)[name] === value;
+ }, "Color picker change applied on the page");
+ }
+};
+
+/* *********************************************
+ * COMPUTED-VIEW
+ * *********************************************
+ * Computed-view related utility functions.
+ * Allows to get properties, links, expand properties, ...
+ */
+
+/**
+ * Get references to the name and value span nodes corresponding to a given
+ * property name in the computed-view
+ *
+ * @param {CssComputedView} view
+ * The instance of the computed view panel
+ * @param {String} name
+ * The name of the property to retrieve
+ * @return an object {nameSpan, valueSpan}
+ */
+function getComputedViewProperty(view, name) {
+ let prop;
+ for (const property of view.styleDocument.querySelectorAll(
+ ".computed-property-view"
+ )) {
+ const nameSpan = property.querySelector(".computed-property-name");
+ const valueSpan = property.querySelector(".computed-property-value");
+
+ if (nameSpan.firstChild.textContent === name) {
+ prop = { nameSpan, valueSpan };
+ break;
+ }
+ }
+ return prop;
+}
+
+/**
+ * Get the text value of the property corresponding to a given name in the
+ * computed-view
+ *
+ * @param {CssComputedView} view
+ * The instance of the computed view panel
+ * @param {String} name
+ * The name of the property to retrieve
+ * @return {String} The property value
+ */
+function getComputedViewPropertyValue(view, name, propertyName) {
+ return getComputedViewProperty(view, name, propertyName).valueSpan
+ .textContent;
+}