tor-browser

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

browser_rules_container-queries.js (9279B)


      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 the rule-view content is correct when the page defines container queries.
      7 const TEST_URI = `
      8  <!DOCTYPE html>
      9  <style type="text/css">
     10    body {
     11      container: mycontainer / size;
     12    }
     13 
     14    section {
     15      container: mycontainer / inline-size;
     16    }
     17 
     18    @container (width > 0px) {
     19      h1, [test-hint="nocontainername"]{
     20        outline-color: chartreuse;
     21      }
     22    }
     23 
     24    @container unknowncontainer (min-width: 2vw) {
     25      h1, [test-hint="unknowncontainer"] {
     26        border-color: salmon;
     27      }
     28    }
     29 
     30    @container mycontainer (1px < width < 10000px) {
     31      h1, [test-hint="container"] {
     32        color: tomato;
     33      }
     34 
     35      section, [test-hint="container-duplicate-name--body"] {
     36        color: gold;
     37      }
     38 
     39      div, [test-hint="container-duplicate-name--section"] {
     40        color: salmon;
     41      }
     42    }
     43  </style>
     44  <body id=myBody class="a-container test">
     45    <h1>Hello @container!</h1>
     46    <section>
     47      <div>
     48        <h2>You rock</h2>
     49      </div>
     50    </section>
     51  </body>
     52 `;
     53 
     54 add_task(async function () {
     55  await addTab(
     56    "https://example.com/document-builder.sjs?html=" +
     57      encodeURIComponent(TEST_URI)
     58  );
     59  const { inspector, view } = await openRuleView();
     60 
     61  await selectNode("h1", inspector);
     62  assertContainerQueryData(view, [
     63    { selector: "element", ancestorRulesData: null },
     64    {
     65      selector: `h1, [test-hint="container"]`,
     66      ancestorRulesData: ["@container mycontainer (1px < width < 10000px) {"],
     67    },
     68    {
     69      selector: `h1, [test-hint="nocontainername"]`,
     70      ancestorRulesData: ["@container (width > 0px) {"],
     71    },
     72  ]);
     73 
     74  info("Check that the query container tooltip works as expected");
     75  // Retrieve query containers sizes
     76  const { bodyInlineSize, bodyBlockSize, sectionInlineSize } =
     77    await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
     78      const body = content.document.body;
     79      const section = content.document.querySelector("section");
     80      return {
     81        bodyInlineSize: content.getComputedStyle(body).inlineSize,
     82        bodyBlockSize: content.getComputedStyle(body).blockSize,
     83        sectionInlineSize: content.getComputedStyle(section).inlineSize,
     84      };
     85    });
     86 
     87  await assertQueryContainerTooltip({
     88    inspector,
     89    view,
     90    ruleIndex: 1,
     91    expectedHeaderText: "<body#myBody.a-container.test>",
     92    expectedBodyText: [
     93      "container-type: size",
     94      `inline-size: ${bodyInlineSize}`,
     95      `block-size: ${bodyBlockSize}`,
     96    ],
     97  });
     98 
     99  info("Check that the 'jump to container' button works as expected");
    100  await assertJumpToContainerButton(inspector, view, 1, "body");
    101 
    102  info("Check that inherited rules display container query data as expected");
    103  await selectNode("h2", inspector);
    104 
    105  assertContainerQueryData(view, [
    106    { selector: "element", ancestorRulesData: null },
    107    {
    108      selector: `div, [test-hint="container-duplicate-name--section"]`,
    109      ancestorRulesData: ["@container mycontainer (1px < width < 10000px) {"],
    110    },
    111    {
    112      selector: `section, [test-hint="container-duplicate-name--body"]`,
    113      ancestorRulesData: ["@container mycontainer (1px < width < 10000px) {"],
    114    },
    115  ]);
    116 
    117  info(
    118    "Check that the query container tooltip works as expected for inherited rules as well"
    119  );
    120  await assertQueryContainerTooltip({
    121    inspector,
    122    view,
    123    ruleIndex: 1,
    124    expectedHeaderText: "<section>",
    125    expectedBodyText: [
    126      "container-type: inline-size",
    127      `inline-size: ${sectionInlineSize}`,
    128    ],
    129  });
    130  await assertQueryContainerTooltip({
    131    inspector,
    132    view,
    133    ruleIndex: 2,
    134    expectedHeaderText: "<body#myBody.a-container.test>",
    135    expectedBodyText: [
    136      "container-type: size",
    137      `inline-size: ${bodyInlineSize}`,
    138      `block-size: ${bodyBlockSize}`,
    139    ],
    140  });
    141 
    142  info(
    143    "Check that the 'jump to container' button works as expected for inherited rules"
    144  );
    145  await assertJumpToContainerButton(inspector, view, 1, "section");
    146 
    147  await selectNode("h2", inspector);
    148  await assertJumpToContainerButton(inspector, view, 2, "body");
    149 });
    150 
    151 function assertContainerQueryData(view, expectedRules) {
    152  const rulesInView = Array.from(
    153    view.element.querySelectorAll(".ruleview-rule")
    154  );
    155 
    156  is(
    157    rulesInView.length,
    158    expectedRules.length,
    159    "All expected rules are displayed"
    160  );
    161 
    162  for (let i = 0; i < expectedRules.length; i++) {
    163    const expectedRule = expectedRules[i];
    164    info(`Checking rule #${i}: ${expectedRule.selector}`);
    165 
    166    const selector = rulesInView[i].querySelector(
    167      ".ruleview-selectors-container"
    168    ).innerText;
    169    is(selector, expectedRule.selector, `Expected selector for ${selector}`);
    170 
    171    const ancestorDataEl = getRuleViewAncestorRulesDataElementByIndex(view, i);
    172 
    173    if (expectedRule.ancestorRulesData == null) {
    174      is(
    175        ancestorDataEl,
    176        null,
    177        `No ancestor rules data displayed for ${selector}`
    178      );
    179    } else {
    180      is(
    181        ancestorDataEl?.innerText,
    182        expectedRule.ancestorRulesData.join("\n"),
    183        `Expected ancestor rules data displayed for ${selector}`
    184      );
    185      Assert.notStrictEqual(
    186        ancestorDataEl.querySelector(".container-query .open-inspector"),
    187        null,
    188        "An icon is displayed to select the container in the markup view"
    189      );
    190    }
    191  }
    192 }
    193 
    194 async function assertJumpToContainerButton(
    195  inspector,
    196  view,
    197  ruleIndex,
    198  expectedSelectedNodeAfterClick
    199 ) {
    200  const selectContainerButton = getRuleViewAncestorRulesDataElementByIndex(
    201    view,
    202    ruleIndex
    203  ).querySelector(".open-inspector");
    204 
    205  // Ensure that the button can be targetted from EventUtils.
    206  selectContainerButton.scrollIntoView();
    207 
    208  const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
    209    getHighlighterTestHelpers(inspector);
    210 
    211  const onNodeHighlight = waitForHighlighterTypeShown(
    212    inspector.highlighters.TYPES.BOXMODEL
    213  );
    214  EventUtils.synthesizeMouseAtCenter(
    215    selectContainerButton,
    216    { type: "mouseover" },
    217    selectContainerButton.ownerDocument.defaultView
    218  );
    219  const { nodeFront: highlightedNodeFront } = await onNodeHighlight;
    220  is(
    221    highlightedNodeFront.displayName,
    222    expectedSelectedNodeAfterClick,
    223    "The correct node was highlighted"
    224  );
    225 
    226  const onceNewNodeFront = inspector.selection.once("new-node-front");
    227  const onNodeUnhighlight = waitForHighlighterTypeHidden(
    228    inspector.highlighters.TYPES.BOXMODEL
    229  );
    230 
    231  EventUtils.synthesizeMouseAtCenter(
    232    selectContainerButton,
    233    {},
    234    selectContainerButton.ownerDocument.defaultView
    235  );
    236 
    237  const nodeFront = await onceNewNodeFront;
    238  is(
    239    nodeFront.displayName,
    240    expectedSelectedNodeAfterClick,
    241    "The correct node has been selected"
    242  );
    243 
    244  await onNodeUnhighlight;
    245  ok(true, "Highlighter was hidden when clicking on icon");
    246 
    247  // Move mouse so it does stay in a position where it could hover something impacting
    248  // the test.
    249  EventUtils.synthesizeMouse(
    250    selectContainerButton.closest("body"),
    251    0,
    252    0,
    253    { type: "mouseover" },
    254    selectContainerButton.ownerDocument.defaultView
    255  );
    256 }
    257 
    258 async function assertQueryContainerTooltip({
    259  inspector,
    260  view,
    261  ruleIndex,
    262  expectedHeaderText,
    263  expectedBodyText,
    264 }) {
    265  const parent = getRuleViewAncestorRulesDataElementByIndex(view, ruleIndex);
    266  const highlighterTriggerEl = parent.querySelector(".open-inspector");
    267  const tooltipTriggerEl = parent.querySelector(".container-query-declaration");
    268 
    269  // Ensure that the element can be targetted from EventUtils.
    270  parent.scrollIntoView();
    271 
    272  const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } =
    273    getHighlighterTestHelpers(inspector);
    274 
    275  const onNodeHighlight = waitForHighlighterTypeShown(
    276    inspector.highlighters.TYPES.BOXMODEL
    277  );
    278 
    279  const tooltip = view.tooltips.getTooltip("interactiveTooltip");
    280 
    281  info("synthesizing mousemove on open-inspector icon: " + tooltip.isVisible());
    282  EventUtils.synthesizeMouseAtCenter(
    283    highlighterTriggerEl,
    284    { type: "mousemove" },
    285    highlighterTriggerEl.ownerDocument.defaultView
    286  );
    287 
    288  await onNodeHighlight;
    289  info("node was highlighted");
    290 
    291  const onNodeUnhighlight = waitForHighlighterTypeHidden(
    292    inspector.highlighters.TYPES.BOXMODEL
    293  );
    294 
    295  const onTooltipReady = tooltip.once("shown");
    296 
    297  info("synthesizing mousemove on tooltip el: " + tooltip.isVisible());
    298  EventUtils.synthesizeMouseAtCenter(
    299    tooltipTriggerEl,
    300    { type: "mousemove" },
    301    tooltipTriggerEl.ownerDocument.defaultView
    302  );
    303 
    304  await onTooltipReady;
    305  info("tooltip was shown");
    306 
    307  await onNodeUnhighlight;
    308  info("highlighter was hidden");
    309 
    310  is(
    311    tooltip.panel.querySelector("header").textContent,
    312    expectedHeaderText,
    313    "Tooltip has expected header content"
    314  );
    315 
    316  const lis = Array.from(tooltip.panel.querySelectorAll("li")).map(
    317    li => li.textContent
    318  );
    319  Assert.deepEqual(lis, expectedBodyText, "Tooltip has expected body items");
    320 
    321  info("Hide the tooltip");
    322  const onHidden = tooltip.once("hidden");
    323 
    324  // Move the mouse elsewhere to hide the tooltip
    325  EventUtils.synthesizeMouse(
    326    tooltipTriggerEl.ownerDocument.body,
    327    1,
    328    1,
    329    { type: "mousemove" },
    330    tooltipTriggerEl.ownerDocument.defaultView
    331  );
    332  await onHidden;
    333 }