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