/* 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=

highlighter test

`; const TOP_LEVEL_URL = `https://example.com/document-builder.sjs?html=

highlighter test

`; 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} 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; }