tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_styleeditor_highlight-selector.js (8148B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test that hovering over a simple selector in the style-editor requests the
      7 // highlighting of the corresponding nodes, even in remote iframes.
      8 
      9 const REMOTE_IFRAME_URL = `https://example.org/document-builder.sjs?html=
     10    <style>h2{color:cyan}</style>
     11    <h2>highlighter test</h2>`;
     12 const TOP_LEVEL_URL = `https://example.com/document-builder.sjs?html=
     13    <style>h1{color:red}</style>
     14    <h1>highlighter test</h1>
     15    <iframe src='${REMOTE_IFRAME_URL}'></iframe>`;
     16 
     17 add_task(async function () {
     18  const { ui } = await openStyleEditorForURL(TOP_LEVEL_URL);
     19 
     20  info(
     21    "Wait until both stylesheet are loaded and ready to handle mouse events"
     22  );
     23  await waitFor(() => ui.editors.length == 2);
     24  const topLevelStylesheetEditor = ui.editors.find(e =>
     25    e._resource.nodeHref.startsWith("https://example.com")
     26  );
     27  const iframeStylesheetEditor = ui.editors.find(e =>
     28    e._resource.nodeHref.startsWith("https://example.org")
     29  );
     30 
     31  await ui.selectStyleSheet(topLevelStylesheetEditor.styleSheet);
     32  await waitFor(() => topLevelStylesheetEditor.highlighter);
     33 
     34  info("Check that highlighting works on the top-level document");
     35  const topLevelHighlighterTestFront =
     36    await topLevelStylesheetEditor._resource.targetFront.getFront(
     37      "highlighterTest"
     38    );
     39  topLevelHighlighterTestFront.highlighter =
     40    topLevelStylesheetEditor.highlighter;
     41 
     42  info("Expecting a node-highlighted event");
     43  let onHighlighted = topLevelStylesheetEditor.once("node-highlighted");
     44 
     45  info("Simulate a mousemove event on the h1 selector");
     46  // mousemove event listeners is set on editor.sourceEditor, which is not defined right away.
     47  await waitFor(() => !!topLevelStylesheetEditor.sourceEditor);
     48  let selectorEl = querySelectorCodeMirrorCssRuleSelectorToken(
     49    topLevelStylesheetEditor
     50  );
     51  EventUtils.synthesizeMouseAtCenter(
     52    selectorEl,
     53    { type: "mousemove" },
     54    selectorEl.ownerDocument.defaultView
     55  );
     56  await onHighlighted;
     57  ok(
     58    await topLevelHighlighterTestFront.isNodeRectHighlighted(
     59      await getElementNodeRectWithinTarget(["h1"])
     60    ),
     61    "The highlighter's outline corresponds to the h1 node"
     62  );
     63 
     64  info(
     65    "Simulate a mousemove event on the property name to hide the highlighter"
     66  );
     67  EventUtils.synthesizeMouseAtCenter(
     68    querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor),
     69    { type: "mousemove" },
     70    selectorEl.ownerDocument.defaultView
     71  );
     72 
     73  await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown());
     74  let isVisible = await topLevelHighlighterTestFront.isHighlighting();
     75  is(isVisible, false, "The highlighter is now hidden");
     76 
     77  // It looks like we need to show the same highlighter again to trigger Bug 1847747
     78  info("Show and hide the highlighter again");
     79  onHighlighted = topLevelStylesheetEditor.once("node-highlighted");
     80  EventUtils.synthesizeMouseAtCenter(
     81    selectorEl,
     82    { type: "mousemove" },
     83    selectorEl.ownerDocument.defaultView
     84  );
     85  await onHighlighted;
     86  EventUtils.synthesizeMouseAtCenter(
     87    querySelectorCodeMirrorCssPropertyNameToken(topLevelStylesheetEditor),
     88    { type: "mousemove" },
     89    selectorEl.ownerDocument.defaultView
     90  );
     91 
     92  await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown());
     93  // wait for a bit so the style editor would have had the time to process
     94  // any unwanted stylesheets
     95  await wait(1000);
     96  ok(
     97    !ui.editors.find(e => e._resource.href?.includes("highlighters.css")),
     98    "highlighters.css isn't displayed in StyleEditor"
     99  );
    100  is(ui.editors.length, 2, "No other stylesheet was displayed");
    101 
    102  info("Check that highlighting works on the iframe document");
    103  await ui.selectStyleSheet(iframeStylesheetEditor.styleSheet);
    104  await waitFor(() => iframeStylesheetEditor.highlighter);
    105 
    106  const iframeHighlighterTestFront =
    107    await iframeStylesheetEditor._resource.targetFront.getFront(
    108      "highlighterTest"
    109    );
    110  iframeHighlighterTestFront.highlighter = iframeStylesheetEditor.highlighter;
    111 
    112  info("Expecting a node-highlighted event");
    113  onHighlighted = iframeStylesheetEditor.once("node-highlighted");
    114 
    115  info("Simulate a mousemove event on the h2 selector");
    116  // mousemove event listeners is set on editor.sourceEditor, which is not defined right away.
    117  await waitFor(() => !!iframeStylesheetEditor.sourceEditor);
    118  selectorEl = querySelectorCodeMirrorCssRuleSelectorToken(
    119    iframeStylesheetEditor
    120  );
    121  EventUtils.synthesizeMouseAtCenter(
    122    selectorEl,
    123    { type: "mousemove" },
    124    selectorEl.ownerDocument.defaultView
    125  );
    126  await onHighlighted;
    127 
    128  isVisible = await iframeHighlighterTestFront.isHighlighting();
    129  ok(isVisible, "The highlighter is shown");
    130  ok(
    131    await iframeHighlighterTestFront.isNodeRectHighlighted(
    132      await getElementNodeRectWithinTarget(["iframe", "h2"])
    133    ),
    134    "The highlighter's outline corresponds to the h2 node"
    135  );
    136 
    137  info("Simulate a mousemove event elsewhere in the editor");
    138  EventUtils.synthesizeMouseAtCenter(
    139    querySelectorCodeMirrorCssPropertyNameToken(iframeStylesheetEditor),
    140    { type: "mousemove" },
    141    selectorEl.ownerDocument.defaultView
    142  );
    143 
    144  await waitFor(async () => !topLevelStylesheetEditor.highlighter.isShown());
    145 
    146  isVisible = await iframeHighlighterTestFront.isHighlighting();
    147  is(isVisible, false, "The highlighter is now hidden");
    148 });
    149 
    150 function querySelectorCodeMirrorCssRuleSelectorToken(stylesheetEditor) {
    151  // CSS Rules selector (e.g. `h1`) are displayed in a .cm-tag span
    152  return querySelectorCodeMirror(stylesheetEditor, ".cm-tag");
    153 }
    154 
    155 function querySelectorCodeMirrorCssPropertyNameToken(stylesheetEditor) {
    156  // properties name (e.g. `color`) are displayed in a .cm-property span
    157  return querySelectorCodeMirror(stylesheetEditor, ".cm-property");
    158 }
    159 
    160 function querySelectorCodeMirror(stylesheetEditor, selector) {
    161  return stylesheetEditor.sourceEditor.codeMirror
    162    .getWrapperElement()
    163    .querySelector(selector);
    164 }
    165 
    166 /**
    167 * Return the bounds of the element matching the selector, relatively to the target bounds
    168 * (e.g. if Fission is enabled, it's related to the iframe bound, if Fission is disabled,
    169 * it's related to the top level document).
    170 *
    171 * @param {Array<string>} selectors: Arrays of CSS selectors from the root document to the node.
    172 *        The last CSS selector of the array is for the node in its frame doc.
    173 *        The before-last CSS selector is for the frame in its parent frame, etc...
    174 *        Ex: ["frame.first-frame", ..., "frame.last-frame", ".target-node"]
    175 * @returns {object} with left/top/width/height properties representing the node bounds
    176 */
    177 async function getElementNodeRectWithinTarget(selectors) {
    178  // Retrieve the browsing context in which the element is
    179  const inBCSelector = selectors.pop();
    180  const frameSelectors = selectors;
    181  const bc = frameSelectors.length
    182    ? await getBrowsingContextInFrames(
    183        gBrowser.selectedBrowser.browsingContext,
    184        frameSelectors
    185      )
    186    : gBrowser.selectedBrowser.browsingContext;
    187 
    188  // Get the element bounds within the Firefox window
    189  const elementBounds = await SpecialPowers.spawn(
    190    bc,
    191    [inBCSelector],
    192    _selector => {
    193      const el = content.document.querySelector(_selector);
    194      const { left, top, width, height } = el
    195        .getBoxQuadsFromWindowOrigin()[0]
    196        .getBounds();
    197      return { left, top, width, height };
    198    }
    199  );
    200 
    201  // Then we need to offset the element bounds from a frame bounds
    202  // The highlighter is only shown within the iframe bounds.  So we only need to
    203  // retrieve the element bounds within the iframe.
    204  const relativeBrowsingContext = bc;
    205  const relativeDocumentBounds = await SpecialPowers.spawn(
    206    relativeBrowsingContext,
    207    [],
    208    () =>
    209      content.document.documentElement
    210        .getBoxQuadsFromWindowOrigin()[0]
    211        .getBounds()
    212  );
    213 
    214  // Adjust the element bounds based on the relative document bounds
    215  elementBounds.left = elementBounds.left - relativeDocumentBounds.left;
    216  elementBounds.top = elementBounds.top - relativeDocumentBounds.top;
    217 
    218  return elementBounds;
    219 }