diff options
Diffstat (limited to 'devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js')
-rw-r--r-- | devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js b/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js new file mode 100644 index 0000000000..a5ee01e438 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js @@ -0,0 +1,223 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that hovering over a simple selector in the style-editor requests the +// highlighting of the corresponding nodes, even in remote iframes. + +const REMOTE_IFRAME_URL = `https://example.org/document-builder.sjs?html= + <style>h2{color:cyan}</style> + <h2>highlighter test</h2>`; +const TOP_LEVEL_URL = `https://example.com/document-builder.sjs?html= + <style>h1{color:red}</style> + <h1>highlighter test</h1> + <iframe src='${REMOTE_IFRAME_URL}'></iframe>`; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TOP_LEVEL_URL); + + info( + "Wait until both stylesheet are loaded and ready to handle mouse events" + ); + await waitFor(() => ui.editors.length == 2); + const topLevelStylesheetEditor = ui.editors.find(e => + e._resource.nodeHref.startsWith("https://example.com") + ); + const iframeStylesheetEditor = ui.editors.find(e => + e._resource.nodeHref.startsWith("https://example.org") + ); + + await ui.selectStyleSheet(topLevelStylesheetEditor.styleSheet); + await waitFor(() => topLevelStylesheetEditor.highlighter); + + info("Check that highlighting works on the top-level document"); + const topLevelHighlighterTestFront = + await topLevelStylesheetEditor._resource.targetFront.getFront( + "highlighterTest" + ); + topLevelHighlighterTestFront.highlighter = + topLevelStylesheetEditor.highlighter; + + info("Expecting a node-highlighted event"); + let onHighlighted = topLevelStylesheetEditor.once("node-highlighted"); + + info("Simulate a mousemove event on the h1 selector"); + // mousemove event listeners is set on editor.sourceEditor, which is not defined right away. + await waitFor(() => !!topLevelStylesheetEditor.sourceEditor); + let selectorEl = querySelectorCodeMirrorCssRuleSelectorToken( + topLevelStylesheetEditor + ); + EventUtils.synthesizeMouseAtCenter( + selectorEl, + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + await onHighlighted; + ok( + await topLevelHighlighterTestFront.isNodeRectHighlighted( + await getElementNodeRectWithinTarget(["h1"]) + ), + "The highlighter's outline corresponds to the h1 node" + ); + + info( + "Simulate a mousemove event on the property name to hide the highlighter" + ); + EventUtils.synthesizeMouseAtCenter( + querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor), + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + + await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); + let isVisible = await topLevelHighlighterTestFront.isHighlighting(); + is(isVisible, false, "The highlighter is now hidden"); + + // It looks like we need to show the same highlighter again to trigger Bug 1847747 + info("Show and hide the highlighter again"); + onHighlighted = topLevelStylesheetEditor.once("node-highlighted"); + EventUtils.synthesizeMouseAtCenter( + selectorEl, + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + await onHighlighted; + EventUtils.synthesizeMouseAtCenter( + querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor), + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + + await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); + // wait for a bit so the style editor would have had the time to process + // any unwanted stylesheets + await wait(1000); + ok( + !ui.editors.find(e => e._resource.href?.includes("highlighters.css")), + "highlighters.css isn't displayed in StyleEditor" + ); + is(ui.editors.length, 2, "No other stylesheet was displayed"); + + info("Check that highlighting works on the iframe document"); + await ui.selectStyleSheet(iframeStylesheetEditor.styleSheet); + await waitFor(() => iframeStylesheetEditor.highlighter); + + const iframeHighlighterTestFront = + await iframeStylesheetEditor._resource.targetFront.getFront( + "highlighterTest" + ); + iframeHighlighterTestFront.highlighter = iframeStylesheetEditor.highlighter; + + info("Expecting a node-highlighted event"); + onHighlighted = iframeStylesheetEditor.once("node-highlighted"); + + info("Simulate a mousemove event on the h2 selector"); + // mousemove event listeners is set on editor.sourceEditor, which is not defined right away. + await waitFor(() => !!iframeStylesheetEditor.sourceEditor); + selectorEl = querySelectorCodeMirrorCssRuleSelectorToken( + iframeStylesheetEditor + ); + EventUtils.synthesizeMouseAtCenter( + selectorEl, + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + await onHighlighted; + + isVisible = await iframeHighlighterTestFront.isHighlighting(); + ok(isVisible, "The highlighter is shown"); + ok( + await iframeHighlighterTestFront.isNodeRectHighlighted( + await getElementNodeRectWithinTarget(["iframe", "h2"]) + ), + "The highlighter's outline corresponds to the h2 node" + ); + + info("Simulate a mousemove event elsewhere in the editor"); + EventUtils.synthesizeMouseAtCenter( + querySelectorCodeMirrorCssPropertyNameToken(iframeStylesheetEditor), + { type: "mousemove" }, + selectorEl.ownerDocument.defaultView + ); + + await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown()); + + isVisible = await iframeHighlighterTestFront.isHighlighting(); + is(isVisible, false, "The highlighter is now hidden"); +}); + +function querySelectorCodeMirrorCssRuleSelectorToken(stylesheetEditor) { + // CSS Rules selector (e.g. `h1`) are displayed in a .cm-tag span + return querySelectorCodeMirror(stylesheetEditor, ".cm-tag"); +} + +function querySelectorCodeMirrorCssPropertyNameToken(stylesheetEditor) { + // properties name (e.g. `color`) are displayed in a .cm-property span + return querySelectorCodeMirror(stylesheetEditor, ".cm-property"); +} + +function querySelectorCodeMirror(stylesheetEditor, selector) { + return stylesheetEditor.sourceEditor.codeMirror + .getWrapperElement() + .querySelector(selector); +} + +/** + * Return the bounds of the element matching the selector, relatively to the target bounds + * (e.g. if Fission is enabled, it's related to the iframe bound, if Fission is disabled, + * it's related to the top level document). + * + * @param {Array<string>} selectors: Arrays of CSS selectors from the root document to the node. + * The last CSS selector of the array is for the node in its frame doc. + * The before-last CSS selector is for the frame in its parent frame, etc... + * Ex: ["frame.first-frame", ..., "frame.last-frame", ".target-node"] + * @returns {Object} with left/top/width/height properties representing the node bounds + */ +async function getElementNodeRectWithinTarget(selectors) { + // Retrieve the browsing context in which the element is + const inBCSelector = selectors.pop(); + const frameSelectors = selectors; + const bc = frameSelectors.length + ? await getBrowsingContextInFrames( + gBrowser.selectedBrowser.browsingContext, + frameSelectors + ) + : gBrowser.selectedBrowser.browsingContext; + + // Get the element bounds within the Firefox window + const elementBounds = await SpecialPowers.spawn( + bc, + [inBCSelector], + _selector => { + const el = content.document.querySelector(_selector); + const { left, top, width, height } = el + .getBoxQuadsFromWindowOrigin()[0] + .getBounds(); + return { left, top, width, height }; + } + ); + + // Then we need to offset the element bounds from a frame bounds + // When fission/EFT is enabled, the highlighter is only shown within the iframe bounds. + // So we only need to retrieve the element bounds within the iframe. + // Otherwise, we retrieve the top frame bounds + const relativeBrowsingContext = + isFissionEnabled() || isEveryFrameTargetEnabled() + ? bc + : gBrowser.selectedBrowser.browsingContext; + const relativeDocumentBounds = await SpecialPowers.spawn( + relativeBrowsingContext, + [], + () => + content.document.documentElement + .getBoxQuadsFromWindowOrigin()[0] + .getBounds() + ); + + // Adjust the element bounds based on the relative document bounds + elementBounds.left = elementBounds.left - relativeDocumentBounds.left; + elementBounds.top = elementBounds.top - relativeDocumentBounds.top; + + return elementBounds; +} |