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 );