summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/shared/test
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/client/inspector/shared/test
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/inspector/shared/test')
-rw-r--r--devtools/client/inspector/shared/test/.eslintrc.js6
-rw-r--r--devtools/client/inspector/shared/test/browser.ini41
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js118
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js99
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js109
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_csslogic-content-stylesheets.js82
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js341
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js43
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js125
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js73
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js120
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js63
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js58
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js86
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js48
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js57
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js103
-rw-r--r--devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js60
-rw-r--r--devtools/client/inspector/shared/test/doc_author-sheet.html37
-rw-r--r--devtools/client/inspector/shared/test/doc_content_stylesheet.html32
-rw-r--r--devtools/client/inspector/shared/test/doc_content_stylesheet.xul9
-rw-r--r--devtools/client/inspector/shared/test/doc_content_stylesheet_imported.css5
-rw-r--r--devtools/client/inspector/shared/test/doc_content_stylesheet_imported2.css3
-rw-r--r--devtools/client/inspector/shared/test/doc_content_stylesheet_linked.css3
-rw-r--r--devtools/client/inspector/shared/test/doc_content_stylesheet_script.css5
-rw-r--r--devtools/client/inspector/shared/test/doc_content_stylesheet_xul.css3
-rw-r--r--devtools/client/inspector/shared/test/doc_frame_script.js115
-rw-r--r--devtools/client/inspector/shared/test/head.js557
28 files changed, 2401 insertions, 0 deletions
diff --git a/devtools/client/inspector/shared/test/.eslintrc.js b/devtools/client/inspector/shared/test/.eslintrc.js
new file mode 100644
index 000000000..698ae9181
--- /dev/null
+++ b/devtools/client/inspector/shared/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/inspector/shared/test/browser.ini b/devtools/client/inspector/shared/test/browser.ini
new file mode 100644
index 000000000..ce85ee80e
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser.ini
@@ -0,0 +1,41 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ doc_author-sheet.html
+ doc_content_stylesheet.html
+ doc_content_stylesheet.xul
+ doc_content_stylesheet_imported.css
+ doc_content_stylesheet_imported2.css
+ doc_content_stylesheet_linked.css
+ doc_content_stylesheet_script.css
+ doc_content_stylesheet_xul.css
+ doc_frame_script.js
+ head.js
+ !/devtools/client/commandline/test/helpers.js
+ !/devtools/client/framework/test/shared-head.js
+ !/devtools/client/inspector/test/head.js
+ !/devtools/client/inspector/test/shared-head.js
+ !/devtools/client/shared/test/test-actor.js
+ !/devtools/client/shared/test/test-actor-registry.js
+
+[browser_styleinspector_context-menu-copy-color_01.js]
+[browser_styleinspector_context-menu-copy-color_02.js]
+subsuite = clipboard
+[browser_styleinspector_context-menu-copy-urls.js]
+subsuite = clipboard
+[browser_styleinspector_csslogic-content-stylesheets.js]
+skip-if = e10s && debug # Bug 1250058 (docshell leak when opening 2 toolboxes)
+[browser_styleinspector_output-parser.js]
+[browser_styleinspector_refresh_when_active.js]
+[browser_styleinspector_tooltip-background-image.js]
+[browser_styleinspector_tooltip-closes-on-new-selection.js]
+skip-if = e10s # Bug 1111546 (e10s)
+[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 000000000..5a27edf16
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
@@ -0,0 +1,118 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ // Test is slow on Linux EC2 instances - Bug 1137765
+ requestLongerTimeout(2);
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector} = yield openInspector();
+ yield testView("ruleview", inspector);
+ yield testView("computedview", inspector);
+});
+
+function* testView(viewId, inspector) {
+ info("Testing " + viewId);
+
+ yield inspector.sidebar.select(viewId);
+ let view = inspector[viewId].view || inspector[viewId].computedView;
+ yield selectNode("div", inspector);
+
+ testIsColorValueNode(view);
+ testIsColorPopupOnAllNodes(view);
+ yield 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.");
+ let root = rootElement(view);
+ let colorNode = root.querySelector("span[data-color]");
+
+ ok(colorNode, "Color node found");
+ for (let node of iterateNodes(colorNode)) {
+ ok(isColorValueNode(node), "Node is part of color value.");
+ }
+}
+
+/**
+ * A function testing that _isColorPopup returns a correct value for all nodes
+ * in the view.
+ */
+function testIsColorPopupOnAllNodes(view) {
+ let root = rootElement(view);
+ for (let node of iterateNodes(root)) {
+ testIsColorPopupOnNode(view, node);
+ }
+}
+
+/**
+ * Test result of _isColorPopup with given node.
+ * @param object view
+ * A CSSRuleView or CssComputedView instance.
+ * @param Node node
+ * A node to check.
+ */
+function testIsColorPopupOnNode(view, node) {
+ info("Testing node " + node);
+ view.styleDocument.popupNode = node;
+ view._contextmenu._colorToCopy = "";
+
+ let result = view._contextmenu._isColorPopup();
+ let correct = isColorValueNode(node);
+
+ is(result, correct, "_isColorPopup returned the expected value " + correct);
+ is(view._contextmenu._colorToCopy, (correct) ? "rgb(18, 58, 188)" : "",
+ "_colorToCopy was set to the expected 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;
+
+ let 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 (let 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 000000000..afae7a2b6
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js
@@ -0,0 +1,99 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+
+ let {inspector, view} = yield openRuleView();
+
+ yield testCopyToClipboard(inspector, view);
+ yield testManualEdit(inspector, view);
+ yield testColorPickerEdit(inspector, view);
+});
+
+function* testCopyToClipboard(inspector, view) {
+ info("Testing that color is copied to clipboard");
+
+ yield selectNode("div", inspector);
+
+ let element = getRuleViewProperty(view, "div", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, element);
+ let menuitemCopyColor = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyColor"));
+
+ ok(menuitemCopyColor.visible, "Copy color is visible");
+
+ yield waitForClipboardPromise(() => menuitemCopyColor.click(),
+ "#123ABC");
+
+ EventUtils.synthesizeKey("VK_ESCAPE", { });
+}
+
+function* testManualEdit(inspector, view) {
+ info("Testing manually edited colors");
+ yield selectNode("div", inspector);
+
+ let {valueSpan} = getRuleViewProperty(view, "div", "color");
+
+ let newColor = "#C9184E";
+ let editor = yield focusEditableField(view, valueSpan);
+
+ info("Typing new value");
+ let input = editor.input;
+ let onBlur = once(input, "blur");
+ EventUtils.sendString(newColor + ";", view.styleWindow);
+ yield onBlur;
+ yield wait(1);
+
+ let colorValueElement = getRuleViewProperty(view, "div", "color")
+ .valueSpan.firstChild;
+ is(colorValueElement.dataset.color, newColor, "data-color was updated");
+
+ view.styleDocument.popupNode = colorValueElement;
+
+ let contextMenu = view._contextmenu;
+ contextMenu._isColorPopup();
+ is(contextMenu._colorToCopy, newColor, "_colorToCopy has the new value");
+}
+
+function* testColorPickerEdit(inspector, view) {
+ info("Testing colors edited via color picker");
+ yield selectNode("div", inspector);
+
+ let swatchElement = getRuleViewProperty(view, "div", "color").valueSpan
+ .querySelector(".ruleview-colorswatch");
+
+ info("Opening the color picker");
+ let picker = view.tooltips.colorPicker;
+ let onColorPickerReady = picker.once("ready");
+ swatchElement.click();
+ yield onColorPickerReady;
+
+ let rgbaColor = [83, 183, 89, 1];
+ let rgbaColorText = "rgba(83, 183, 89, 1)";
+ yield simulateColorPickerChange(view, picker, rgbaColor);
+
+ is(swatchElement.parentNode.dataset.color, rgbaColorText,
+ "data-color was updated");
+ view.styleDocument.popupNode = swatchElement;
+
+ let contextMenu = view._contextmenu;
+ contextMenu._isColorPopup();
+ is(contextMenu._colorToCopy, rgbaColorText, "_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 000000000..412137825
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js
@@ -0,0 +1,109 @@
+/* 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(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>`;
+
+ yield addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI));
+
+ yield startTest();
+});
+
+function* startTest() {
+ info("Opening rule view");
+ let {inspector, view} = yield openRuleView();
+
+ info("Test valid background image URL in rule view");
+ yield testCopyUrlToClipboard({view, inspector}, "data-uri",
+ ".valid-background", TEST_DATA_URI);
+ yield testCopyUrlToClipboard({view, inspector}, "url",
+ ".valid-background", TEST_DATA_URI);
+
+ info("Test invalid background image URL in rue view");
+ yield testCopyUrlToClipboard({view, inspector}, "data-uri",
+ ".invalid-background", ERROR_MESSAGE);
+ yield 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");
+ yield testCopyUrlToClipboard({view, inspector}, "data-uri",
+ ".valid-background", TEST_DATA_URI);
+ yield testCopyUrlToClipboard({view, inspector}, "url",
+ ".valid-background", TEST_DATA_URI);
+
+ info("Test invalid background image URL in computed view");
+ yield testCopyUrlToClipboard({view, inspector}, "data-uri",
+ ".invalid-background", ERROR_MESSAGE);
+ yield testCopyUrlToClipboard({view, inspector}, "url",
+ ".invalid-background", INVALID_IMAGE_URI);
+}
+
+function* testCopyUrlToClipboard({view, inspector}, type, selector, expected) {
+ info("Select node in inspector panel");
+ yield selectNode(selector, inspector);
+
+ info("Retrieve background-image link for selected node in current " +
+ "styleinspector view");
+ let property = getBackgroundImageProperty(view, selector);
+ let imageLink = property.valueSpan.querySelector(".theme-link");
+ ok(imageLink, "Background-image link element found");
+
+ info("Simulate right click on the background-image URL");
+ let allMenuItems = openStyleContextMenuAndGetAllItems(view, imageLink);
+ let menuitemCopyUrl = allMenuItems.find(item => item.label ===
+ STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyUrl"));
+ let 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");
+ yield waitForClipboardPromise(() => {
+ return menuitemCopyImageDataUrl.click();
+ }, expected);
+ } else {
+ info("Click Copy URL and wait for clipboard");
+ yield waitForClipboardPromise(() => {
+ return menuitemCopyUrl.click();
+ }, expected);
+ }
+
+ info("Hide context menu");
+}
+
+function getBackgroundImageProperty(view, selector) {
+ let isRuleView = view instanceof CssRuleView;
+ if (isRuleView) {
+ return getRuleViewProperty(view, selector, "background-image");
+ }
+ return getComputedViewProperty(view, "background-image");
+}
diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_csslogic-content-stylesheets.js b/devtools/client/inspector/shared/test/browser_styleinspector_csslogic-content-stylesheets.js
new file mode 100644
index 000000000..421a2bb47
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_csslogic-content-stylesheets.js
@@ -0,0 +1,82 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check stylesheets on HMTL and XUL document
+
+// FIXME: this test opens the devtools for nothing, it should be changed into a
+// devtools/server/tests/mochitest/test_css-logic-...something...html
+// test
+
+const TEST_URI_HTML = TEST_URL_ROOT + "doc_content_stylesheet.html";
+const TEST_URI_AUTHOR = TEST_URL_ROOT + "doc_author-sheet.html";
+const TEST_URI_XUL = TEST_URL_ROOT + "doc_content_stylesheet.xul";
+const XUL_URI = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(TEST_URI_XUL, null, null);
+var ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+const XUL_PRINCIPAL = ssm.createCodebasePrincipal(XUL_URI, {});
+
+add_task(function* () {
+ requestLongerTimeout(2);
+
+ info("Checking stylesheets on HTML document");
+ yield addTab(TEST_URI_HTML);
+
+ let {inspector, testActor} = yield openInspector();
+ yield selectNode("#target", inspector);
+
+ info("Checking stylesheets");
+ yield checkSheets("#target", testActor);
+
+ info("Checking authored stylesheets");
+ yield addTab(TEST_URI_AUTHOR);
+
+ ({inspector} = yield openInspector());
+ yield selectNode("#target", inspector);
+ yield checkSheets("#target", testActor);
+
+ info("Checking stylesheets on XUL document");
+ info("Allowing XUL content");
+ allowXUL();
+ yield addTab(TEST_URI_XUL);
+
+ ({inspector} = yield openInspector());
+ yield selectNode("#target", inspector);
+
+ yield checkSheets("#target", testActor);
+ info("Disallowing XUL content");
+ disallowXUL();
+});
+
+function allowXUL() {
+ Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager)
+ .addFromPrincipal(XUL_PRINCIPAL, "allowXULXBL",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+}
+
+function disallowXUL() {
+ Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager)
+ .addFromPrincipal(XUL_PRINCIPAL, "allowXULXBL",
+ Ci.nsIPermissionManager.DENY_ACTION);
+}
+
+function* checkSheets(targetSelector, testActor) {
+ let sheets = yield testActor.getStyleSheetsInfoForNode(targetSelector);
+
+ for (let sheet of sheets) {
+ if (!sheet.href ||
+ /doc_content_stylesheet_/.test(sheet.href) ||
+ // For the "authored" case.
+ /^data:.*seagreen/.test(sheet.href)) {
+ ok(sheet.isContentSheet,
+ sheet.href + " identified as content stylesheet");
+ } else {
+ ok(!sheet.isContentSheet,
+ sheet.href + " identified as non-content stylesheet");
+ }
+ }
+}
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 000000000..f1f846f5d
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js
@@ -0,0 +1,341 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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("devtools/client/shared/output-parser");
+const {initCssProperties, getCssProperties} = require("devtools/shared/fronts/css-properties");
+
+const COLOR_CLASS = "color-class";
+const URL_CLASS = "url-class";
+const CUBIC_BEZIER_CLASS = "bezier-class";
+const ANGLE_CLASS = "angle-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: "-moz-binding",
+ value: "url(http://somesite.com/path/to/binding.xml#someid)",
+ test: fragment => {
+ is(countAll(fragment), 1);
+ is(countUrls(fragment), 1);
+ is(getUrl(fragment), "http://somesite.com/path/to/binding.xml#someid");
+ }
+ },
+ {
+ 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);
+ let 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: "-moz-radial-gradient(center 45deg, circle closest-side, " +
+ "orange 0%, red 100%)",
+ test: fragment => {
+ is(countAll(fragment), 6);
+ let colorSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
+ is(colorSwatches.length, 2);
+ is(colorSwatches[0].textContent, "orange");
+ is(colorSwatches[1].textContent, "red");
+ let angleSwatches = fragment.querySelectorAll("." + ANGLE_CLASS);
+ is(angleSwatches.length, 1);
+ is(angleSwatches[0].textContent, "45deg");
+ }
+ },
+ {
+ 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",
+ 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: "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)");
+ }
+ }
+];
+
+add_task(function* () {
+ // Mock the toolbox that initCssProperties expect so we get the fallback css properties.
+ let toolbox = {target: {client: {}, hasActor: () => false}};
+ yield initCssProperties(toolbox);
+ let cssProperties = getCssProperties(toolbox);
+
+ let parser = new OutputParser(document, cssProperties);
+ for (let i = 0; i < TEST_DATA.length; i++) {
+ let 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,
+ defaultColorType: false
+ }));
+ }
+});
+
+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 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;
+}
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 000000000..942fe05e2
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js
@@ -0,0 +1,43 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the style-inspector views only refresh 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#one", inspector);
+
+ is(getRuleViewPropertyValue(view, "element", "color"), "red",
+ "The rule-view shows the properties for test node one");
+
+ let cView = inspector.computedview.computedView;
+ let prop = getComputedViewProperty(cView, "color");
+ ok(!prop, "The computed-view doesn't show the properties for test node one");
+
+ info("Switching to the computed-view");
+ let onComputedViewReady = inspector.once("computed-view-refreshed");
+ selectComputedView(inspector);
+ yield onComputedViewReady;
+
+ ok(getComputedViewPropertyValue(cView, "color"), "#F00",
+ "The computed-view shows the properties for test node one");
+
+ info("Selecting test node two");
+ yield selectNode("#two", inspector);
+
+ ok(getComputedViewPropertyValue(cView, "color"), "#00F",
+ "The computed-view shows the properties for test node two");
+
+ is(getRuleViewPropertyValue(view, "element", "color"), "red",
+ "The rule-view doesn't the properties for test node two");
+});
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 000000000..bd467b800
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js
@@ -0,0 +1,125 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/warning-64.png) no-repeat left center;
+ padding-left: 70px;
+ }
+ </style>
+ <div class="test-element">test element</div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ info("Testing the background-image property on the body rule");
+ yield testBodyRuleView(view);
+
+ info("Selecting the test div node");
+ yield selectNode(".test-element", inspector);
+ info("Testing the the background property on the .test-element rule");
+ yield testDivRuleView(view);
+
+ info("Testing that image preview tooltips show even when there are " +
+ "fields being edited");
+ yield testTooltipAppearsEvenInEditMode(view);
+
+ info("Switching over to the computed-view");
+ let onComputedViewReady = inspector.once("computed-view-refreshed");
+ view = selectComputedView(inspector);
+ yield onComputedViewReady;
+
+ info("Testing that the background-image computed style has a tooltip too");
+ yield testComputedView(view);
+});
+
+function* testBodyRuleView(view) {
+ info("Testing tooltips in the rule view");
+ let panel = view.tooltips.previewTooltip.panel;
+
+ // Check that the rule view has a tooltip and that a XUL panel has
+ // been created
+ ok(view.tooltips.previewTooltip, "Tooltip instance exists");
+ ok(panel, "XUL panel exists");
+
+ // Get the background-image property inside the rule view
+ let {valueSpan} = getRuleViewProperty(view, "body", "background-image");
+ let uriSpan = valueSpan.querySelector(".theme-link");
+
+ yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
+
+ let images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ ok(images[0].getAttribute("src")
+ .indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1,
+ "The image URL seems fine");
+}
+
+function* testDivRuleView(view) {
+ let panel = view.tooltips.previewTooltip.panel;
+
+ // Get the background property inside the rule view
+ let {valueSpan} = getRuleViewProperty(view, ".test-element", "background");
+ let uriSpan = valueSpan.querySelector(".theme-link");
+
+ yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
+
+ 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");
+}
+
+function* testTooltipAppearsEvenInEditMode(view) {
+ info("Switching to edit mode in the rule view");
+ let editor = yield turnToEditMode(view);
+
+ info("Now trying to show the preview tooltip");
+ let {valueSpan} = getRuleViewProperty(view, ".test-element", "background");
+ let uriSpan = valueSpan.querySelector(".theme-link");
+ yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
+
+ is(view.styleDocument.activeElement, editor.input,
+ "Tooltip was shown in edit mode, and inplace-editor still focused");
+}
+
+function turnToEditMode(ruleView) {
+ let brace = ruleView.styleDocument.querySelector(".ruleview-ruleclose");
+ return focusEditableField(ruleView, brace);
+}
+
+function* testComputedView(view) {
+ let tooltip = view.tooltips.previewTooltip;
+ ok(tooltip, "The computed-view has a tooltip defined");
+
+ let panel = tooltip.panel;
+ ok(panel, "The computed-view tooltip has a XUL panel");
+
+ let {valueSpan} = getComputedViewProperty(view, "background-image");
+ let uriSpan = valueSpan.querySelector(".theme-link");
+
+ yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
+
+ 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 in the computed-view too");
+}
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 000000000..7f15d4fbe
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js
@@ -0,0 +1,73 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".one", inspector);
+
+ info("Testing rule view tooltip closes on new selection");
+ yield testRuleView(view, inspector);
+
+ info("Testing computed view tooltip closes on new selection");
+ view = selectComputedView(inspector);
+ yield testComputedView(view, inspector);
+});
+
+function* testRuleView(ruleView, inspector) {
+ info("Showing the tooltip");
+
+ let tooltip = ruleView.tooltips.previewTooltip;
+ let tooltipContent = ruleView.styleDocument.createElementNS(XHTML_NS, "div");
+ yield tooltip.setContent(tooltipContent, {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();
+
+ let onShown = tooltip.once("shown");
+ tooltip.show(ruleView.styleDocument.firstElementChild);
+ yield onShown;
+
+ info("Selecting a new node");
+ let onHidden = tooltip.once("hidden");
+ yield selectNode(".two", inspector);
+ yield onHidden;
+
+ ok(true, "Rule view tooltip closed after a new node got selected");
+}
+
+function* testComputedView(computedView, inspector) {
+ info("Showing the tooltip");
+
+ let tooltip = computedView.tooltips.previewTooltip;
+ let tooltipContent = computedView.styleDocument.createElementNS(XHTML_NS, "div");
+ yield tooltip.setContent(tooltipContent, {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();
+
+ let onShown = tooltip.once("shown");
+ tooltip.show(computedView.styleDocument.firstElementChild);
+ yield onShown;
+
+ info("Selecting a new node");
+ let onHidden = tooltip.once("hidden");
+ yield selectNode(".one", inspector);
+ yield 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 000000000..6bce367ae
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js
@@ -0,0 +1,120 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("#testElement", inspector);
+ yield testRuleView(view, inspector.selection.nodeFront);
+
+ info("Opening the computed view");
+ let onComputedViewReady = inspector.once("computed-view-refreshed");
+ view = selectComputedView(inspector);
+ yield onComputedViewReady;
+
+ yield testComputedView(view, inspector.selection.nodeFront);
+
+ yield testExpandedComputedViewProperty(view, inspector.selection.nodeFront);
+});
+
+function* testRuleView(ruleView, nodeFront) {
+ info("Testing font-family tooltips in the rule view");
+
+ let tooltip = ruleView.tooltips.previewTooltip;
+ let 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
+ let {valueSpan} = getRuleViewProperty(ruleView, "#testElement",
+ "font-family");
+
+ // And verify that the tooltip gets shown on this property
+ yield assertHoverTooltipOn(tooltip, 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 = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(images[0].getAttribute("src"), dataURL,
+ "Tooltip contains the correct data-uri image");
+}
+
+function* testComputedView(computedView, nodeFront) {
+ info("Testing font-family tooltips in the computed view");
+
+ let tooltip = computedView.tooltips.previewTooltip;
+ let panel = tooltip.panel;
+ let {valueSpan} = getComputedViewProperty(computedView, "font-family");
+
+ yield assertHoverTooltipOn(tooltip, 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 = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(images[0].getAttribute("src"), dataURL,
+ "Tooltip contains the correct data-uri image");
+}
+
+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");
+ let propertyView = getPropertyView(computedView, "font-family");
+ propertyView.matchedExpanded = true;
+ yield propertyView.refreshMatchedSelectors();
+
+ let valueSpan = propertyView.matchedSelectorsContainer
+ .querySelector(".bestmatch .other-property-value");
+
+ let tooltip = computedView.tooltips.previewTooltip;
+ let panel = tooltip.panel;
+
+ yield assertHoverTooltipOn(tooltip, 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 = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(images[0].getAttribute("src"), dataURL,
+ "Tooltip contains the correct data-uri image");
+}
+
+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 000000000..60d747a45
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js
@@ -0,0 +1,63 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector} = yield openInspector();
+
+ yield testRuleViewUrls(inspector);
+ yield testComputedViewUrls(inspector);
+});
+
+function* testRuleViewUrls(inspector) {
+ info("Testing tooltips in the rule view");
+ let view = selectRuleView(inspector);
+ yield selectNode("h1", inspector);
+
+ let {valueSpan} = getRuleViewProperty(view, "h1", "background");
+ yield performChecks(view, valueSpan);
+}
+
+function* testComputedViewUrls(inspector) {
+ info("Testing tooltips in the computed view");
+
+ let onComputedViewReady = inspector.once("computed-view-refreshed");
+ let view = selectComputedView(inspector);
+ yield onComputedViewReady;
+
+ let {valueSpan} = getComputedViewProperty(view, "background-image");
+
+ yield performChecks(view, valueSpan);
+}
+
+/**
+ * A helper that checks url() tooltips contain correct images
+ */
+function* performChecks(view, propertyValue) {
+ function checkTooltip(panel, imageSrc) {
+ let images = panel.getElementsByTagName("img");
+ is(images.length, 1, "Tooltip contains an image");
+ is(images[0].getAttribute("src"), imageSrc, "The image URL is correct");
+ }
+
+ let links = propertyValue.querySelectorAll(".theme-link");
+ let panel = view.tooltips.previewTooltip.panel;
+
+ info("Checking first link tooltip");
+ yield assertHoverTooltipOn(view.tooltips.previewTooltip, links[0]);
+ checkTooltip(panel, YELLOW_DOT);
+
+ info("Checking second link tooltip");
+ yield assertHoverTooltipOn(view.tooltips.previewTooltip, links[1]);
+ checkTooltip(panel, BLUE_DOT);
+}
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 000000000..bb851ec92
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js
@@ -0,0 +1,58 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ yield selectNode("#testElement", inspector);
+ yield testRuleView(view, inspector.selection.nodeFront);
+});
+
+function* testRuleView(ruleView, nodeFront) {
+ info("Testing font-family tooltips in the rule view");
+
+ let tooltip = ruleView.tooltips.previewTooltip;
+ let 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
+ let propertyList = ruleView.element
+ .querySelectorAll(".ruleview-propertylist");
+ let fontExpander = propertyList[1].querySelectorAll(".ruleview-expander")[0];
+ fontExpander.click();
+
+ let rule = getRuleViewRule(ruleView, "#testElement");
+ let valueSpan = rule
+ .querySelector(".ruleview-computed .ruleview-propertyvalue");
+
+ // And verify that the tooltip gets shown on this property
+ yield assertHoverTooltipOn(tooltip, 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 = yield getFontFamilyDataURL(valueSpan.textContent, nodeFront);
+ is(images[0].getAttribute("src"), dataURL,
+ "Tooltip contains the correct data-uri image");
+}
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 000000000..b231fe1b1
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js
@@ -0,0 +1,86 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/warning-64.png);
+ }
+ </style>
+ <div></div>
+`;
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode("div", inspector);
+ yield testImageDimension(view);
+ yield testPickerDimension(view);
+});
+
+function* testImageDimension(ruleView) {
+ info("Testing background-image tooltip dimensions");
+
+ let tooltip = ruleView.tooltips.previewTooltip;
+ let panel = tooltip.panel;
+ let {valueSpan} = getRuleViewProperty(ruleView, "div", "background");
+ let uriSpan = valueSpan.querySelector(".theme-link");
+
+ // Make sure there is a hover tooltip for this property, this also will fill
+ // the tooltip with its content
+ yield assertHoverTooltipOn(tooltip, uriSpan);
+
+ info("Showing the tooltip");
+ let onShown = tooltip.once("shown");
+ tooltip.show(uriSpan);
+ yield onShown;
+
+ // Let's not test for a specific size, but instead let's make sure it's at
+ // least as big as the image
+ let imageRect = panel.querySelector("img").getBoundingClientRect();
+ let panelRect = panel.getBoundingClientRect();
+
+ ok(panelRect.width >= imageRect.width,
+ "The panel is wide enough to show the image");
+ ok(panelRect.height >= imageRect.height,
+ "The panel is high enough to show the image");
+
+ let onHidden = tooltip.once("hidden");
+ tooltip.hide();
+ yield onHidden;
+}
+
+function* testPickerDimension(ruleView) {
+ info("Testing color-picker tooltip dimensions");
+
+ let {valueSpan} = getRuleViewProperty(ruleView, "div", "background");
+ let swatch = valueSpan.querySelector(".ruleview-colorswatch");
+ let cPicker = ruleView.tooltips.colorPicker;
+
+ let onReady = cPicker.once("ready");
+ swatch.click();
+ yield 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
+ let spectrumRect = cPicker.spectrum.element.getBoundingClientRect();
+ let panelRect = cPicker.tooltip.container.getBoundingClientRect();
+
+ ok(panelRect.width >= spectrumRect.width,
+ "The panel is wide enough to show the picker");
+ ok(panelRect.height >= spectrumRect.height,
+ "The panel is high enough to show the picker");
+
+ let onHidden = cPicker.tooltip.once("hidden");
+ let onRuleViewChanged = ruleView.once("ruleview-changed");
+ cPicker.hide();
+ yield onHidden;
+ yield 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 000000000..68a91ff95
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js
@@ -0,0 +1,48 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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
+
+const TEST_URI = `
+ <style type="text/css">
+ body {
+ transform: skew(16deg);
+ }
+ </style>
+ Test the css transform highlighter
+`;
+
+const TYPE = "CssTransformHighlighter";
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ let overlay = view.highlighters;
+
+ ok(!overlay.highlighters[TYPE], "No highlighter exists in the rule-view");
+ let h = yield 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");
+ let h2 = yield overlay._getHighlighter(TYPE);
+ is(h, h2,
+ "The same instance of highlighter is returned everytime in the rule-view");
+
+ let onComputedViewReady = inspector.once("computed-view-refreshed");
+ let cView = selectComputedView(inspector);
+ yield onComputedViewReady;
+ overlay = cView.highlighters;
+
+ ok(!overlay.highlighters[TYPE], "No highlighter exists in the computed-view");
+ h = yield overlay._getHighlighter(TYPE);
+ ok(overlay.highlighters[TYPE],
+ "The highlighter has been created in the computed-view");
+ is(h, overlay.highlighters[TYPE], "The right highlighter has been created");
+ h2 = yield overlay._getHighlighter(TYPE);
+ is(h, h2, "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 000000000..a44a31422
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js
@@ -0,0 +1,57 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield 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("highlighter-shown");
+ hs._onMouseMove({target: valueSpan});
+ yield onHighlighterShown;
+
+ let onComputedViewReady = inspector.once("computed-view-refreshed");
+ let cView = selectComputedView(inspector);
+ yield onComputedViewReady;
+ hs = cView.highlighters;
+
+ ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (1)");
+
+ 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 (2)");
+
+ info("Faking a mousemove on a transform property");
+ ({valueSpan} = getComputedViewProperty(cView, "transform"));
+ onHighlighterShown = hs.once("highlighter-shown");
+ hs._onMouseMove({target: valueSpan});
+ yield onHighlighterShown;
+});
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 000000000..1ecdf279e
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js
@@ -0,0 +1,103 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+
+ // Mock the highlighter front to get the reference of the NodeFront
+ let HighlighterFront = {
+ isShown: false,
+ nodeFront: null,
+ nbOfTimesShown: 0,
+ show: function (nodeFront) {
+ this.nodeFront = nodeFront;
+ this.isShown = true;
+ this.nbOfTimesShown ++;
+ return promise.resolve(true);
+ },
+ hide: function () {
+ this.nodeFront = null;
+ this.isShown = false;
+ return promise.resolve();
+ },
+ finalize: function () {}
+ };
+
+ // Inject the mock highlighter in the rule-view
+ let 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("highlighter-shown");
+ hs._onMouseMove({target: valueSpan});
+ yield onHighlighterShown;
+ ok(HighlighterFront.isShown, "The highlighter is shown");
+ let onHighlighterHidden = hs.once("highlighter-hidden");
+ hs._onMouseOut();
+ yield 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");
+ let nb = HighlighterFront.nbOfTimesShown;
+ onHighlighterShown = hs.once("highlighter-shown");
+ hs._onMouseMove({target: valueSpan});
+ yield 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");
+ yield selectNode("html", inspector);
+ ({valueSpan} = getRuleViewProperty(view, "html", "transform"));
+ onHighlighterShown = hs.once("highlighter-shown");
+ hs._onMouseMove({target: valueSpan});
+ yield onHighlighterShown;
+ is(HighlighterFront.nodeFront.tagName, "HTML",
+ "The right NodeFront is passed to the highlighter (1)");
+
+ yield selectNode("body", inspector);
+ ({valueSpan} = getRuleViewProperty(view, "body", "transform"));
+ onHighlighterShown = hs.once("highlighter-shown");
+ hs._onMouseMove({target: valueSpan});
+ yield 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("highlighter-hidden");
+ hs._onMouseMove({target: valueSpan});
+ yield 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 000000000..9d81e2649
--- /dev/null
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js
@@ -0,0 +1,60 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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(function* () {
+ yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+ let {inspector, view} = yield openRuleView();
+ yield selectNode(".test", inspector);
+
+ let 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");
+ let classRuleEditor = getRuleViewRuleEditor(view, 1);
+ let propEditor = classRuleEditor.rule.textProps[0].editor;
+ propEditor.enable.click();
+ yield 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"));
+ let onHighlighterShown = hs.once("highlighter-shown");
+ hs._onMouseMove({target: valueSpan});
+ yield onHighlighterShown;
+});
diff --git a/devtools/client/inspector/shared/test/doc_author-sheet.html b/devtools/client/inspector/shared/test/doc_author-sheet.html
new file mode 100644
index 000000000..d611bb387
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_author-sheet.html
@@ -0,0 +1,37 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <title>authored sheet test</title>
+ <style>
+ #target {
+ color: chartreuse;
+ }
+ </style>
+ <script>
+ "use strict";
+ var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+
+ var style = "data:text/css,div { background-color: seagreen; }";
+ var uri = gIOService.newURI(style, null, null);
+ var windowUtils = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ windowUtils.loadSheet(uri, windowUtils.AUTHOR_SHEET);
+ </script>
+</head>
+<body>
+ <div id="target"> the ocean </div>
+ <input type=text placeholder=test></input>
+ <input type=color></input>
+ <input type=range></input>
+ <input type=number></input>
+ <progress></progress>
+ <blockquote type=cite>
+ <pre _moz_quote=true>
+ inspect <a href="foo">user agent</a> styles
+ </pre>
+ </blockquote>
+</body>
+</html>
diff --git a/devtools/client/inspector/shared/test/doc_content_stylesheet.html b/devtools/client/inspector/shared/test/doc_content_stylesheet.html
new file mode 100644
index 000000000..f9b52f78d
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_stylesheet.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+ <title>test</title>
+ <link href="./doc_content_stylesheet_linked.css" rel="stylesheet" type="text/css">
+ <script>
+ /* exported loadCSS */
+ "use strict";
+ // Load script.css
+ function loadCSS() {
+ let link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.type = "text/css";
+ link.href = "./doc_content_stylesheet_script.css";
+ document.getElementsByTagName("head")[0].appendChild(link);
+ }
+ </script>
+ <style>
+ table {
+ border: 1px solid #000;
+ }
+ </style>
+</head>
+<body onload="loadCSS();">
+ <table id="target">
+ <tr>
+ <td>
+ <h3>Simple test</h3>
+ </td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/devtools/client/inspector/shared/test/doc_content_stylesheet.xul b/devtools/client/inspector/shared/test/doc_content_stylesheet.xul
new file mode 100644
index 000000000..efd53815d
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_stylesheet.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/xul.css" type="text/css"?>
+<?xml-stylesheet href="./doc_content_stylesheet_xul.css"
+ type="text/css"?>
+<!DOCTYPE window>
+<window id="testwindow" xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <label id="target" value="Simple XUL document" />
+</window> \ No newline at end of file
diff --git a/devtools/client/inspector/shared/test/doc_content_stylesheet_imported.css b/devtools/client/inspector/shared/test/doc_content_stylesheet_imported.css
new file mode 100644
index 000000000..ea1a3d986
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_stylesheet_imported.css
@@ -0,0 +1,5 @@
+@import url("./doc_content_stylesheet_imported2.css");
+
+#target {
+ text-decoration: underline;
+}
diff --git a/devtools/client/inspector/shared/test/doc_content_stylesheet_imported2.css b/devtools/client/inspector/shared/test/doc_content_stylesheet_imported2.css
new file mode 100644
index 000000000..77c73299e
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_stylesheet_imported2.css
@@ -0,0 +1,3 @@
+#target {
+ text-decoration: underline;
+}
diff --git a/devtools/client/inspector/shared/test/doc_content_stylesheet_linked.css b/devtools/client/inspector/shared/test/doc_content_stylesheet_linked.css
new file mode 100644
index 000000000..712ba78fb
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_stylesheet_linked.css
@@ -0,0 +1,3 @@
+table {
+ border-collapse: collapse;
+}
diff --git a/devtools/client/inspector/shared/test/doc_content_stylesheet_script.css b/devtools/client/inspector/shared/test/doc_content_stylesheet_script.css
new file mode 100644
index 000000000..5aa5e2c6c
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_stylesheet_script.css
@@ -0,0 +1,5 @@
+@import url("./doc_content_stylesheet_imported.css");
+
+table {
+ opacity: 1;
+}
diff --git a/devtools/client/inspector/shared/test/doc_content_stylesheet_xul.css b/devtools/client/inspector/shared/test/doc_content_stylesheet_xul.css
new file mode 100644
index 000000000..a14ae7f6f
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_content_stylesheet_xul.css
@@ -0,0 +1,3 @@
+#target {
+ font-size: 200px;
+}
diff --git a/devtools/client/inspector/shared/test/doc_frame_script.js b/devtools/client/inspector/shared/test/doc_frame_script.js
new file mode 100644
index 000000000..aeb73a115
--- /dev/null
+++ b/devtools/client/inspector/shared/test/doc_frame_script.js
@@ -0,0 +1,115 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* globals addMessageListener, sendAsyncMessage */
+
+"use strict";
+
+// A helper frame-script for brower/devtools/styleinspector tests.
+//
+// Most listeners in the script expect "Test:"-namespaced messages from chrome,
+// then execute code upon receiving, and immediately send back a message.
+// This is so that chrome test code can execute code in content and wait for a
+// response this way:
+// let response = yield executeInContent(browser, "Test:MsgName", data, true);
+// The response message should have the same name "Test:MsgName"
+//
+// Some listeners do not send a response message back.
+
+var {utils: Cu} = Components;
+var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var defer = require("devtools/shared/defer");
+
+/**
+ * Get a value for a given property name in a css rule in a stylesheet, given
+ * their indexes
+ * @param {Object} data Expects a data object with the following properties
+ * - {Number} styleSheetIndex
+ * - {Number} ruleIndex
+ * - {String} name
+ * @return {String} The value, if found, null otherwise
+ */
+addMessageListener("Test:GetRulePropertyValue", function (msg) {
+ let {name, styleSheetIndex, ruleIndex} = msg.data;
+ let value = null;
+
+ dumpn("Getting the value for property name " + name + " in sheet " +
+ styleSheetIndex + " and rule " + ruleIndex);
+
+ let sheet = content.document.styleSheets[styleSheetIndex];
+ if (sheet) {
+ let rule = sheet.cssRules[ruleIndex];
+ if (rule) {
+ value = rule.style.getPropertyValue(name);
+ }
+ }
+
+ sendAsyncMessage("Test:GetRulePropertyValue", value);
+});
+
+/**
+ * Get the property value from the computed style for an element.
+ * @param {Object} data Expects a data object with the following properties
+ * - {String} selector: The selector used to obtain the element.
+ * - {String} pseudo: pseudo id to query, or null.
+ * - {String} name: name of the property
+ * @return {String} The value, if found, null otherwise
+ */
+addMessageListener("Test:GetComputedStylePropertyValue", function (msg) {
+ let {selector, pseudo, name} = msg.data;
+ let doc = content.document;
+
+ let element = doc.querySelector(selector);
+ let value = content.getComputedStyle(element, pseudo).getPropertyValue(name);
+ sendAsyncMessage("Test:GetComputedStylePropertyValue", value);
+});
+
+/**
+ * Wait the property value from the computed style for an element and
+ * compare it with the expected value
+ * @param {Object} data Expects a data object with the following properties
+ * - {String} selector: The selector used to obtain the element.
+ * - {String} pseudo: pseudo id to query, or null.
+ * - {String} name: name of the property
+ * - {String} expected: the expected value for property
+ */
+addMessageListener("Test:WaitForComputedStylePropertyValue", function (msg) {
+ let {selector, pseudo, name, expected} = msg.data;
+ let element = content.document.querySelector(selector);
+ waitForSuccess(() => {
+ let value = content.document.defaultView.getComputedStyle(element, pseudo)
+ .getPropertyValue(name);
+
+ return value === expected;
+ }).then(() => {
+ sendAsyncMessage("Test:WaitForComputedStylePropertyValue");
+ });
+});
+
+var dumpn = msg => dump(msg + "\n");
+
+/**
+ * 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") {
+ let def = defer();
+
+ function wait(fn) {
+ if (fn()) {
+ def.resolve();
+ } else {
+ setTimeout(() => wait(fn), 200);
+ }
+ }
+ wait(validatorFn);
+
+ return def.promise;
+}
diff --git a/devtools/client/inspector/shared/test/head.js b/devtools/client/inspector/shared/test/head.js
new file mode 100644
index 000000000..bcc2ec2c7
--- /dev/null
+++ b/devtools/client/inspector/shared/test/head.js
@@ -0,0 +1,557 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../test/head.js */
+"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("devtools/client/inspector/rules/rules");
+var {getInplaceEditorForSpan: inplaceEditor} =
+ require("devtools/client/shared/inplace-editor");
+const {getColor: getThemeColor} = require("devtools/client/shared/theme");
+
+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 FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
+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(function*() {
+ * yield addTab(TEST_URI);
+ * let {toolbox, inspector} = yield openInspector();
+ * inspector.sidebar.select(viewId);
+ * let view = inspector[viewId].view;
+ * yield selectNode("#test", inspector);
+ * yield someAsyncTestFunction(view);
+ * });
+ *
+ * add_task is the way to define the testcase in the test file. It accepts
+ * a single generator-function argument.
+ * The generator function should yield any async call.
+ *
+ * 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 add_task, 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, ...
+ */
+
+/**
+ * The rule-view tests rely on a frame-script to be injected in the content test
+ * page. So override the shared-head's addTab to load the frame script after the
+ * tab was added.
+ * FIXME: Refactor the rule-view tests to use the testActor instead of a frame
+ * script, so they can run on remote targets too.
+ */
+var _addTab = addTab;
+addTab = function (url) {
+ return _addTab(url).then(tab => {
+ info("Loading the helper frame script " + FRAME_SCRIPT_URL);
+ let browser = tab.linkedBrowser;
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+ return tab;
+ });
+};
+
+/**
+ * Wait for a content -> chrome message on the message manager (the window
+ * messagemanager is used).
+ *
+ * @param {String} name
+ * The message name
+ * @return {Promise} A promise that resolves to the response data when the
+ * message has been received
+ */
+function waitForContentMessage(name) {
+ info("Expecting message " + name + " from content");
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ let def = defer();
+ mm.addMessageListener(name, function onMessage(msg) {
+ mm.removeMessageListener(name, onMessage);
+ def.resolve(msg.data);
+ });
+ return def.promise;
+}
+
+/**
+ * Send an async message to the frame script (chrome -> content) and wait for a
+ * response message with the same name (content -> chrome).
+ *
+ * @param {String} name
+ * The message name. Should be one of the messages defined
+ * in doc_frame_script.js
+ * @param {Object} data
+ * Optional data to send along
+ * @param {Object} objects
+ * Optional CPOW objects to send along
+ * @param {Boolean} expectResponse
+ * If set to false, don't wait for a response with the same name
+ * from the content script. Defaults to true.
+ * @return {Promise} Resolves to the response data if a response is expected,
+ * immediately resolves otherwise
+ */
+function executeInContent(name, data = {}, objects = {},
+ expectResponse = true) {
+ info("Sending message " + name + " to content");
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ mm.sendAsyncMessage(name, data, objects);
+ if (expectResponse) {
+ return waitForContentMessage(name);
+ }
+
+ return promise.resolve();
+}
+
+/**
+ * Send an async message to the frame script and get back the requested
+ * computed style property.
+ *
+ * @param {String} selector
+ * The selector used to obtain the element.
+ * @param {String} pseudo
+ * pseudo id to query, or null.
+ * @param {String} name
+ * name of the property.
+ */
+function* getComputedStyleProperty(selector, pseudo, propName) {
+ return yield executeInContent("Test:GetComputedStylePropertyValue",
+ {selector,
+ pseudo,
+ name: propName});
+}
+
+/**
+ * Send an async message to the frame script and wait until the requested
+ * computed style property has the expected value.
+ *
+ * @param {String} selector
+ * The selector used to obtain the element.
+ * @param {String} pseudo
+ * pseudo id to query, or null.
+ * @param {String} prop
+ * name of the property.
+ * @param {String} expected
+ * expected value of property
+ * @param {String} name
+ * the name used in test message
+ */
+function* waitForComputedStyleProperty(selector, pseudo, name, expected) {
+ return yield executeInContent("Test:WaitForComputedStylePropertyValue",
+ {selector,
+ pseudo,
+ expected,
+ name});
+}
+
+/**
+ * Given an inplace editable element, click to switch it to edit mode, wait for
+ * focus
+ *
+ * @return a promise that resolves to the inplace-editor element when ready
+ */
+var focusEditableField = Task.async(function* (ruleView, editable, xOffset = 1,
+ yOffset = 1, options = {}) {
+ let onFocus = once(editable.parentNode, "focus", true);
+ info("Clicking on editable field to turn to edit mode");
+ EventUtils.synthesizeMouse(editable, xOffset, yOffset, options,
+ editable.ownerDocument.defaultView);
+ yield onFocus;
+
+ info("Editable field gained focus, returning the input field now");
+ let onEdit = inplaceEditor(editable.ownerDocument.activeElement);
+
+ return onEdit;
+});
+
+/**
+ * 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") {
+ let def = defer();
+
+ function wait(validator) {
+ if (validator()) {
+ ok(true, "Validator function " + name + " returned true");
+ def.resolve();
+ } else {
+ setTimeout(() => wait(validator), 200);
+ }
+ }
+ wait(validatorFn);
+
+ return def.promise;
+}
+
+/**
+ * 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 = Task.async(function* (font, nodeFront) {
+ let fillStyle = getThemeColor("body-color");
+
+ let {data} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
+ let dataURL = yield data.string();
+ return dataURL;
+});
+
+/* *********************************************
+ * RULE-VIEW
+ * *********************************************
+ * Rule-view related test utility functions
+ * This object contains functions to get rules, get properties, ...
+ */
+
+/**
+ * Get the DOMNode for a css rule in the rule-view that corresponds to the given
+ * selector
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view for which the rule
+ * object is wanted
+ * @return {DOMNode}
+ */
+function getRuleViewRule(view, selectorText) {
+ let rule;
+ for (let r of view.styleDocument.querySelectorAll(".ruleview-rule")) {
+ let selector = r.querySelector(".ruleview-selectorcontainer, " +
+ ".ruleview-selector-matched");
+ if (selector && selector.textContent === selectorText) {
+ rule = r;
+ break;
+ }
+ }
+
+ return rule;
+}
+
+/**
+ * Get references to the name and value span nodes corresponding to a given
+ * selector and property name in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for the property in
+ * @param {String} propertyName
+ * The name of the property
+ * @return {Object} An object like {nameSpan: DOMNode, valueSpan: DOMNode}
+ */
+function getRuleViewProperty(view, selectorText, propertyName) {
+ let prop;
+
+ let rule = getRuleViewRule(view, selectorText);
+ if (rule) {
+ // Look for the propertyName in that rule element
+ for (let p of rule.querySelectorAll(".ruleview-property")) {
+ let nameSpan = p.querySelector(".ruleview-propertyname");
+ let valueSpan = p.querySelector(".ruleview-propertyvalue");
+
+ if (nameSpan.textContent === propertyName) {
+ prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+ break;
+ }
+ }
+ }
+ return prop;
+}
+
+/**
+ * Get the text value of the property corresponding to a given selector and name
+ * in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for the property in
+ * @param {String} propertyName
+ * The name of the property
+ * @return {String} The property value
+ */
+function getRuleViewPropertyValue(view, selectorText, propertyName) {
+ return getRuleViewProperty(view, selectorText, propertyName)
+ .valueSpan.textContent;
+}
+
+/**
+ * Get a reference to the selector DOM element corresponding to a given selector
+ * in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for
+ * @return {DOMNode} The selector DOM element
+ */
+function getRuleViewSelector(view, selectorText) {
+ let rule = getRuleViewRule(view, selectorText);
+ return rule.querySelector(".ruleview-selector, .ruleview-selector-matched");
+}
+
+/**
+ * Get a reference to the selectorhighlighter icon DOM element corresponding to
+ * a given selector in the rule-view
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} selectorText
+ * The selector in the rule-view to look for
+ * @return {DOMNode} The selectorhighlighter icon DOM element
+ */
+function getRuleViewSelectorHighlighterIcon(view, selectorText) {
+ let rule = getRuleViewRule(view, selectorText);
+ return rule.querySelector(".ruleview-selectorhighlighter");
+}
+
+/**
+ * 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 = Task.async(function* (ruleView, colorPicker,
+ newRgba, expectedChange) {
+ let onRuleViewChanged = ruleView.once("ruleview-changed");
+ info("Getting the spectrum colorpicker object");
+ let spectrum = yield 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");
+ yield onRuleViewChanged;
+
+ if (expectedChange) {
+ info("Waiting for the style to be applied on the page");
+ yield waitForSuccess(() => {
+ let {element, name, value} = expectedChange;
+ return content.getComputedStyle(element)[name] === value;
+ }, "Color picker change applied on the page");
+ }
+});
+
+/**
+ * Get a rule-link from the rule-view given its index
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {Number} index
+ * The index of the link to get
+ * @return {DOMNode} The link if any at this index
+ */
+function getRuleViewLinkByIndex(view, index) {
+ let links = view.styleDocument.querySelectorAll(".ruleview-rule-source");
+ return links[index];
+}
+
+/**
+ * Get rule-link text from the rule-view given its index
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {Number} index
+ * The index of the link to get
+ * @return {String} The string at this index
+ */
+function getRuleViewLinkTextByIndex(view, index) {
+ let link = getRuleViewLinkByIndex(view, index);
+ return link.querySelector(".ruleview-rule-source-label").textContent;
+}
+
+/**
+ * Click on a rule-view's close brace to focus a new property name editor
+ *
+ * @param {RuleEditor} ruleEditor
+ * An instance of RuleEditor that will receive the new property
+ * @return a promise that resolves to the newly created editor when ready and
+ * focused
+ */
+var focusNewRuleViewProperty = Task.async(function* (ruleEditor) {
+ info("Clicking on a close ruleEditor brace to start editing a new property");
+ ruleEditor.closeBrace.scrollIntoView();
+ let editor = yield focusEditableField(ruleEditor.ruleView,
+ ruleEditor.closeBrace);
+
+ is(inplaceEditor(ruleEditor.newPropSpan), editor,
+ "Focused editor is the new property editor.");
+
+ return editor;
+});
+
+/**
+ * Create a new property name in the rule-view, focusing a new property editor
+ * by clicking on the close brace, and then entering the given text.
+ * Keep in mind that the rule-view knows how to handle strings with multiple
+ * properties, so the input text may be like: "p1:v1;p2:v2;p3:v3".
+ *
+ * @param {RuleEditor} ruleEditor
+ * The instance of RuleEditor that will receive the new property(ies)
+ * @param {String} inputValue
+ * The text to be entered in the new property name field
+ * @return a promise that resolves when the new property name has been entered
+ * and once the value field is focused
+ */
+var createNewRuleViewProperty = Task.async(function* (ruleEditor, inputValue) {
+ info("Creating a new property editor");
+ let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+ info("Entering the value " + inputValue);
+ editor.input.value = inputValue;
+
+ info("Submitting the new value and waiting for value field focus");
+ let onFocus = once(ruleEditor.element, "focus", true);
+ EventUtils.synthesizeKey("VK_RETURN", {},
+ ruleEditor.element.ownerDocument.defaultView);
+ yield onFocus;
+});
+
+/**
+ * Set the search value for the rule-view filter styles search box.
+ *
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @param {String} searchValue
+ * The filter search value
+ * @return a promise that resolves when the rule-view is filtered for the
+ * search term
+ */
+var setSearchFilter = Task.async(function* (view, searchValue) {
+ info("Setting filter text to \"" + searchValue + "\"");
+ let win = view.styleWindow;
+ let searchField = view.searchField;
+ searchField.focus();
+ synthesizeKeys(searchValue, win);
+ yield view.inspector.once("ruleview-filtered");
+});
+
+/* *********************************************
+ * 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 (let property of view.styleDocument.querySelectorAll(".property-view")) {
+ let nameSpan = property.querySelector(".property-name");
+ let valueSpan = property.querySelector(".property-value");
+
+ if (nameSpan.textContent === name) {
+ prop = {nameSpan: nameSpan, valueSpan: 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;
+}
+
+/**
+ * Open the style editor context menu and return all of it's items in a flat array
+ * @param {CssRuleView} view
+ * The instance of the rule-view panel
+ * @return An array of MenuItems
+ */
+function openStyleContextMenuAndGetAllItems(view, target) {
+ let menu = view._contextmenu._openMenu({target: target});
+
+ // Flatten all menu items into a single array to make searching through it easier
+ let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
+ if (item.submenu) {
+ return addItem(item.submenu.items);
+ }
+ return item;
+ }));
+
+ return allItems;
+}