From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- devtools/client/inspector/shared/test/browser.toml | 44 +++ ...er_styleinspector_context-menu-copy-color_01.js | 85 +++++ ...er_styleinspector_context-menu-copy-color_02.js | 115 +++++++ ...rowser_styleinspector_context-menu-copy-urls.js | 160 +++++++++ .../test/browser_styleinspector_output-parser.js | 381 +++++++++++++++++++++ .../browser_styleinspector_refresh_when_active.js | 50 +++ ...er_styleinspector_refresh_when_style_changes.js | 99 ++++++ ...wser_styleinspector_tooltip-background-image.js | 150 ++++++++ ...yleinspector_tooltip-closes-on-new-selection.js | 80 +++++ ...r_styleinspector_tooltip-longhand-fontfamily.js | 178 ++++++++++ ...inspector_tooltip-multiple-background-images.js | 68 ++++ ..._styleinspector_tooltip-shorthand-fontfamily.js | 73 ++++ .../test/browser_styleinspector_tooltip-size.js | 90 +++++ ...wser_styleinspector_transform-highlighter-01.js | 53 +++ ...wser_styleinspector_transform-highlighter-02.js | 63 ++++ ...wser_styleinspector_transform-highlighter-03.js | 115 +++++++ ...wser_styleinspector_transform-highlighter-04.js | 63 ++++ .../shared/test/doc_content_style_changes.html | 28 ++ devtools/client/inspector/shared/test/head.js | 218 ++++++++++++ 19 files changed, 2113 insertions(+) create mode 100644 devtools/client/inspector/shared/test/browser.toml create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js create mode 100644 devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js create mode 100644 devtools/client/inspector/shared/test/doc_content_style_changes.html create mode 100644 devtools/client/inspector/shared/test/head.js (limited to 'devtools/client/inspector/shared/test') diff --git a/devtools/client/inspector/shared/test/browser.toml b/devtools/client/inspector/shared/test/browser.toml new file mode 100644 index 0000000000..3cb858d97f --- /dev/null +++ b/devtools/client/inspector/shared/test/browser.toml @@ -0,0 +1,44 @@ +[DEFAULT] +tags = "devtools" +subsuite = "devtools" +support-files = [ + "doc_content_style_changes.html", + "head.js", + "!/devtools/client/inspector/test/head.js", + "!/devtools/client/inspector/test/shared-head.js", + "!/devtools/client/shared/test/shared-head.js", + "!/devtools/client/shared/test/telemetry-test-helpers.js", + "!/devtools/client/shared/test/highlighter-test-actor.js", +] + +["browser_styleinspector_context-menu-copy-color_01.js"] + +["browser_styleinspector_context-menu-copy-color_02.js"] + +["browser_styleinspector_context-menu-copy-urls.js"] + +["browser_styleinspector_output-parser.js"] + +["browser_styleinspector_refresh_when_active.js"] + +["browser_styleinspector_refresh_when_style_changes.js"] + +["browser_styleinspector_tooltip-background-image.js"] + +["browser_styleinspector_tooltip-closes-on-new-selection.js"] + +["browser_styleinspector_tooltip-longhand-fontfamily.js"] + +["browser_styleinspector_tooltip-multiple-background-images.js"] + +["browser_styleinspector_tooltip-shorthand-fontfamily.js"] + +["browser_styleinspector_tooltip-size.js"] + +["browser_styleinspector_transform-highlighter-01.js"] + +["browser_styleinspector_transform-highlighter-02.js"] + +["browser_styleinspector_transform-highlighter-03.js"] + +["browser_styleinspector_transform-highlighter-04.js"] diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js new file mode 100644 index 0000000000..c1809dd543 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test "Copy color" item of the context menu #1: Test _isColorPopup. + +const TEST_URI = ` +
+ Test "Copy color" context menu option +
+`; + +add_task(async function () { + // Test is slow on Linux EC2 instances - Bug 1137765 + requestLongerTimeout(2); + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector } = await openInspector(); + await testView("ruleview", inspector); + await testView("computedview", inspector); +}); + +async function testView(viewId, inspector) { + info("Testing " + viewId); + + await inspector.sidebar.select(viewId); + const view = + inspector.getPanel(viewId).view || inspector.getPanel(viewId).computedView; + await selectNode("div", inspector); + + testIsColorValueNode(view); + await clearCurrentNodeSelection(inspector); +} + +/** + * A function testing that isColorValueNode correctly detects nodes part of + * color values. + */ +function testIsColorValueNode(view) { + info("Testing that child nodes of color nodes are detected."); + const root = rootElement(view); + const colorNode = root.querySelector("span[data-color]"); + + ok(colorNode, "Color node found"); + for (const node of iterateNodes(colorNode)) { + ok(isColorValueNode(node), "Node is part of color value."); + } +} + +/** + * Check if a node is part of color value i.e. it has parent with a 'data-color' + * attribute. + */ +function isColorValueNode(node) { + let container = node.nodeType == node.TEXT_NODE ? node.parentElement : node; + + const isColorNode = el => el.dataset && "color" in el.dataset; + + while (!isColorNode(container)) { + container = container.parentNode; + if (!container) { + info("No color. Node is not part of color value."); + return false; + } + } + + info("Found a color. Node is part of color value."); + + return true; +} + +/** + * A generator that iterates recursively trough all child nodes of baseNode. + */ +function* iterateNodes(baseNode) { + yield baseNode; + + for (const child of baseNode.childNodes) { + yield* iterateNodes(child); + } +} + +/** + * Returns the root element for the given view, rule or computed. + */ +var rootElement = view => (view.element ? view.element : view.styleDocument); diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js new file mode 100644 index 0000000000..546f3520d0 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_02.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test "Copy color" item of the context menu #2: Test that correct color is +// copied if the color changes. + +const TEST_URI = ` + +
Testing the color picker tooltip!
+`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + + const { inspector, view } = await openRuleView(); + + await testCopyToClipboard(inspector, view); + await testManualEdit(inspector, view); + await testColorPickerEdit(inspector, view); +}); + +async function testCopyToClipboard(inspector, view) { + info("Testing that color is copied to clipboard"); + + await selectNode("div", inspector); + + const element = getRuleViewProperty( + view, + "div", + "color" + ).valueSpan.querySelector(".ruleview-colorswatch"); + + const menu = view.contextMenu._openMenu({ target: element }); + const allMenuItems = buildContextMenuItems(menu); + const menuitemCopyColor = allMenuItems.find( + item => + item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyColor") + ); + + ok(menuitemCopyColor.visible, "Copy color is visible"); + + await waitForClipboardPromise(() => menuitemCopyColor.click(), "#123ABC"); + ok(true, "expected color was copied to clipboard"); + + // close context menu + const onContextMenuClose = menu.once("close"); + menu.hide(element.ownerDocument); + await onContextMenuClose; +} + +async function testManualEdit(inspector, view) { + info("Testing manually edited colors"); + await selectNode("div", inspector); + + const colorTextProp = getTextProperty(view, 1, { color: "#123ABC" }); + const newColor = "#C9184E"; + await setProperty(view, colorTextProp, newColor); + + const colorValueElement = await waitFor(() => { + const el = getRuleViewProperty(view, "div", "color").valueSpan.firstChild; + if (el?.dataset?.color !== newColor) { + return false; + } + return el; + }); + + ok(!!colorValueElement, "data-color was updated"); + + const contextMenu = view.contextMenu; + contextMenu.currentTarget = colorValueElement; + contextMenu._isColorPopup(); + + is(contextMenu._colorToCopy, newColor, "_colorToCopy has the new value"); +} + +async function testColorPickerEdit(inspector, view) { + info("Testing colors edited via color picker"); + await selectNode("div", inspector); + + const swatchElement = getRuleViewProperty( + view, + "div", + "color" + ).valueSpan.querySelector(".ruleview-colorswatch"); + + info("Opening the color picker"); + const picker = view.tooltips.getTooltip("colorPicker"); + const onColorPickerReady = picker.once("ready"); + swatchElement.click(); + await onColorPickerReady; + + const newColor = "#53B759"; + const { colorUtils } = require("resource://devtools/shared/css/color.js"); + + const { r, g, b, a } = new colorUtils.CssColor(newColor).getRGBATuple(); + await simulateColorPickerChange(view, picker, [r, g, b, a]); + + is( + swatchElement.parentNode.dataset.color, + newColor, + "data-color was updated" + ); + + const contextMenu = view.contextMenu; + contextMenu.currentTarget = swatchElement; + contextMenu._isColorPopup(); + + is(contextMenu._colorToCopy, newColor, "_colorToCopy has the new value"); +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js new file mode 100644 index 0000000000..69042cc747 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-urls.js @@ -0,0 +1,160 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* Tests both Copy URL and Copy Data URL context menu items */ + +const TEST_DATA_URI = + "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="; + +// Invalid URL still needs to be reachable otherwise getImageDataUrl will +// timeout. DevTools chrome:// URLs aren't content accessible, so use some +// random resource:// URL here. +const INVALID_IMAGE_URI = "resource://devtools/client/definitions.js"; +const ERROR_MESSAGE = STYLE_INSPECTOR_L10N.getStr( + "styleinspector.copyImageDataUrlError" +); + +add_task(async function () { + const TEST_URI = ` +
Valid background image
+
Invalid background image
`; + + await addTab("data:text/html;charset=utf8," + encodeURIComponent(TEST_URI)); + + await startTest(); +}); + +async function startTest() { + info("Opening rule view"); + let { inspector, view } = await openRuleView(); + + info("Test valid background image URL in rule view"); + await testCopyUrlToClipboard( + { view, inspector }, + "data-uri", + ".valid-background", + TEST_DATA_URI + ); + await testCopyUrlToClipboard( + { view, inspector }, + "url", + ".valid-background", + TEST_DATA_URI + ); + + info("Test invalid background image URL in rue view"); + await testCopyUrlToClipboard( + { view, inspector }, + "data-uri", + ".invalid-background", + ERROR_MESSAGE + ); + await testCopyUrlToClipboard( + { view, inspector }, + "url", + ".invalid-background", + INVALID_IMAGE_URI + ); + + info("Opening computed view"); + view = selectComputedView(inspector); + + info("Test valid background image URL in computed view"); + await testCopyUrlToClipboard( + { view, inspector }, + "data-uri", + ".valid-background", + TEST_DATA_URI + ); + await testCopyUrlToClipboard( + { view, inspector }, + "url", + ".valid-background", + TEST_DATA_URI + ); + + info("Test invalid background image URL in computed view"); + await testCopyUrlToClipboard( + { view, inspector }, + "data-uri", + ".invalid-background", + ERROR_MESSAGE + ); + await testCopyUrlToClipboard( + { view, inspector }, + "url", + ".invalid-background", + INVALID_IMAGE_URI + ); +} + +async function testCopyUrlToClipboard( + { view, inspector }, + type, + selector, + expected +) { + info("Select node in inspector panel"); + await selectNode(selector, inspector); + + info( + "Retrieve background-image link for selected node in current " + + "styleinspector view" + ); + const property = await getBackgroundImageProperty(view, selector); + const imageLink = property.valueSpan.querySelector(".theme-link"); + ok(imageLink, "Background-image link element found"); + + info("Simulate right click on the background-image URL"); + const allMenuItems = openStyleContextMenuAndGetAllItems(view, imageLink); + const menuitemCopyUrl = allMenuItems.find( + item => + item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyUrl") + ); + const menuitemCopyImageDataUrl = allMenuItems.find( + item => + item.label === + STYLE_INSPECTOR_L10N.getStr("styleinspector.contextmenu.copyImageDataUrl") + ); + + info("Context menu is displayed"); + ok(menuitemCopyUrl.visible, '"Copy URL" menu entry is displayed'); + ok( + menuitemCopyImageDataUrl.visible, + '"Copy Image Data-URL" menu entry is displayed' + ); + + if (type == "data-uri") { + info("Click Copy Data URI and wait for clipboard"); + await waitForClipboardPromise(() => { + return menuitemCopyImageDataUrl.click(); + }, expected); + } else { + info("Click Copy URL and wait for clipboard"); + await waitForClipboardPromise(() => { + return menuitemCopyUrl.click(); + }, expected); + } + + info("Hide context menu"); +} + +async function getBackgroundImageProperty(view, selector) { + const isRuleView = view instanceof CssRuleView; + if (isRuleView) { + return getRuleViewProperty(view, selector, "background-image", { + wait: true, + }); + } + return getComputedViewProperty(view, "background-image"); +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js b/devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js new file mode 100644 index 0000000000..1aa2879ee2 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_output-parser.js @@ -0,0 +1,381 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test expected outputs of the output-parser's parseCssProperty function. + +// This is more of a unit test than a mochitest-browser test, but can't be +// tested with an xpcshell test as the output-parser requires the DOM to work. + +const OutputParser = require("resource://devtools/client/shared/output-parser.js"); + +const COLOR_CLASS = "color-class"; +const URL_CLASS = "url-class"; +const CUBIC_BEZIER_CLASS = "bezier-class"; +const ANGLE_CLASS = "angle-class"; +const LINEAR_EASING_CLASS = "linear-easing-class"; + +const TEST_DATA = [ + { + name: "width", + value: "100%", + test: fragment => { + is(countAll(fragment), 0); + is(fragment.textContent, "100%"); + }, + }, + { + name: "width", + value: "blue", + test: fragment => { + is(countAll(fragment), 0); + }, + }, + { + name: "content", + value: "'red url(test.png) repeat top left'", + test: fragment => { + is(countAll(fragment), 0); + }, + }, + { + name: "content", + value: '"blue"', + test: fragment => { + is(countAll(fragment), 0); + }, + }, + { + name: "margin-left", + value: "url(something.jpg)", + test: fragment => { + is(countAll(fragment), 0); + }, + }, + { + name: "background-color", + value: "transparent", + test: fragment => { + is(countAll(fragment), 2); + is(countColors(fragment), 1); + is(fragment.textContent, "transparent"); + }, + }, + { + name: "color", + value: "red", + test: fragment => { + is(countColors(fragment), 1); + is(fragment.textContent, "red"); + }, + }, + { + name: "color", + value: "#F06", + test: fragment => { + is(countColors(fragment), 1); + is(fragment.textContent, "#F06"); + }, + }, + { + name: "border", + value: "80em dotted pink", + test: fragment => { + is(countAll(fragment), 2); + is(countColors(fragment), 1); + is(getColor(fragment), "pink"); + }, + }, + { + name: "color", + value: "red !important", + test: fragment => { + is(countColors(fragment), 1); + is(fragment.textContent, "red !important"); + }, + }, + { + name: "background", + value: "red url(test.png) repeat top left", + test: fragment => { + is(countColors(fragment), 1); + is(countUrls(fragment), 1); + is(getColor(fragment), "red"); + is(getUrl(fragment), "test.png"); + is(countAll(fragment), 3); + }, + }, + { + name: "background", + value: "blue url(test.png) repeat top left !important", + test: fragment => { + is(countColors(fragment), 1); + is(countUrls(fragment), 1); + is(getColor(fragment), "blue"); + is(getUrl(fragment), "test.png"); + is(countAll(fragment), 3); + }, + }, + { + name: "list-style-image", + value: 'url("images/arrow.gif")', + test: fragment => { + is(countAll(fragment), 1); + is(getUrl(fragment), "images/arrow.gif"); + }, + }, + { + name: "list-style-image", + value: 'url("images/arrow.gif")!important', + test: fragment => { + is(countAll(fragment), 1); + is(getUrl(fragment), "images/arrow.gif"); + is(fragment.textContent, 'url("images/arrow.gif")!important'); + }, + }, + { + name: "background", + value: + "linear-gradient(to right, rgba(183,222,237,1) 0%, " + + "rgba(33,180,226,1) 30%, rgba(31,170,217,.5) 44%, " + + "#F06 75%, red 100%)", + test: fragment => { + is(countAll(fragment), 10); + const allSwatches = fragment.querySelectorAll("." + COLOR_CLASS); + is(allSwatches.length, 5); + is(allSwatches[0].textContent, "rgba(183,222,237,1)"); + is(allSwatches[1].textContent, "rgba(33,180,226,1)"); + is(allSwatches[2].textContent, "rgba(31,170,217,.5)"); + is(allSwatches[3].textContent, "#F06"); + is(allSwatches[4].textContent, "red"); + }, + }, + { + name: "background", + value: + "radial-gradient(circle closest-side at center, orange 0%, red 100%)", + test: fragment => { + is(countAll(fragment), 4); + const colorSwatches = fragment.querySelectorAll("." + COLOR_CLASS); + is(colorSwatches.length, 2); + is(colorSwatches[0].textContent, "orange"); + is(colorSwatches[1].textContent, "red"); + }, + }, + { + name: "background", + value: "white url(http://test.com/wow_such_image.png) no-repeat top left", + test: fragment => { + is(countAll(fragment), 3); + is(countUrls(fragment), 1); + is(countColors(fragment), 1); + }, + }, + { + name: "background", + value: + 'url("http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t")', + test: fragment => { + is(countAll(fragment), 1); + is( + getUrl(fragment), + "http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t" + ); + }, + }, + { + name: "background-image", + value: "url(this-is-an-incredible-image.jpeg)", + test: fragment => { + is(countAll(fragment), 1); + is(getUrl(fragment), "this-is-an-incredible-image.jpeg"); + }, + }, + { + name: "background", + value: + 'red url( "http://wow.com/cool/../../../you\'re(doingit)wrong" ) repeat center', + test: fragment => { + is(countAll(fragment), 3); + is(countColors(fragment), 1); + is(getUrl(fragment), "http://wow.com/cool/../../../you're(doingit)wrong"); + }, + }, + { + name: "background-image", + value: + "url(../../../look/at/this/folder/structure/../" + + "../red.blue.green.svg )", + test: fragment => { + is(countAll(fragment), 1); + is( + getUrl(fragment), + "../../../look/at/this/folder/structure/../" + "../red.blue.green.svg" + ); + }, + }, + { + name: "transition-timing-function", + value: "linear", + test: fragment => { + is(countCubicBeziers(fragment), 1); + is(getCubicBezier(fragment), "linear"); + }, + }, + { + name: "animation-timing-function", + value: "ease-in-out", + test: fragment => { + is(countCubicBeziers(fragment), 1); + is(getCubicBezier(fragment), "ease-in-out"); + }, + }, + { + name: "animation-timing-function", + value: "cubic-bezier(.1, 0.55, .9, -3.45)", + test: fragment => { + is(countCubicBeziers(fragment), 1); + is(getCubicBezier(fragment), "cubic-bezier(.1, 0.55, .9, -3.45)"); + }, + }, + { + name: "animation-timing-function", + value: "CUBIC-BEZIER(.1, 0.55, .9, -3.45)", + test: fragment => { + is(countCubicBeziers(fragment), 1); + is(getCubicBezier(fragment), "CUBIC-BEZIER(.1, 0.55, .9, -3.45)"); + }, + }, + { + name: "animation", + value: "move 3s cubic-bezier(.1, 0.55, .9, -3.45)", + test: fragment => { + is(countCubicBeziers(fragment), 1); + is(getCubicBezier(fragment), "cubic-bezier(.1, 0.55, .9, -3.45)"); + }, + }, + { + name: "transition", + value: "top 1s ease-in", + test: fragment => { + is(countCubicBeziers(fragment), 1); + is(getCubicBezier(fragment), "ease-in"); + }, + }, + { + name: "animation-timing-function", + value: "linear(0, 1 50% 100%)", + test: fragment => { + is(countLinears(fragment), 1); + is(getLinear(fragment), "linear(0, 1 50% 100%)"); + }, + }, + { + name: "animation-timing-function", + value: "LINEAR(0, 1 50% 100%)", + test: fragment => { + is(countLinears(fragment), 1); + is(getLinear(fragment), "LINEAR(0, 1 50% 100%)"); + }, + }, + { + name: "transition", + value: "top 3s steps(4, end)", + test: fragment => { + is(countAll(fragment), 0); + }, + }, + { + name: "transition", + value: "top 3s step-start", + test: fragment => { + is(countAll(fragment), 0); + }, + }, + { + name: "transition", + value: "top 3s step-end", + test: fragment => { + is(countAll(fragment), 0); + }, + }, + { + name: "background", + value: "rgb(255, var(--g-value), 192)", + test: fragment => { + is(fragment.textContent, "rgb(255, var(--g-value), 192)"); + }, + }, + { + name: "background", + value: "rgb(255, var(--g-value, 0), 192)", + test: fragment => { + is(fragment.textContent, "rgb(255, var(--g-value, 0), 192)"); + }, + }, + { + name: "--url", + value: "url(())", + test: fragment => { + is(countAll(fragment), 0); + is(fragment.textContent, "url(())"); + }, + }, +]; + +add_task(async function () { + const cssProperties = getClientCssProperties(); + const parser = new OutputParser(document, cssProperties); + for (let i = 0; i < TEST_DATA.length; i++) { + const data = TEST_DATA[i]; + info( + "Output-parser test data " + + i + + ". {" + + data.name + + " : " + + data.value + + ";}" + ); + data.test( + parser.parseCssProperty(data.name, data.value, { + colorClass: COLOR_CLASS, + urlClass: URL_CLASS, + bezierClass: CUBIC_BEZIER_CLASS, + angleClass: ANGLE_CLASS, + linearEasingClass: LINEAR_EASING_CLASS, + }) + ); + } +}); + +function countAll(fragment) { + return fragment.querySelectorAll("*").length; +} +function countColors(fragment) { + return fragment.querySelectorAll("." + COLOR_CLASS).length; +} +function countUrls(fragment) { + return fragment.querySelectorAll("." + URL_CLASS).length; +} +function countCubicBeziers(fragment) { + return fragment.querySelectorAll("." + CUBIC_BEZIER_CLASS).length; +} +function countLinears(fragment) { + return fragment.querySelectorAll("." + LINEAR_EASING_CLASS).length; +} +function getColor(fragment, index) { + return fragment.querySelectorAll("." + COLOR_CLASS)[index || 0].textContent; +} +function getUrl(fragment, index) { + return fragment.querySelectorAll("." + URL_CLASS)[index || 0].textContent; +} +function getCubicBezier(fragment, index) { + return fragment.querySelectorAll("." + CUBIC_BEZIER_CLASS)[index || 0] + .textContent; +} +function getLinear(fragment, index = 0) { + return fragment.querySelectorAll("." + LINEAR_EASING_CLASS)[index] + .textContent; +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js new file mode 100644 index 0000000000..de2f3cc3e6 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule and computed view refreshes when they are active. + +const TEST_URI = ` +
one
+
two
+`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + + await selectNode("#one", inspector); + + is( + getRuleViewPropertyValue(view, "element", "color"), + "red", + "The rule-view shows the properties for test node one" + ); + + info("Switching to the computed-view"); + const onComputedViewReady = inspector.once("computed-view-refreshed"); + selectComputedView(inspector); + await onComputedViewReady; + const cView = inspector.getPanel("computedview").computedView; + + is( + getComputedViewPropertyValue(cView, "color"), + "rgb(255, 0, 0)", + "The computed-view shows the properties for test node one" + ); + + info("Selecting test node two"); + await selectNode("#two", inspector); + + is( + getComputedViewPropertyValue(cView, "color"), + "rgb(0, 0, 255)", + "The computed-view shows the properties for test node two" + ); + is( + getRuleViewPropertyValue(view, "element", "color"), + "blue", + "The rule-view shows the properties for test node two" + ); +}); diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js new file mode 100644 index 0000000000..9b92e65c07 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_style_changes.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule and computed views refresh when style changes that impact the +// current selection occur. +// This test does not need to worry about the correctness of the styles and rules +// displayed in these views (other tests do this) but only cares that they do catch the +// change. + +const TEST_URI = TEST_URL_ROOT + "doc_content_style_changes.html"; + +const TEST_DATA = [ + { + target: "#test", + className: "green-class", + force: true, + }, + { + target: "#test", + className: "green-class", + force: false, + }, + { + target: "#parent", + className: "purple-class", + force: true, + }, + { + target: "#parent", + className: "purple-class", + force: false, + }, + { + target: "#sibling", + className: "blue-class", + force: true, + }, + { + target: "#sibling", + className: "blue-class", + force: false, + }, +]; + +add_task(async function () { + const tab = await addTab(TEST_URI); + + const { inspector } = await openRuleView(); + await selectNode("#test", inspector); + + info("Run the test on the rule-view"); + await runViewTest(inspector, tab, "rule"); + + info("Switch to the computed view"); + const onComputedViewReady = inspector.once("computed-view-refreshed"); + selectComputedView(inspector); + await onComputedViewReady; + + info("Run the test again on the computed view"); + await runViewTest(inspector, tab, "computed"); +}); + +async function runViewTest(inspector, tab, viewName) { + for (const { target, className, force } of TEST_DATA) { + info( + (force ? "Adding" : "Removing") + + ` class ${className} on ${target} and expecting a ${viewName}-view refresh` + ); + + await toggleClassAndWaitForViewChange( + { target, className, force }, + inspector, + tab, + `${viewName}-view-refreshed` + ); + } +} + +async function toggleClassAndWaitForViewChange( + whatToMutate, + inspector, + tab, + eventName +) { + const onRefreshed = inspector.once(eventName); + + await SpecialPowers.spawn( + tab.linkedBrowser, + [whatToMutate], + function ({ target, className, force }) { + content.document.querySelector(target).classList.toggle(className, force); + } + ); + + await onRefreshed; + ok(true, "The view was refreshed after the class was changed"); +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js new file mode 100644 index 0000000000..638648d78e --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-background-image.js @@ -0,0 +1,150 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that background-image URLs have image preview tooltips in the rule-view +// and computed-view + +const TEST_URI = ` + +
test element
+`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let { inspector, view } = await openRuleView(); + + info("Testing the background-image property on the body rule"); + await testBodyRuleView(view); + + info("Selecting the test div node"); + await selectNode(".test-element", inspector); + info("Testing the the background property on the .test-element rule"); + await testDivRuleView(view); + + info( + "Testing that image preview tooltips show even when there are " + + "fields being edited" + ); + await testTooltipAppearsEvenInEditMode(view); + + info("Switching over to the computed-view"); + const onComputedViewReady = inspector.once("computed-view-refreshed"); + view = selectComputedView(inspector); + await onComputedViewReady; + + info("Testing that the background-image computed style has a tooltip too"); + await testComputedView(view); +}); + +async function testBodyRuleView(view) { + info("Testing tooltips in the rule view"); + + // XXX we have an intermittent here (Bug 1743594) where the rule view is still empty + // at this point. We're currently investigating what's going on and a proper way to + // wait in openRuleView, but for now, let's fix the intermittent by waiting until the + // rule view has the expected content. + const property = await waitFor(() => + getRuleViewProperty(view, "body", "background-image") + ); + + // Get the background-image property inside the rule view + const { valueSpan } = property; + const uriSpan = valueSpan.querySelector(".theme-link"); + + const previewTooltip = await assertShowPreviewTooltip(view, uriSpan); + + const images = previewTooltip.panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + ok( + images[0] + .getAttribute("src") + .includes("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe"), + "The image URL seems fine" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan); +} + +async function testDivRuleView(view) { + // Get the background property inside the rule view + const { valueSpan } = getRuleViewProperty( + view, + ".test-element", + "background" + ); + const uriSpan = valueSpan.querySelector(".theme-link"); + + const previewTooltip = await assertShowPreviewTooltip(view, uriSpan); + + const images = previewTooltip.panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + ok( + images[0].getAttribute("src").startsWith("data:"), + "Tooltip contains a data-uri image as expected" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan); +} + +async function testTooltipAppearsEvenInEditMode(view) { + info("Switching to edit mode in the rule view"); + const editor = await turnToEditMode(view); + + info("Now trying to show the preview tooltip"); + const { valueSpan } = getRuleViewProperty( + view, + ".test-element", + "background" + ); + const uriSpan = valueSpan.querySelector(".theme-link"); + + const previewTooltip = await assertShowPreviewTooltip(view, uriSpan); + + is( + view.styleDocument.activeElement, + editor.input, + "Tooltip was shown in edit mode, and inplace-editor still focused" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan); +} + +function turnToEditMode(ruleView) { + const brace = ruleView.styleDocument.querySelector(".ruleview-ruleclose"); + return focusEditableField(ruleView, brace); +} + +async function testComputedView(view) { + const { valueSpan } = getComputedViewProperty(view, "background-image"); + const uriSpan = valueSpan.querySelector(".theme-link"); + + // Scroll to ensure the line is visible as we see the box model by default + valueSpan.scrollIntoView(); + + const previewTooltip = await assertShowPreviewTooltip(view, uriSpan); + + const images = previewTooltip.panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + + ok( + images[0].getAttribute("src").startsWith("data:"), + "Tooltip contains a data-uri in the computed-view too" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan); +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js new file mode 100644 index 0000000000..3fe58aa63d --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-closes-on-new-selection.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that if a tooltip is visible when a new selection is made, it closes + +const TEST_URI = "
el 1
el 2
"; +const XHTML_NS = "http://www.w3.org/1999/xhtml"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let { inspector, view } = await openRuleView(); + await selectNode(".one", inspector); + + info("Testing rule view tooltip closes on new selection"); + await testRuleView(view, inspector); + + info("Testing computed view tooltip closes on new selection"); + view = selectComputedView(inspector); + await testComputedView(view, inspector); +}); + +async function testRuleView(ruleView, inspector) { + info("Showing the tooltip"); + + const tooltip = ruleView.tooltips.getTooltip("previewTooltip"); + const tooltipContent = ruleView.styleDocument.createElementNS( + XHTML_NS, + "div" + ); + tooltip.panel.appendChild(tooltipContent); + tooltip.setContentSize({ width: 100, height: 30 }); + + // Stop listening for mouse movements because it's not needed for this test, + // and causes intermittent failures on Linux. When this test runs in the suite + // sometimes a mouseleave event is dispatched at the start, which causes the + // tooltip to hide in the middle of being shown, which causes timeouts later. + tooltip.stopTogglingOnHover(); + + const onShown = tooltip.once("shown"); + tooltip.show(ruleView.styleDocument.firstElementChild); + await onShown; + + info("Selecting a new node"); + const onHidden = tooltip.once("hidden"); + await selectNode(".two", inspector); + await onHidden; + + ok(true, "Rule view tooltip closed after a new node got selected"); +} + +async function testComputedView(computedView, inspector) { + info("Showing the tooltip"); + + const tooltip = computedView.tooltips.getTooltip("previewTooltip"); + const tooltipContent = computedView.styleDocument.createElementNS( + XHTML_NS, + "div" + ); + tooltip.panel.appendChild(tooltipContent); + await tooltip.setContentSize({ width: 100, height: 30 }); + + // Stop listening for mouse movements because it's not needed for this test, + // and causes intermittent failures on Linux. When this test runs in the suite + // sometimes a mouseleave event is dispatched at the start, which causes the + // tooltip to hide in the middle of being shown, which causes timeouts later. + tooltip.stopTogglingOnHover(); + + const onShown = tooltip.once("shown"); + tooltip.show(computedView.styleDocument.firstElementChild); + await onShown; + + info("Selecting a new node"); + const onHidden = tooltip.once("hidden"); + await selectNode(".one", inspector); + await onHidden; + + ok(true, "Computed view tooltip closed after a new node got selected"); +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js new file mode 100644 index 0000000000..8a31e94918 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-longhand-fontfamily.js @@ -0,0 +1,178 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the fontfamily tooltip on longhand properties + +const TEST_URI = ` + +
test element
+`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + let { inspector, view } = await openRuleView(); + await selectNode("#testElement", inspector); + await testRuleView(view, inspector.selection.nodeFront); + + info("Opening the computed view"); + const onComputedViewReady = inspector.once("computed-view-refreshed"); + view = selectComputedView(inspector); + await onComputedViewReady; + + await testComputedView(view, inspector.selection.nodeFront); + + await testExpandedComputedViewProperty(view, inspector.selection.nodeFront); +}); + +async function testRuleView(ruleView, nodeFront) { + info("Testing font-family tooltips in the rule view"); + + const tooltip = ruleView.tooltips.getTooltip("previewTooltip"); + const panel = tooltip.panel; + + // Check that the rule view has a tooltip and that a XUL panel has + // been created + ok(tooltip, "Tooltip instance exists"); + ok(panel, "XUL panel exists"); + + // Get the font family property inside the rule view + const { valueSpan } = getRuleViewProperty( + ruleView, + "#testElement", + "font-family" + ); + + // And verify that the tooltip gets shown on this property + valueSpan.scrollIntoView(true); + let previewTooltip = await assertShowPreviewTooltip(ruleView, valueSpan); + + let images = panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + ok( + images[0].getAttribute("src").startsWith("data:"), + "Tooltip contains a data-uri image as expected" + ); + + let dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront); + is( + images[0].getAttribute("src"), + dataURL, + "Tooltip contains the correct data-uri image" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan); + + // Do the tooltip test again, but now when hovering on the span that + // encloses each and every font family. + const fontFamilySpan = valueSpan.querySelector(".ruleview-font-family"); + fontFamilySpan.scrollIntoView(true); + + previewTooltip = await assertShowPreviewTooltip(ruleView, fontFamilySpan); + + images = panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + ok( + images[0].getAttribute("src").startsWith("data:"), + "Tooltip contains a data-uri image as expected" + ); + + dataURL = await getFontFamilyDataURL(fontFamilySpan.textContent, nodeFront); + is( + images[0].getAttribute("src"), + dataURL, + "Tooltip contains the correct data-uri image" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, fontFamilySpan); +} + +async function testComputedView(computedView, nodeFront) { + info("Testing font-family tooltips in the computed view"); + + const tooltip = computedView.tooltips.getTooltip("previewTooltip"); + const panel = tooltip.panel; + const { valueSpan } = getComputedViewProperty(computedView, "font-family"); + + valueSpan.scrollIntoView(true); + const previewTooltip = await assertShowPreviewTooltip( + computedView, + valueSpan + ); + + const images = panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + ok( + images[0].getAttribute("src").startsWith("data:"), + "Tooltip contains a data-uri image as expected" + ); + + const dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront); + is( + images[0].getAttribute("src"), + dataURL, + "Tooltip contains the correct data-uri image" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan); +} + +async function testExpandedComputedViewProperty(computedView, nodeFront) { + info( + "Testing font-family tooltips in expanded properties of the " + + "computed view" + ); + + info("Expanding the font-family property to reveal matched selectors"); + const propertyView = getPropertyView(computedView, "font-family"); + propertyView.matchedExpanded = true; + await propertyView.refreshMatchedSelectors(); + + const valueSpan = propertyView.matchedSelectorsContainer.querySelector( + ".bestmatch .computed-other-property-value" + ); + + const tooltip = computedView.tooltips.getTooltip("previewTooltip"); + const panel = tooltip.panel; + + valueSpan.scrollIntoView(true); + const previewTooltip = await assertShowPreviewTooltip( + computedView, + valueSpan + ); + + const images = panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + ok( + images[0].getAttribute("src").startsWith("data:"), + "Tooltip contains a data-uri image as expected" + ); + + const dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront); + is( + images[0].getAttribute("src"), + dataURL, + "Tooltip contains the correct data-uri image" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan); +} + +function getPropertyView(computedView, name) { + let propertyView = null; + computedView.propertyViews.some(function (view) { + if (view.name == name) { + propertyView = view; + return true; + } + return false; + }); + return propertyView; +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js new file mode 100644 index 0000000000..5caab37555 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-multiple-background-images.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test for bug 1026921: Ensure the URL of hovered url() node is used instead +// of the first found from the declaration as there might be multiple urls. + +const YELLOW_DOT = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gYcDCwCr0o5ngAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAANSURBVAjXY/j/n6EeAAd9An7Z55GEAAAAAElFTkSuQmCC"; +const BLUE_DOT = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gYcDCwlCkCM9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAANSURBVAjXY2Bg+F8PAAKCAX/tPkrkAAAAAElFTkSuQmCC"; +const TEST_STYLE = `h1 {background: url(${YELLOW_DOT}), url(${BLUE_DOT});}`; +const TEST_URI = `

test element

`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector } = await openInspector(); + + await testRuleViewUrls(inspector); + await testComputedViewUrls(inspector); +}); + +async function testRuleViewUrls(inspector) { + info("Testing tooltips in the rule view"); + const view = selectRuleView(inspector); + await selectNode("h1", inspector); + + const { valueSpan } = getRuleViewProperty(view, "h1", "background"); + await performChecks(view, valueSpan); +} + +async function testComputedViewUrls(inspector) { + info("Testing tooltips in the computed view"); + + const onComputedViewReady = inspector.once("computed-view-refreshed"); + const view = selectComputedView(inspector); + await onComputedViewReady; + + const { valueSpan } = getComputedViewProperty(view, "background-image"); + + await performChecks(view, valueSpan); +} + +/** + * A helper that checks url() tooltips contain correct images + */ +async function performChecks(view, propertyValue) { + function checkTooltip(panel, imageSrc) { + const images = panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + is(images[0].getAttribute("src"), imageSrc, "The image URL is correct"); + } + + const links = propertyValue.querySelectorAll(".theme-link"); + + info("Checking first link tooltip"); + let previewTooltip = await assertShowPreviewTooltip(view, links[0]); + const panel = view.tooltips.getTooltip("previewTooltip").panel; + checkTooltip(panel, YELLOW_DOT); + + await assertTooltipHiddenOnMouseOut(previewTooltip, links[0]); + + info("Checking second link tooltip"); + previewTooltip = await assertShowPreviewTooltip(view, links[1]); + checkTooltip(panel, BLUE_DOT); + + await assertTooltipHiddenOnMouseOut(previewTooltip, links[1]); +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js new file mode 100644 index 0000000000..ef98a20a37 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-shorthand-fontfamily.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the fontfamily tooltip on shorthand properties + +const TEST_URI = ` + +
test element
+`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + + await selectNode("#testElement", inspector); + await testRuleView(view, inspector.selection.nodeFront); +}); + +async function testRuleView(ruleView, nodeFront) { + info("Testing font-family tooltips in the rule view"); + + const tooltip = ruleView.tooltips.getTooltip("previewTooltip"); + const panel = tooltip.panel; + + // Check that the rule view has a tooltip and that a XUL panel has + // been created + ok(tooltip, "Tooltip instance exists"); + ok(panel, "XUL panel exists"); + + // Get the computed font family property inside the font rule view + const propertyList = ruleView.element.querySelectorAll( + ".ruleview-propertylist" + ); + const fontExpander = + propertyList[1].querySelectorAll(".ruleview-expander")[0]; + fontExpander.click(); + + const rule = getRuleViewRule(ruleView, "#testElement"); + const computedlist = rule.querySelectorAll(".ruleview-computed"); + let valueSpan; + for (const computed of computedlist) { + const propertyName = computed.querySelector(".ruleview-propertyname"); + if (propertyName.textContent == "font-family") { + valueSpan = computed.querySelector(".ruleview-propertyvalue"); + break; + } + } + + // And verify that the tooltip gets shown on this property + const previewTooltip = await assertShowPreviewTooltip(ruleView, valueSpan); + + const images = panel.getElementsByTagName("img"); + is(images.length, 1, "Tooltip contains an image"); + ok( + images[0].getAttribute("src").startsWith("data:"), + "Tooltip contains a data-uri image as expected" + ); + + const dataURL = await getFontFamilyDataURL(valueSpan.textContent, nodeFront); + is( + images[0].getAttribute("src"), + dataURL, + "Tooltip contains the correct data-uri image" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, valueSpan); +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js new file mode 100644 index 0000000000..4458477a0c --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_tooltip-size.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Checking tooltips dimensions, to make sure their big enough to display their +// content + +const TEST_URI = ` + +
+`; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + await selectNode("div", inspector); + await testImageDimension(view); + await testPickerDimension(view); +}); + +async function testImageDimension(ruleView) { + info("Testing background-image tooltip dimensions"); + + const tooltip = ruleView.tooltips.getTooltip("previewTooltip"); + const panel = tooltip.panel; + const { valueSpan } = getRuleViewProperty(ruleView, "div", "background"); + const uriSpan = valueSpan.querySelector(".theme-link"); + + // Make sure there is a hover tooltip for this property, this also will fill + // the tooltip with its content + const previewTooltip = await assertShowPreviewTooltip(ruleView, uriSpan); + + // Let's not test for a specific size, but instead let's make sure it's at + // least as big as the image + const imageRect = panel.querySelector("img").getBoundingClientRect(); + const panelRect = panel.getBoundingClientRect(); + + Assert.greaterOrEqual( + panelRect.width, + imageRect.width, + "The panel is wide enough to show the image" + ); + Assert.greaterOrEqual( + panelRect.height, + imageRect.height, + "The panel is high enough to show the image" + ); + + await assertTooltipHiddenOnMouseOut(previewTooltip, uriSpan); +} + +async function testPickerDimension(ruleView) { + info("Testing color-picker tooltip dimensions"); + + const { valueSpan } = getRuleViewProperty(ruleView, "div", "background"); + const swatch = valueSpan.querySelector(".ruleview-colorswatch"); + const cPicker = ruleView.tooltips.getTooltip("colorPicker"); + + const onReady = cPicker.once("ready"); + swatch.click(); + await onReady; + + // The colorpicker spectrum's iframe has a fixed width height, so let's + // make sure the tooltip is at least as big as that + const spectrumRect = cPicker.spectrum.element.getBoundingClientRect(); + const panelRect = cPicker.tooltip.container.getBoundingClientRect(); + + Assert.greaterOrEqual( + panelRect.width, + spectrumRect.width, + "The panel is wide enough to show the picker" + ); + Assert.greaterOrEqual( + panelRect.height, + spectrumRect.height, + "The panel is high enough to show the picker" + ); + + const onHidden = cPicker.tooltip.once("hidden"); + const onRuleViewChanged = ruleView.once("ruleview-changed"); + cPicker.hide(); + await onHidden; + await onRuleViewChanged; +} diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js new file mode 100644 index 0000000000..b4297a66c2 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-01.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the css transform highlighter is created only when asked and only one +// instance exists across the inspector + +const TEST_URI = ` + + Test the css transform highlighter +`; + +const TYPE = "CssTransformHighlighter"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + + let overlay = view.highlighters; + + ok(!overlay.highlighters[TYPE], "No highlighter exists in the rule-view"); + const h = await overlay._getHighlighter(TYPE); + ok( + overlay.highlighters[TYPE], + "The highlighter has been created in the rule-view" + ); + is(h, overlay.highlighters[TYPE], "The right highlighter has been created"); + const h2 = await overlay._getHighlighter(TYPE); + is( + h, + h2, + "The same instance of highlighter is returned everytime in the rule-view" + ); + + const onComputedViewReady = inspector.once("computed-view-refreshed"); + const cView = selectComputedView(inspector); + await onComputedViewReady; + overlay = cView.highlighters; + + ok(overlay.highlighters[TYPE], "The highlighter exists in the computed-view"); + const h3 = await overlay._getHighlighter(TYPE); + is( + h, + h3, + "The same instance of highlighter is returned everytime " + + "in the computed-view" + ); +}); diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js new file mode 100644 index 0000000000..f1ddc68892 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-02.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the css transform highlighter is created when hovering over a +// transform property + +const TEST_URI = ` + + Test the css transform highlighter +`; + +var TYPE = "CssTransformHighlighter"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + let hs = view.highlighters; + + ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (1)"); + + info("Faking a mousemove on a non-transform property"); + let { valueSpan } = getRuleViewProperty(view, "body", "color"); + hs.onMouseMove({ target: valueSpan }); + ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (2)"); + + info("Faking a mousemove on a transform property"); + ({ valueSpan } = getRuleViewProperty(view, "body", "transform")); + let onHighlighterShown = hs.once("css-transform-highlighter-shown"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterShown; + + const onComputedViewReady = inspector.once("computed-view-refreshed"); + const cView = selectComputedView(inspector); + await onComputedViewReady; + hs = cView.highlighters; + + info("Remove the created transform highlighter"); + hs.highlighters[TYPE].finalize(); + hs.highlighters[TYPE] = null; + + info("Faking a mousemove on a non-transform property"); + ({ valueSpan } = getComputedViewProperty(cView, "color")); + hs.onMouseMove({ target: valueSpan }); + ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (3)"); + + info("Faking a mousemove on a transform property"); + ({ valueSpan } = getComputedViewProperty(cView, "transform")); + onHighlighterShown = hs.once("css-transform-highlighter-shown"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterShown; + + ok( + hs.highlighters[TYPE], + "The highlighter has been created in the computed-view" + ); +}); diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js new file mode 100644 index 0000000000..ee95db2432 --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-03.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the css transform highlighter is shown when hovering over transform +// properties + +// Note that in this test, we mock the highlighter front, merely testing the +// behavior of the style-inspector UI for now + +const TEST_URI = ` + + Test the css transform highlighter +`; + +const TYPE = "CssTransformHighlighter"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + + // Mock the highlighter front to get the reference of the NodeFront + const HighlighterFront = { + isShown: false, + nodeFront: null, + nbOfTimesShown: 0, + show(nodeFront) { + this.nodeFront = nodeFront; + this.isShown = true; + this.nbOfTimesShown++; + return Promise.resolve(true); + }, + hide() { + this.nodeFront = null; + this.isShown = false; + return Promise.resolve(); + }, + finalize() {}, + }; + + // Inject the mock highlighter in the rule-view + const hs = view.highlighters; + hs.highlighters[TYPE] = HighlighterFront; + + let { valueSpan } = getRuleViewProperty(view, "body", "transform"); + + info("Checking that the HighlighterFront's show/hide methods are called"); + let onHighlighterShown = hs.once("css-transform-highlighter-shown"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterShown; + ok(HighlighterFront.isShown, "The highlighter is shown"); + let onHighlighterHidden = hs.once("css-transform-highlighter-hidden"); + hs.onMouseOut(); + await onHighlighterHidden; + ok(!HighlighterFront.isShown, "The highlighter is hidden"); + + info( + "Checking that hovering several times over the same property doesn't" + + " show the highlighter several times" + ); + const nb = HighlighterFront.nbOfTimesShown; + onHighlighterShown = hs.once("css-transform-highlighter-shown"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterShown; + is(HighlighterFront.nbOfTimesShown, nb + 1, "The highlighter was shown once"); + hs.onMouseMove({ target: valueSpan }); + hs.onMouseMove({ target: valueSpan }); + is( + HighlighterFront.nbOfTimesShown, + nb + 1, + "The highlighter was shown once, after several mousemove" + ); + + info("Checking that the right NodeFront reference is passed"); + await selectNode("html", inspector); + ({ valueSpan } = getRuleViewProperty(view, "html", "transform")); + onHighlighterShown = hs.once("css-transform-highlighter-shown"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterShown; + is( + HighlighterFront.nodeFront.tagName, + "HTML", + "The right NodeFront is passed to the highlighter (1)" + ); + + await selectNode("body", inspector); + ({ valueSpan } = getRuleViewProperty(view, "body", "transform")); + onHighlighterShown = hs.once("css-transform-highlighter-shown"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterShown; + is( + HighlighterFront.nodeFront.tagName, + "BODY", + "The right NodeFront is passed to the highlighter (2)" + ); + + info( + "Checking that the highlighter gets hidden when hovering a " + + "non-transform property" + ); + ({ valueSpan } = getRuleViewProperty(view, "body", "color")); + onHighlighterHidden = hs.once("css-transform-highlighter-hidden"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterHidden; + ok(!HighlighterFront.isShown, "The highlighter is hidden"); +}); diff --git a/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js new file mode 100644 index 0000000000..1bf76d517b --- /dev/null +++ b/devtools/client/inspector/shared/test/browser_styleinspector_transform-highlighter-04.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the css transform highlighter is shown only when hovering over a +// transform declaration that isn't overriden or disabled + +// Note that unlike the other browser_styleinspector_transform-highlighter-N.js +// tests, this one only tests the rule-view as only this view features disabled +// and overriden properties + +const TEST_URI = ` + +
+`; + +const TYPE = "CssTransformHighlighter"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + await selectNode(".test", inspector); + + const hs = view.highlighters; + + info("Faking a mousemove on the overriden property"); + let { valueSpan } = getRuleViewProperty(view, "div", "transform"); + hs.onMouseMove({ target: valueSpan }); + ok( + !hs.highlighters[TYPE], + "No highlighter was created for the overriden property" + ); + + info("Disabling the applied property"); + const classRuleEditor = getRuleViewRuleEditor(view, 1); + const propEditor = classRuleEditor.rule.textProps[0].editor; + propEditor.enable.click(); + await classRuleEditor.rule._applyingModifications; + + info("Faking a mousemove on the disabled property"); + ({ valueSpan } = getRuleViewProperty(view, ".test", "transform")); + hs.onMouseMove({ target: valueSpan }); + ok( + !hs.highlighters[TYPE], + "No highlighter was created for the disabled property" + ); + + info("Faking a mousemove on the now unoverriden property"); + ({ valueSpan } = getRuleViewProperty(view, "div", "transform")); + const onHighlighterShown = hs.once("css-transform-highlighter-shown"); + hs.onMouseMove({ target: valueSpan }); + await onHighlighterShown; +}); diff --git a/devtools/client/inspector/shared/test/doc_content_style_changes.html b/devtools/client/inspector/shared/test/doc_content_style_changes.html new file mode 100644 index 0000000000..c439f2bf4f --- /dev/null +++ b/devtools/client/inspector/shared/test/doc_content_style_changes.html @@ -0,0 +1,28 @@ + + + +
+
+
+
test
+
+
diff --git a/devtools/client/inspector/shared/test/head.js b/devtools/client/inspector/shared/test/head.js new file mode 100644 index 0000000000..f87fed0f03 --- /dev/null +++ b/devtools/client/inspector/shared/test/head.js @@ -0,0 +1,218 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint no-unused-vars: [2, {"vars": "local"}] */ + +"use strict"; + +// Import the inspector's head.js first (which itself imports shared-head.js). +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js", + this +); + +var { + CssRuleView, +} = require("resource://devtools/client/inspector/rules/rules.js"); +var { + getInplaceEditorForSpan: inplaceEditor, +} = require("resource://devtools/client/shared/inplace-editor.js"); +const { + getColor: getThemeColor, +} = require("resource://devtools/client/shared/theme.js"); + +const TEST_URL_ROOT = + "http://example.com/browser/devtools/client/inspector/shared/test/"; +const TEST_URL_ROOT_SSL = + "https://example.com/browser/devtools/client/inspector/shared/test/"; +const ROOT_TEST_DIR = getRootDirectory(gTestPath); +const STYLE_INSPECTOR_L10N = new LocalizationHelper( + "devtools/shared/locales/styleinspector.properties" +); + +// Clean-up all prefs that might have been changed during a test run +// (safer here because if the test fails, then the pref is never reverted) +registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.defaultColorUnit"); +}); + +/** + * The functions found below are here to ease test development and maintenance. + * Most of these functions are stateless and will require some form of context + * (the instance of the current toolbox, or inspector panel for instance). + * + * Most of these functions are async too and return promises. + * + * All tests should follow the following pattern: + * + * add_task(async function() { + * await addTab(TEST_URI); + * let {toolbox, inspector} = await openInspector(); + * await inspector.sidebar.select(viewId); + * let view = inspector.getPanel(viewId).view; + * await selectNode("#test", inspector); + * await someAsyncTestFunction(view); + * }); + * + * add_task is the way to define the testcase in the test file. It accepts + * a single argument: a function returning a promise (usually async function). + * + * There is no need to clean tabs up at the end of a test as this is done + * automatically. + * + * It is advised not to store any references on the global scope. There + * shouldn't be a need to anyway. Thanks to async functions, test steps, even + * though asynchronous, can be described in a nice flat way, and + * if/for/while/... control flow can be used as in sync code, making it + * possible to write the outline of the test case all in add_task, and delegate + * actual processing and assertions to other functions. + */ + +/* ********************************************* + * UTILS + * ********************************************* + * General test utilities. + * Add new tabs, open the toolbox and switch to the various panels, select + * nodes, get node references, ... + */ + +/** + * Polls a given function waiting for it to return true. + * + * @param {Function} validatorFn + * A validator function that returns a boolean. + * This is called every few milliseconds to check if the result is true. + * When it is true, the promise resolves. + * @param {String} name + * Optional name of the test. This is used to generate + * the success and failure messages. + * @return a promise that resolves when the function returned true or rejects + * if the timeout is reached + */ +function waitForSuccess(validatorFn, name = "untitled") { + return new Promise(resolve => { + function wait(validator) { + if (validator()) { + ok(true, "Validator function " + name + " returned true"); + resolve(); + } else { + setTimeout(() => wait(validator), 200); + } + } + wait(validatorFn); + }); +} + +/** + * Get the dataURL for the font family tooltip. + * + * @param {String} font + * The font family value. + * @param {object} nodeFront + * The NodeActor that will used to retrieve the dataURL for the + * font family tooltip contents. + */ +var getFontFamilyDataURL = async function (font, nodeFront) { + const fillStyle = getThemeColor("body-color"); + + const { data } = await nodeFront.getFontFamilyDataURL(font, fillStyle); + const dataURL = await data.string(); + return dataURL; +}; + +/* ********************************************* + * RULE-VIEW + * ********************************************* + * Rule-view related test utility functions + * This object contains functions to get rules, get properties, ... + */ + +/** + * Simulate a color change in a given color picker tooltip, and optionally wait + * for a given element in the page to have its style changed as a result + * + * @param {RuleView} ruleView + * The related rule view instance + * @param {SwatchColorPickerTooltip} colorPicker + * @param {Array} newRgba + * The new color to be set [r, g, b, a] + * @param {Object} expectedChange + * Optional object that needs the following props: + * - {DOMNode} element The element in the page that will have its + * style changed. + * - {String} name The style name that will be changed + * - {String} value The expected style value + * The style will be checked like so: getComputedStyle(element)[name] === value + */ +var simulateColorPickerChange = async function ( + ruleView, + colorPicker, + newRgba, + expectedChange +) { + const onRuleViewChanged = ruleView.once("ruleview-changed"); + info("Getting the spectrum colorpicker object"); + const spectrum = await colorPicker.spectrum; + info("Setting the new color"); + spectrum.rgb = newRgba; + info("Applying the change"); + spectrum.updateUI(); + spectrum.onChange(); + info("Waiting for rule-view to update"); + await onRuleViewChanged; + + if (expectedChange) { + info("Waiting for the style to be applied on the page"); + await waitForSuccess(() => { + const { element, name, value } = expectedChange; + return content.getComputedStyle(element)[name] === value; + }, "Color picker change applied on the page"); + } +}; + +/* ********************************************* + * COMPUTED-VIEW + * ********************************************* + * Computed-view related utility functions. + * Allows to get properties, links, expand properties, ... + */ + +/** + * Get references to the name and value span nodes corresponding to a given + * property name in the computed-view + * + * @param {CssComputedView} view + * The instance of the computed view panel + * @param {String} name + * The name of the property to retrieve + * @return an object {nameSpan, valueSpan} + */ +function getComputedViewProperty(view, name) { + let prop; + for (const property of view.styleDocument.querySelectorAll( + ".computed-property-view" + )) { + const nameSpan = property.querySelector(".computed-property-name"); + const valueSpan = property.querySelector(".computed-property-value"); + + if (nameSpan.firstChild.textContent === name) { + prop = { nameSpan, valueSpan }; + break; + } + } + return prop; +} + +/** + * Get the text value of the property corresponding to a given name in the + * computed-view + * + * @param {CssComputedView} view + * The instance of the computed view panel + * @param {String} name + * The name of the property to retrieve + * @return {String} The property value + */ +function getComputedViewPropertyValue(view, name, propertyName) { + return getComputedViewProperty(view, name, propertyName).valueSpan + .textContent; +} -- cgit v1.2.3