summaryrefslogtreecommitdiffstats
path: root/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js')
-rw-r--r--devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js223
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;
+}