diff options
Diffstat (limited to 'devtools/client/inspector/shared/test')
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; +} |