diff options
Diffstat (limited to 'devtools/server/tests/browser/browser_inspector-mutations-childlist.js')
-rw-r--r-- | devtools/server/tests/browser/browser_inspector-mutations-childlist.js | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/devtools/server/tests/browser/browser_inspector-mutations-childlist.js b/devtools/server/tests/browser/browser_inspector-mutations-childlist.js new file mode 100644 index 0000000000..6818c9c8dc --- /dev/null +++ b/devtools/server/tests/browser/browser_inspector-mutations-childlist.js @@ -0,0 +1,282 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", + this +); + +function loadSelector(walker, selector) { + return walker.querySelectorAll(walker.rootNode, selector).then(nodeList => { + return nodeList.items(); + }); +} + +function loadSelectors(walker, selectors) { + return Promise.all(Array.from(selectors, sel => loadSelector(walker, sel))); +} + +function doMoves(movesArg) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [movesArg], + function (moves) { + function setParent(nodeSelector, newParentSelector) { + const node = content.document.querySelector(nodeSelector); + if (newParentSelector) { + const newParent = content.document.querySelector(newParentSelector); + newParent.appendChild(node); + } else { + node.remove(); + } + } + for (const move of moves) { + setParent(move[0], move[1]); + } + } + ); +} + +/** + * Test a set of tree rearrangements and make sure they cause the expected changes. + */ + +var gDummySerial = 0; + +function mutationTest(testSpec) { + return async function () { + const { walker } = await initInspectorFront( + MAIN_DOMAIN + "inspector-traversal-data.html" + ); + await loadSelectors(walker, testSpec.load || ["html"]); + walker.autoCleanup = !!testSpec.autoCleanup; + if (testSpec.preCheck) { + testSpec.preCheck(); + } + const onMutations = walker.once("mutations"); + + await doMoves(testSpec.moves || []); + + // Some of these moves will trigger no mutation events, + // so do a dummy change to the root node to trigger + // a mutation event anyway. + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [[gDummySerial++]], + function (serial) { + content.document.documentElement.setAttribute("data-dummy", serial); + } + ); + + let mutations = await onMutations; + + // Filter out our dummy mutation. + mutations = mutations.filter(change => { + if (change.type == "attributes" && change.attributeName == "data-dummy") { + return false; + } + return true; + }); + await assertOwnershipTrees(walker); + if (testSpec.postCheck) { + testSpec.postCheck(walker, mutations); + } + }; +} + +// Verify that our dummy mutation works. +add_task( + mutationTest({ + autoCleanup: false, + postCheck(walker, mutations) { + is(mutations.length, 0, "Dummy mutation is filtered out."); + }, + }) +); + +// Test a simple move to a different location in the sibling list for the same +// parent. +add_task( + mutationTest({ + autoCleanup: false, + load: ["#longlist div"], + moves: [["#a", "#longlist"]], + postCheck(walker, mutations) { + const remove = mutations[0]; + is(remove.type, "childList", "First mutation should be a childList."); + ok(!!remove.removed.length, "First mutation should be a removal."); + const add = mutations[1]; + is( + add.type, + "childList", + "Second mutation should be a childList removal." + ); + ok(!!add.added.length, "Second mutation should be an addition."); + const a = add.added[0]; + is(a.id, "a", "Added node should be #a"); + is(a.parentNode(), remove.target, "Should still be a child of longlist."); + is( + remove.target, + add.target, + "First and second mutations should be against the same node." + ); + }, + }) +); + +// Test a move to another location that is within our ownership tree. +add_task( + mutationTest({ + autoCleanup: false, + load: ["#longlist div", "#longlist-sibling"], + moves: [["#a", "#longlist-sibling"]], + postCheck(walker, mutations) { + const remove = mutations[0]; + is(remove.type, "childList", "First mutation should be a childList."); + ok(!!remove.removed.length, "First mutation should be a removal."); + const add = mutations[1]; + is( + add.type, + "childList", + "Second mutation should be a childList removal." + ); + ok(!!add.added.length, "Second mutation should be an addition."); + const a = add.added[0]; + is(a.id, "a", "Added node should be #a"); + is(a.parentNode(), add.target, "Should still be a child of longlist."); + is( + add.target.id, + "longlist-sibling", + "long-sibling should be the target." + ); + }, + }) +); + +// Move an unseen node with a seen parent into our ownership tree - should generate a +// childList pair with no adds or removes. +add_task( + mutationTest({ + autoCleanup: false, + load: ["#longlist"], + moves: [["#longlist-sibling", "#longlist"]], + postCheck(walker, mutations) { + is(mutations.length, 2, "Should generate two mutations"); + is(mutations[0].type, "childList", "Should be childList mutations."); + is(mutations[0].added.length, 0, "Should have no adds."); + is(mutations[0].removed.length, 0, "Should have no removes."); + is(mutations[1].type, "childList", "Should be childList mutations."); + is(mutations[1].added.length, 0, "Should have no adds."); + is(mutations[1].removed.length, 0, "Should have no removes."); + }, + }) +); + +// Move an unseen node with an unseen parent into our ownership tree. Should only +// generate one childList mutation with no adds or removes. +add_task( + mutationTest({ + autoCleanup: false, + load: ["#longlist div"], + moves: [["#longlist-sibling-firstchild", "#longlist"]], + postCheck(walker, mutations) { + is(mutations.length, 1, "Should generate two mutations"); + is(mutations[0].type, "childList", "Should be childList mutations."); + is(mutations[0].added.length, 0, "Should have no adds."); + is(mutations[0].removed.length, 0, "Should have no removes."); + }, + }) +); + +// Move a node between unseen nodes, should generate no mutations. +add_task( + mutationTest({ + autoCleanup: false, + load: ["html"], + moves: [["#longlist-sibling", "#longlist"]], + postCheck(walker, mutations) { + is(mutations.length, 0, "Should generate no mutations."); + }, + }) +); + +// Orphan a node and don't clean it up +add_task( + mutationTest({ + autoCleanup: false, + load: ["#longlist div"], + moves: [["#longlist", null]], + postCheck(walker, mutations) { + is(mutations.length, 1, "Should generate one mutation."); + const change = mutations[0]; + is(change.type, "childList", "Should be a childList."); + is(change.removed.length, 1, "Should have removed a child."); + const ownership = clientOwnershipTree(walker); + is(ownership.orphaned.length, 1, "Should have one orphaned subtree."); + is( + ownershipTreeSize(ownership.orphaned[0]), + 1 + 26 + 26, + "Should have orphaned longlist, and 26 children, and 26 singleTextChilds" + ); + }, + }) +); + +// Orphan a node, and do clean it up. +add_task( + mutationTest({ + autoCleanup: true, + load: ["#longlist div"], + moves: [["#longlist", null]], + postCheck(walker, mutations) { + is(mutations.length, 1, "Should generate one mutation."); + const change = mutations[0]; + is(change.type, "childList", "Should be a childList."); + is(change.removed.length, 1, "Should have removed a child."); + const ownership = clientOwnershipTree(walker); + is(ownership.orphaned.length, 0, "Should have no orphaned subtrees."); + }, + }) +); + +// Orphan a node by moving it into the tree but out of our visible subtree. +add_task( + mutationTest({ + autoCleanup: false, + load: ["#longlist div"], + moves: [["#longlist", "#longlist-sibling"]], + postCheck(walker, mutations) { + is(mutations.length, 1, "Should generate one mutation."); + const change = mutations[0]; + is(change.type, "childList", "Should be a childList."); + is(change.removed.length, 1, "Should have removed a child."); + const ownership = clientOwnershipTree(walker); + is(ownership.orphaned.length, 1, "Should have one orphaned subtree."); + is( + ownershipTreeSize(ownership.orphaned[0]), + 1 + 26 + 26, + "Should have orphaned longlist, 26 children, and 26 singleTextChilds." + ); + }, + }) +); + +// Orphan a node by moving it into the tree but out of our visible subtree, +// and clean it up. +add_task( + mutationTest({ + autoCleanup: true, + load: ["#longlist div"], + moves: [["#longlist", "#longlist-sibling"]], + postCheck(walker, mutations) { + is(mutations.length, 1, "Should generate one mutation."); + const change = mutations[0]; + is(change.type, "childList", "Should be a childList."); + is(change.removed.length, 1, "Should have removed a child."); + const ownership = clientOwnershipTree(walker); + is(ownership.orphaned.length, 0, "Should have no orphaned subtrees."); + }, + }) +); |