tor-browser

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

browser_markup_mutation_01.js (13627B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Tests that various mutations to the dom update the markup view correctly.
      7 
      8 const TEST_URL = URL_ROOT + "doc_markup_mutation.html";
      9 
     10 // Mutation tests. Each entry in the array has the following properties:
     11 // - desc: for logging only
     12 // - numMutations: how many mutations are expected to come happen due to the
     13 //   test case.  Defaults to 1 if not set.
     14 // - test: a function supposed to mutate the DOM
     15 // - check: a function supposed to test that the mutation was handled
     16 const TEST_DATA = [
     17  {
     18    desc: "Adding an attribute",
     19    async test() {
     20      await setContentPageElementAttribute("#node1", "newattr", "newattrval");
     21    },
     22    async check(inspector) {
     23      const { editor } = await getContainerForSelector("#node1", inspector);
     24      ok(
     25        [...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
     26          return (
     27            attr.textContent.trim() === 'newattr="newattrval"' &&
     28            attr.dataset.value === "newattrval" &&
     29            attr.dataset.attr === "newattr"
     30          );
     31        }),
     32        "newattr attribute found"
     33      );
     34    },
     35  },
     36  {
     37    desc: "Removing an attribute",
     38    async test() {
     39      await removeContentPageElementAttribute("#node1", "newattr");
     40    },
     41    async check(inspector) {
     42      const { editor } = await getContainerForSelector("#node1", inspector);
     43      ok(
     44        ![...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
     45          return attr.textContent.trim() === 'newattr="newattrval"';
     46        }),
     47        "newattr attribute removed"
     48      );
     49    },
     50  },
     51  {
     52    desc: "Re-adding an attribute",
     53    async test() {
     54      await setContentPageElementAttribute("#node1", "newattr", "newattrval");
     55    },
     56    async check(inspector) {
     57      const { editor } = await getContainerForSelector("#node1", inspector);
     58      ok(
     59        [...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
     60          return (
     61            attr.textContent.trim() === 'newattr="newattrval"' &&
     62            attr.dataset.value === "newattrval" &&
     63            attr.dataset.attr === "newattr"
     64          );
     65        }),
     66        "newattr attribute found"
     67      );
     68    },
     69  },
     70  {
     71    desc: "Changing an attribute",
     72    async test() {
     73      await setContentPageElementAttribute(
     74        "#node1",
     75        "newattr",
     76        "newattrchanged"
     77      );
     78    },
     79    async check(inspector) {
     80      const { editor } = await getContainerForSelector("#node1", inspector);
     81      ok(
     82        [...editor.attrList.querySelectorAll(".attreditor")].some(attr => {
     83          return (
     84            attr.textContent.trim() === 'newattr="newattrchanged"' &&
     85            attr.dataset.value === "newattrchanged" &&
     86            attr.dataset.attr === "newattr"
     87          );
     88        }),
     89        "newattr attribute found"
     90      );
     91    },
     92  },
     93  {
     94    desc: "Adding another attribute does not rerender unchanged attributes",
     95    async test(inspector) {
     96      const { editor } = await getContainerForSelector("#node1", inspector);
     97 
     98      // This test checks the impact on the markup-view nodes after setting attributes on
     99      // content nodes.
    100      info("Expect attribute-container for 'new-attr' from the previous test");
    101      const attributeContainer = editor.attrList.querySelector(
    102        "[data-attr=newattr]"
    103      );
    104      ok(attributeContainer, "attribute-container for 'newattr' found");
    105 
    106      info("Set a flag on the attribute-container to check after the mutation");
    107      attributeContainer.beforeMutationFlag = true;
    108 
    109      info(
    110        "Add the attribute 'otherattr' on the content node to trigger the mutation"
    111      );
    112      await setContentPageElementAttribute("#node1", "otherattr", "othervalue");
    113    },
    114    async check(inspector) {
    115      const { editor } = await getContainerForSelector("#node1", inspector);
    116 
    117      info(
    118        "Check the attribute-container for the new attribute mutation was created"
    119      );
    120      const otherAttrContainer = editor.attrList.querySelector(
    121        "[data-attr=otherattr]"
    122      );
    123      ok(otherAttrContainer, "attribute-container for 'otherattr' found");
    124 
    125      info(
    126        "Check the attribute-container for 'new-attr' is the same node as earlier."
    127      );
    128      const newAttrContainer = editor.attrList.querySelector(
    129        "[data-attr=newattr]"
    130      );
    131      ok(newAttrContainer, "attribute-container for 'newattr' found");
    132      ok(
    133        newAttrContainer.beforeMutationFlag,
    134        "attribute-container same as earlier"
    135      );
    136    },
    137  },
    138  {
    139    desc: "Adding ::after element",
    140    numMutations: 2,
    141    async test() {
    142      await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    143        const node1 = content.document.querySelector("#node1");
    144        node1.classList.add("pseudo");
    145      });
    146    },
    147    async check(inspector) {
    148      const { children } = await getContainerForSelector("#node1", inspector);
    149      is(
    150        children.childNodes.length,
    151        2,
    152        "Node1 now has 2 children (text child and ::after"
    153      );
    154    },
    155  },
    156  {
    157    desc: "Removing ::after element",
    158    numMutations: 2,
    159    async test() {
    160      await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    161        const node1 = content.document.querySelector("#node1");
    162        node1.classList.remove("pseudo");
    163      });
    164    },
    165    async check(inspector) {
    166      const container = await getContainerForSelector("#node1", inspector);
    167      ok(container.inlineTextChild, "Has single text child.");
    168    },
    169  },
    170  {
    171    desc: "Updating the text-content",
    172    async test() {
    173      await setContentPageElementProperty("#node1", "textContent", "newtext");
    174    },
    175    async check(inspector) {
    176      const container = await getContainerForSelector("#node1", inspector);
    177      ok(container.inlineTextChild, "Has single text child.");
    178      ok(!container.canExpand, "Can't expand container with inlineTextChild.");
    179      ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
    180      is(
    181        container.editor.elt.querySelector(".text").textContent.trim(),
    182        "newtext",
    183        "Single text child editor updated."
    184      );
    185    },
    186  },
    187  {
    188    desc: "Adding a second text child",
    189    async test() {
    190      await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    191        const node1 = content.document.querySelector("#node1");
    192        const newText = node1.ownerDocument.createTextNode("more");
    193        node1.appendChild(newText);
    194      });
    195    },
    196    async check(inspector) {
    197      const container = await getContainerForSelector("#node1", inspector);
    198      ok(!container.inlineTextChild, "Does not have single text child.");
    199      ok(container.canExpand, "Can expand container with child nodes.");
    200      Assert.equal(
    201        container.editor.elt.querySelector(".text"),
    202        null,
    203        "Single text child editor removed."
    204      );
    205    },
    206  },
    207  {
    208    desc: "Go from 2 to 1 text child",
    209    async test() {
    210      await setContentPageElementProperty("#node1", "textContent", "newtext");
    211    },
    212    async check(inspector) {
    213      const container = await getContainerForSelector("#node1", inspector);
    214      ok(container.inlineTextChild, "Has single text child.");
    215      ok(!container.canExpand, "Can't expand container with inlineTextChild.");
    216      ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
    217      is(
    218        container.editor.elt.querySelector(".text").textContent.trim(),
    219        "newtext",
    220        "Single text child editor updated."
    221      );
    222    },
    223  },
    224  {
    225    desc: "Removing an only text child",
    226    async test() {
    227      await setContentPageElementProperty("#node1", "innerHTML", "");
    228    },
    229    async check(inspector) {
    230      const container = await getContainerForSelector("#node1", inspector);
    231      ok(!container.inlineTextChild, "Does not have single text child.");
    232      ok(!container.canExpand, "Can't expand empty container.");
    233      Assert.equal(
    234        container.editor.elt.querySelector(".text"),
    235        null,
    236        "Single text child editor removed."
    237      );
    238    },
    239  },
    240  {
    241    desc: "Go from 0 to 1 text child",
    242    async test() {
    243      await setContentPageElementProperty("#node1", "textContent", "newtext");
    244    },
    245    async check(inspector) {
    246      const container = await getContainerForSelector("#node1", inspector);
    247      ok(container.inlineTextChild, "Has single text child.");
    248      ok(!container.canExpand, "Can't expand container with inlineTextChild.");
    249      ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
    250      is(
    251        container.editor.elt.querySelector(".text").textContent.trim(),
    252        "newtext",
    253        "Single text child editor updated."
    254      );
    255    },
    256  },
    257 
    258  {
    259    desc: "Updating the innerHTML",
    260    async test() {
    261      await setContentPageElementProperty(
    262        "#node2",
    263        "innerHTML",
    264        "<div><span>foo</span></div>"
    265      );
    266    },
    267    async check(inspector) {
    268      const container = await getContainerForSelector("#node2", inspector);
    269 
    270      const openTags = container.children.querySelectorAll(".open .tag");
    271      is(openTags.length, 2, "There are 2 tags in node2");
    272      is(openTags[0].textContent.trim(), "div", "The first tag is a div");
    273      is(openTags[1].textContent.trim(), "span", "The second tag is a span");
    274 
    275      is(
    276        container.children.querySelector(".text").textContent.trim(),
    277        "foo",
    278        "The span's textcontent is correct"
    279      );
    280    },
    281  },
    282  {
    283    desc: "Removing child nodes",
    284    async test() {
    285      await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    286        const node4 = content.document.querySelector("#node4");
    287        node4.replaceChildren();
    288      });
    289    },
    290    async check(inspector) {
    291      const { children } = await getContainerForSelector("#node4", inspector);
    292      is(children.innerHTML, "", "Children have been removed");
    293    },
    294  },
    295  {
    296    desc: "Appending a child to a different parent",
    297    async test() {
    298      await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    299        const node17 = content.document.querySelector("#node17");
    300        const node2 = content.document.querySelector("#node2");
    301        node2.appendChild(node17);
    302      });
    303    },
    304    async check(inspector) {
    305      const { children } = await getContainerForSelector("#node16", inspector);
    306      is(
    307        children.innerHTML,
    308        "",
    309        "Node17 has been removed from its node16 parent"
    310      );
    311 
    312      const container = await getContainerForSelector("#node2", inspector);
    313      const openTags = container.children.querySelectorAll(".open .tag");
    314      is(openTags.length, 3, "There are now 3 tags in node2");
    315      is(openTags[2].textContent.trim(), "p", "The third tag is node17");
    316    },
    317  },
    318  {
    319    desc: "Swapping a parent and child element, putting them in the same tree",
    320    // body
    321    //  node1
    322    //  node18
    323    //    node19
    324    //      node20
    325    //        node21
    326    // will become:
    327    // body
    328    //   node1
    329    //     node20
    330    //      node21
    331    //      node18
    332    //        node19
    333    async test() {
    334      await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    335        const node18 = content.document.querySelector("#node18");
    336        const node20 = content.document.querySelector("#node20");
    337        const node1 = content.document.querySelector("#node1");
    338        node1.appendChild(node20);
    339        node20.appendChild(node18);
    340      });
    341    },
    342    async check(inspector) {
    343      await inspector.markup.expandAll();
    344 
    345      const { children } = await getContainerForSelector("#node1", inspector);
    346      is(
    347        children.childNodes.length,
    348        2,
    349        "Node1 now has 2 children (textnode and node20)"
    350      );
    351 
    352      const node20 = children.childNodes[1];
    353      const node20Children = node20.container.children;
    354      is(
    355        node20Children.childNodes.length,
    356        2,
    357        "Node20 has 2 children (21 and 18)"
    358      );
    359 
    360      const node21 = node20Children.childNodes[0];
    361      is(
    362        node21.container.editor.elt.querySelector(".text").textContent.trim(),
    363        "line21",
    364        "Node21 has a single text child"
    365      );
    366 
    367      const node18 = node20Children.childNodes[1];
    368      is(
    369        node18
    370          .querySelector(".open .attreditor .attr-value")
    371          .textContent.trim(),
    372        "node18",
    373        "Node20's second child is indeed node18"
    374      );
    375    },
    376  },
    377 ];
    378 
    379 add_task(async function () {
    380  const { inspector } = await openInspectorForURL(TEST_URL);
    381 
    382  info("Expanding all markup-view nodes");
    383  await inspector.markup.expandAll();
    384 
    385  for (let { desc, test, check, numMutations } of TEST_DATA) {
    386    info("Starting test: " + desc);
    387 
    388    numMutations = numMutations || 1;
    389 
    390    info("Executing the test markup mutation");
    391 
    392    // If a test expects more than one mutation it may come through in a single
    393    // event or possibly in multiples.
    394    let seenMutations = 0;
    395    const promise = new Promise(resolve => {
    396      inspector.on("markupmutation", function onmutation(mutations) {
    397        seenMutations += mutations.length;
    398        info(
    399          "Receieved " +
    400            seenMutations +
    401            " mutations, expecting at least " +
    402            numMutations
    403        );
    404        if (seenMutations >= numMutations) {
    405          inspector.off("markupmutation", onmutation);
    406          resolve();
    407        }
    408      });
    409    });
    410    await test(inspector);
    411    await promise;
    412 
    413    info("Expanding all markup-view nodes to make sure new nodes are imported");
    414    await inspector.markup.expandAll();
    415 
    416    info("Checking the markup-view content");
    417    await check(inspector);
    418  }
    419 });