summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/browser/browser_inspector-mutations-childlist.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests/browser/browser_inspector-mutations-childlist.js')
-rw-r--r--devtools/server/tests/browser/browser_inspector-mutations-childlist.js282
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.");
+ },
+ })
+);