tor-browser

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

browser_inspector_breadcrumbs_mutations.js (8265B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 "use strict";
      4 
      5 // Test that the breadcrumbs widget refreshes correctly when there are markup
      6 // mutations (and that it doesn't refresh when those mutations don't change its
      7 // output).
      8 
      9 const TEST_URI = URL_ROOT + "doc_inspector_breadcrumbs.html";
     10 
     11 // Each item in the TEST_DATA array is a test case that should contain the
     12 // following properties:
     13 // - desc {String} A description of this test case (will be logged).
     14 // - setup {Function*} A generator function (can yield promises) that sets up
     15 //   the test case. Useful for selecting a node before starting the test.
     16 // - run {Function*} A generator function (can yield promises) that runs the
     17 //   actual test case, i.e, mutates the content DOM to cause the breadcrumbs
     18 //   to refresh, or not.
     19 // - shouldRefresh {Boolean} Once the `run` function has completed, and the test
     20 //   has detected that the page has changed, this boolean instructs the test to
     21 //   verify if the breadcrumbs has refreshed or not.
     22 // - output {Array} A list of strings for the text that should be found in each
     23 //   button after the test has run.
     24 const TEST_DATA = [
     25  {
     26    desc: "Adding a child at the end of the chain shouldn't change anything",
     27    async setup(inspector) {
     28      await selectNode("#i1111", inspector);
     29    },
     30    async run({ walker, selection }) {
     31      await walker.setInnerHTML(selection.nodeFront, "<b>test</b>");
     32    },
     33    shouldRefresh: false,
     34    output: ["html", "body", "article#i1", "div#i11", "div#i111", "div#i1111"],
     35  },
     36  {
     37    desc: "Updating an ID to an displayed element should refresh",
     38    setup() {},
     39    async run({ walker }) {
     40      const node = await walker.querySelector(walker.rootNode, "#i1");
     41      await node.modifyAttributes([
     42        {
     43          attributeName: "id",
     44          newValue: "i1-changed",
     45        },
     46      ]);
     47    },
     48    shouldRefresh: true,
     49    output: [
     50      "html",
     51      "body",
     52      "article#i1-changed",
     53      "div#i11",
     54      "div#i111",
     55      "div#i1111",
     56    ],
     57  },
     58  {
     59    desc: "Updating an class to a displayed element should refresh",
     60    setup() {},
     61    async run({ walker }) {
     62      const node = await walker.querySelector(walker.rootNode, "body");
     63      await node.modifyAttributes([
     64        {
     65          attributeName: "class",
     66          newValue: "test-class",
     67        },
     68      ]);
     69    },
     70    shouldRefresh: true,
     71    output: [
     72      "html",
     73      "body.test-class",
     74      "article#i1-changed",
     75      "div#i11",
     76      "div#i111",
     77      "div#i1111",
     78    ],
     79  },
     80  {
     81    desc:
     82      "Updating a non id/class attribute to a displayed element should not " +
     83      "refresh",
     84    setup() {},
     85    async run({ walker }) {
     86      const node = await walker.querySelector(walker.rootNode, "#i11");
     87      await node.modifyAttributes([
     88        {
     89          attributeName: "name",
     90          newValue: "value",
     91        },
     92      ]);
     93    },
     94    shouldRefresh: false,
     95    output: [
     96      "html",
     97      "body.test-class",
     98      "article#i1-changed",
     99      "div#i11",
    100      "div#i111",
    101      "div#i1111",
    102    ],
    103  },
    104  {
    105    desc: "Moving a child in an element that's not displayed should not refresh",
    106    setup() {},
    107    async run({ walker }) {
    108      // Re-append #i1211 as a last child of #i2.
    109      const parent = await walker.querySelector(walker.rootNode, "#i2");
    110      const child = await walker.querySelector(walker.rootNode, "#i211");
    111      await walker.insertBefore(child, parent);
    112    },
    113    shouldRefresh: false,
    114    output: [
    115      "html",
    116      "body.test-class",
    117      "article#i1-changed",
    118      "div#i11",
    119      "div#i111",
    120      "div#i1111",
    121    ],
    122  },
    123  {
    124    desc: "Moving an undisplayed child in a displayed element should not refresh",
    125    setup() {},
    126    async run({ walker }) {
    127      // Re-append #i2 in body (move it to the end).
    128      const parent = await walker.querySelector(walker.rootNode, "body");
    129      const child = await walker.querySelector(walker.rootNode, "#i2");
    130      await walker.insertBefore(child, parent);
    131    },
    132    shouldRefresh: false,
    133    output: [
    134      "html",
    135      "body.test-class",
    136      "article#i1-changed",
    137      "div#i11",
    138      "div#i111",
    139      "div#i1111",
    140    ],
    141  },
    142  {
    143    desc:
    144      "Updating attributes on an element that's not displayed should not " +
    145      "refresh",
    146    setup() {},
    147    async run({ walker }) {
    148      const node = await walker.querySelector(walker.rootNode, "#i2");
    149      await node.modifyAttributes([
    150        {
    151          attributeName: "id",
    152          newValue: "i2-changed",
    153        },
    154        {
    155          attributeName: "class",
    156          newValue: "test-class",
    157        },
    158      ]);
    159    },
    160    shouldRefresh: false,
    161    output: [
    162      "html",
    163      "body.test-class",
    164      "article#i1-changed",
    165      "div#i11",
    166      "div#i111",
    167      "div#i1111",
    168    ],
    169  },
    170  {
    171    desc: "Removing the currently selected node should refresh",
    172    async setup(inspector) {
    173      await selectNode("#i2-changed", inspector);
    174    },
    175    async run({ walker, selection }) {
    176      await walker.removeNode(selection.nodeFront);
    177    },
    178    shouldRefresh: true,
    179    output: ["html", "body.test-class"],
    180  },
    181  {
    182    desc: "Changing the class of the currently selected node should refresh",
    183    setup() {},
    184    async run({ selection }) {
    185      await selection.nodeFront.modifyAttributes([
    186        {
    187          attributeName: "class",
    188          newValue: "test-class-changed",
    189        },
    190      ]);
    191    },
    192    shouldRefresh: true,
    193    output: ["html", "body.test-class-changed"],
    194  },
    195  {
    196    desc: "Changing the id of the currently selected node should refresh",
    197    setup() {},
    198    async run({ selection }) {
    199      await selection.nodeFront.modifyAttributes([
    200        {
    201          attributeName: "id",
    202          newValue: "new-id",
    203        },
    204      ]);
    205    },
    206    shouldRefresh: true,
    207    output: ["html", "body#new-id.test-class-changed"],
    208  },
    209 ];
    210 
    211 add_task(async function () {
    212  const { inspector } = await openInspectorForURL(TEST_URI);
    213  const breadcrumbs = inspector.panelDoc.getElementById(
    214    "inspector-breadcrumbs"
    215  );
    216  const container = breadcrumbs.querySelector(".html-arrowscrollbox-inner");
    217  const win = container.ownerDocument.defaultView;
    218 
    219  for (const { desc, setup, run, shouldRefresh, output } of TEST_DATA) {
    220    info("Running test case: " + desc);
    221 
    222    info(
    223      "Listen to markupmutation events from the inspector to know when a " +
    224        "test case has completed"
    225    );
    226    const onContentMutation = inspector.once("markupmutation");
    227 
    228    info("Running setup");
    229    await setup(inspector);
    230 
    231    info("Listen to mutations on the breadcrumbs container");
    232    let hasBreadcrumbsMutated = false;
    233    const observer = new win.MutationObserver(mutations => {
    234      // Only consider childList changes or tooltiptext/checked attributes
    235      // changes. The rest may be mutations caused by the overflowing arrowbox.
    236      for (const { type, attributeName } of mutations) {
    237        const isChildList = type === "childList";
    238        const isAttributes =
    239          type === "attributes" &&
    240          (attributeName === "checked" || attributeName === "tooltiptext");
    241        if (isChildList || isAttributes) {
    242          hasBreadcrumbsMutated = true;
    243          break;
    244        }
    245      }
    246    });
    247    observer.observe(container, {
    248      attributes: true,
    249      childList: true,
    250      subtree: true,
    251    });
    252 
    253    info("Running the test case");
    254    await run(inspector);
    255 
    256    info("Wait until the page has mutated");
    257    await onContentMutation;
    258 
    259    if (shouldRefresh) {
    260      info("The breadcrumbs is expected to refresh, so wait for it");
    261      await inspector.once("inspector-updated");
    262    } else {
    263      ok(
    264        !inspector._updateProgress,
    265        "The breadcrumbs widget is not currently updating"
    266      );
    267    }
    268 
    269    is(shouldRefresh, hasBreadcrumbsMutated, "Has the breadcrumbs refreshed?");
    270    observer.disconnect();
    271 
    272    info("Check the output of the breadcrumbs widget");
    273    is(container.childNodes.length, output.length, "Correct number of buttons");
    274    for (let i = 0; i < container.childNodes.length; i++) {
    275      is(
    276        output[i],
    277        container.childNodes[i].textContent,
    278        "Text content for button " + i + " is correct"
    279      );
    280    }
    281  }
    282 });