tor-browser

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

browser_inspector-mutations-childlist.js (8914B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 Services.scriptloader.loadSubScript(
      7  "chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js",
      8  this
      9 );
     10 
     11 function loadSelector(walker, selector) {
     12  return walker.querySelectorAll(walker.rootNode, selector).then(nodeList => {
     13    return nodeList.items();
     14  });
     15 }
     16 
     17 function loadSelectors(walker, selectors) {
     18  return Promise.all(Array.from(selectors, sel => loadSelector(walker, sel)));
     19 }
     20 
     21 function doMoves(movesArg) {
     22  return SpecialPowers.spawn(
     23    gBrowser.selectedBrowser,
     24    [movesArg],
     25    function (moves) {
     26      function setParent(nodeSelector, newParentSelector) {
     27        const node = content.document.querySelector(nodeSelector);
     28        if (newParentSelector) {
     29          const newParent = content.document.querySelector(newParentSelector);
     30          newParent.appendChild(node);
     31        } else {
     32          node.remove();
     33        }
     34      }
     35      for (const move of moves) {
     36        setParent(move[0], move[1]);
     37      }
     38    }
     39  );
     40 }
     41 
     42 /**
     43 * Test a set of tree rearrangements and make sure they cause the expected changes.
     44 */
     45 
     46 var gDummySerial = 0;
     47 
     48 function mutationTest(testSpec) {
     49  return async function () {
     50    const { walker } = await initInspectorFront(
     51      MAIN_DOMAIN + "inspector-traversal-data.html"
     52    );
     53    await loadSelectors(walker, testSpec.load || ["html"]);
     54    walker.autoCleanup = !!testSpec.autoCleanup;
     55    if (testSpec.preCheck) {
     56      testSpec.preCheck();
     57    }
     58    const onMutations = walker.once("mutations");
     59 
     60    await doMoves(testSpec.moves || []);
     61 
     62    // Some of these moves will trigger no mutation events,
     63    // so do a dummy change to the root node to trigger
     64    // a mutation event anyway.
     65    await SpecialPowers.spawn(
     66      gBrowser.selectedBrowser,
     67      [[gDummySerial++]],
     68      function (serial) {
     69        content.document.documentElement.setAttribute("data-dummy", serial);
     70      }
     71    );
     72 
     73    let mutations = await onMutations;
     74 
     75    // Filter out our dummy mutation.
     76    mutations = mutations.filter(change => {
     77      if (change.type == "attributes" && change.attributeName == "data-dummy") {
     78        return false;
     79      }
     80      return true;
     81    });
     82    await assertOwnershipTrees(walker);
     83    if (testSpec.postCheck) {
     84      testSpec.postCheck(walker, mutations);
     85    }
     86  };
     87 }
     88 
     89 // Verify that our dummy mutation works.
     90 add_task(
     91  mutationTest({
     92    autoCleanup: false,
     93    postCheck(walker, mutations) {
     94      is(mutations.length, 0, "Dummy mutation is filtered out.");
     95    },
     96  })
     97 );
     98 
     99 // Test a simple move to a different location in the sibling list for the same
    100 // parent.
    101 add_task(
    102  mutationTest({
    103    autoCleanup: false,
    104    load: ["#longlist div"],
    105    moves: [["#a", "#longlist"]],
    106    postCheck(walker, mutations) {
    107      const remove = mutations[0];
    108      is(remove.type, "childList", "First mutation should be a childList.");
    109      ok(!!remove.removed.length, "First mutation should be a removal.");
    110      const add = mutations[1];
    111      is(
    112        add.type,
    113        "childList",
    114        "Second mutation should be a childList removal."
    115      );
    116      ok(!!add.added.length, "Second mutation should be an addition.");
    117      const a = add.added[0];
    118      is(a.id, "a", "Added node should be #a");
    119      is(a.parentNode(), remove.target, "Should still be a child of longlist.");
    120      is(
    121        remove.target,
    122        add.target,
    123        "First and second mutations should be against the same node."
    124      );
    125    },
    126  })
    127 );
    128 
    129 // Test a move to another location that is within our ownership tree.
    130 add_task(
    131  mutationTest({
    132    autoCleanup: false,
    133    load: ["#longlist div", "#longlist-sibling"],
    134    moves: [["#a", "#longlist-sibling"]],
    135    postCheck(walker, mutations) {
    136      const remove = mutations[0];
    137      is(remove.type, "childList", "First mutation should be a childList.");
    138      ok(!!remove.removed.length, "First mutation should be a removal.");
    139      const add = mutations[1];
    140      is(
    141        add.type,
    142        "childList",
    143        "Second mutation should be a childList removal."
    144      );
    145      ok(!!add.added.length, "Second mutation should be an addition.");
    146      const a = add.added[0];
    147      is(a.id, "a", "Added node should be #a");
    148      is(a.parentNode(), add.target, "Should still be a child of longlist.");
    149      is(
    150        add.target.id,
    151        "longlist-sibling",
    152        "long-sibling should be the target."
    153      );
    154    },
    155  })
    156 );
    157 
    158 // Move an unseen node with a seen parent into our ownership tree - should generate a
    159 // childList pair with no adds or removes.
    160 add_task(
    161  mutationTest({
    162    autoCleanup: false,
    163    load: ["#longlist"],
    164    moves: [["#longlist-sibling", "#longlist"]],
    165    postCheck(walker, mutations) {
    166      is(mutations.length, 2, "Should generate two mutations");
    167      is(mutations[0].type, "childList", "Should be childList mutations.");
    168      is(mutations[0].added.length, 0, "Should have no adds.");
    169      is(mutations[0].removed.length, 0, "Should have no removes.");
    170      is(mutations[1].type, "childList", "Should be childList mutations.");
    171      is(mutations[1].added.length, 0, "Should have no adds.");
    172      is(mutations[1].removed.length, 0, "Should have no removes.");
    173    },
    174  })
    175 );
    176 
    177 // Move an unseen node with an unseen parent into our ownership tree.  Should only
    178 // generate one childList mutation with no adds or removes.
    179 add_task(
    180  mutationTest({
    181    autoCleanup: false,
    182    load: ["#longlist div"],
    183    moves: [["#longlist-sibling-firstchild", "#longlist"]],
    184    postCheck(walker, mutations) {
    185      is(mutations.length, 1, "Should generate two mutations");
    186      is(mutations[0].type, "childList", "Should be childList mutations.");
    187      is(mutations[0].added.length, 0, "Should have no adds.");
    188      is(mutations[0].removed.length, 0, "Should have no removes.");
    189    },
    190  })
    191 );
    192 
    193 // Move a node between unseen nodes, should generate no mutations.
    194 add_task(
    195  mutationTest({
    196    autoCleanup: false,
    197    load: ["html"],
    198    moves: [["#longlist-sibling", "#longlist"]],
    199    postCheck(walker, mutations) {
    200      is(mutations.length, 0, "Should generate no mutations.");
    201    },
    202  })
    203 );
    204 
    205 // Orphan a node and don't clean it up
    206 add_task(
    207  mutationTest({
    208    autoCleanup: false,
    209    load: ["#longlist div"],
    210    moves: [["#longlist", null]],
    211    postCheck(walker, mutations) {
    212      is(mutations.length, 1, "Should generate one mutation.");
    213      const change = mutations[0];
    214      is(change.type, "childList", "Should be a childList.");
    215      is(change.removed.length, 1, "Should have removed a child.");
    216      const ownership = clientOwnershipTree(walker);
    217      is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
    218      is(
    219        ownershipTreeSize(ownership.orphaned[0]),
    220        1 + 26 + 26,
    221        "Should have orphaned longlist, and 26 children, and 26 singleTextChilds"
    222      );
    223    },
    224  })
    225 );
    226 
    227 // Orphan a node, and do clean it up.
    228 add_task(
    229  mutationTest({
    230    autoCleanup: true,
    231    load: ["#longlist div"],
    232    moves: [["#longlist", null]],
    233    postCheck(walker, mutations) {
    234      is(mutations.length, 1, "Should generate one mutation.");
    235      const change = mutations[0];
    236      is(change.type, "childList", "Should be a childList.");
    237      is(change.removed.length, 1, "Should have removed a child.");
    238      const ownership = clientOwnershipTree(walker);
    239      is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
    240    },
    241  })
    242 );
    243 
    244 // Orphan a node by moving it into the tree but out of our visible subtree.
    245 add_task(
    246  mutationTest({
    247    autoCleanup: false,
    248    load: ["#longlist div"],
    249    moves: [["#longlist", "#longlist-sibling"]],
    250    postCheck(walker, mutations) {
    251      is(mutations.length, 1, "Should generate one mutation.");
    252      const change = mutations[0];
    253      is(change.type, "childList", "Should be a childList.");
    254      is(change.removed.length, 1, "Should have removed a child.");
    255      const ownership = clientOwnershipTree(walker);
    256      is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
    257      is(
    258        ownershipTreeSize(ownership.orphaned[0]),
    259        1 + 26 + 26,
    260        "Should have orphaned longlist, 26 children, and 26 singleTextChilds."
    261      );
    262    },
    263  })
    264 );
    265 
    266 // Orphan a node by moving it into the tree but out of our visible subtree,
    267 // and clean it up.
    268 add_task(
    269  mutationTest({
    270    autoCleanup: true,
    271    load: ["#longlist div"],
    272    moves: [["#longlist", "#longlist-sibling"]],
    273    postCheck(walker, mutations) {
    274      is(mutations.length, 1, "Should generate one mutation.");
    275      const change = mutations[0];
    276      is(change.type, "childList", "Should be a childList.");
    277      is(change.removed.length, 1, "Should have removed a child.");
    278      const ownership = clientOwnershipTree(walker);
    279      is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
    280    },
    281  })
    282 );