selectedcontent-mutations.html (19380B)
1 <!DOCTYPE html> 2 <link rel=author href="mailto:jarhar@chromium.org"> 3 <link rel=help href="https://github.com/whatwg/html/issues/9799"> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 7 <script> 8 window.testIdToMutationRecords = new Map(); 9 function startTest(testId) { 10 const mutationRecords = []; 11 window.testIdToMutationRecords.set(testId, mutationRecords); 12 const mutationObserver = new MutationObserver(mutations => { 13 mutationRecords.push(...mutations); 14 }); 15 const config = {attributes: true, childList: true, subtree: true}; 16 mutationObserver.observe(document.getElementById(testId), config); 17 } 18 </script> 19 20 <div id=test1> 21 <script> 22 startTest('test1'); 23 </script> 24 <select> 25 <button> 26 <selectedcontent></selectedcontent> 27 </button> 28 <option><span>span</span> one</option> 29 <option><span>span</span> two</option> 30 <option selected><span>span</span> three</option> 31 </select> 32 </div> 33 34 <div id=test2> 35 <script> 36 startTest('test2'); 37 </script> 38 <select> 39 <selectedcontent></selectedcontent> 40 <option>option</option> 41 </select> 42 </div> 43 44 <div id=test3> 45 <script> 46 startTest('test3'); 47 </script> 48 <select> 49 <button> 50 <selectedcontent>outer1 51 <selectedcontent>inner1</selectedcontent> 52 </selectedcontent> 53 <selectedcontent>outer2 54 <selectedcontent>inner2</selectedcontent> 55 </selectedcontent> 56 </button> 57 <option>option</option> 58 </select> 59 </div> 60 61 <div id=test4> 62 <script> 63 startTest('test4'); 64 </script> 65 <select> 66 <button> 67 <selectedcontent></selectedcontent> 68 </button> 69 <div> 70 <option>one</option> 71 </div> 72 <div> 73 <option selected>two</option> 74 </div> 75 </select> 76 </div> 77 78 <div id=test5> 79 <script> 80 startTest('test5'); 81 </script> 82 <select> 83 <option>option</option> 84 <button> 85 <selectedcontent></selectedcontent> 86 </button> 87 </select> 88 </div> 89 90 <div id=test6> 91 <script> 92 startTest('test6'); 93 </script> 94 <select multiple size=1> 95 <button> 96 <selectedcontent> 97 <selectedcontent></selectedcontent> 98 </selectedcontent> 99 <selectedcontent> 100 <selectedcontent></selectedcontent> 101 </selectedcontent> 102 </button> 103 <option>option</option> 104 </select> 105 </div> 106 107 <div id=test7> 108 <script> 109 startTest('test7'); 110 </script> 111 <select> 112 <button> 113 <selectedcontent></selectedcontent> 114 </button> 115 <option>one</option> 116 <div></div> 117 </select> 118 </div> 119 <script> 120 const select = document.getElementById('test7').querySelector('select'); 121 122 const optionTwo = document.createElement('option'); 123 optionTwo.textContent = 'two'; 124 optionTwo.setAttribute('selected', ''); 125 select.appendChild(optionTwo); 126 127 const selectDiv = select.querySelector('div'); 128 const optionThree = document.createElement('option'); 129 optionThree.textContent = 'three'; 130 optionThree.setAttribute('selected', ''); 131 select.appendChild(optionThree); 132 133 select.value = 'one'; 134 </script> 135 136 <script> 137 function getNodeRepresentation(node) { 138 if (!node) { 139 return 'null'; 140 } 141 switch (node.nodeType) { 142 case Node.ELEMENT_NODE: 143 let representation = node.tagName.toLowerCase(); 144 if (node.id) { 145 representation += `#${node.id}`; 146 } 147 if (node.classList && node.classList.length > 0) { 148 representation += `.${Array.from(node.classList).join('.')}`; 149 } 150 return representation; 151 case Node.TEXT_NODE: 152 const text = node.textContent.trim(); 153 return `#text: "${text.length > 50 ? text.substring(0, 47) + '...' : text}"`; 154 case Node.COMMENT_NODE: 155 return ''; 156 default: 157 return `[Node type ${node.nodeType}]`; 158 } 159 } 160 161 function mutationRecordToString(record) { 162 if (!record) { 163 return '[Invalid MutationRecord]'; 164 } 165 166 const targetStr = getNodeRepresentation(record.target); 167 let summary = `Type: ${record.type} | Target: ${targetStr}`; 168 169 switch (record.type) { 170 case 'attributes': 171 const attrName = record.attributeName; 172 const oldValue = record.oldValue !== null ? `"${record.oldValue}"` : 'null'; 173 const newValue = record.target.getAttribute(attrName); 174 const newValueStr = newValue !== null ? `"${newValue}"` : 'null'; 175 summary += ` | Attribute: '${attrName}' changed from ${oldValue} to ${newValueStr}`; 176 if (record.attributeNamespace) { 177 summary += ` (Namespace: ${record.attributeNamespace})`; 178 } 179 break; 180 181 case 'characterData': 182 const oldText = record.oldValue !== null ? `"${record.oldValue}"` : 'null'; 183 const newText = record.target.textContent !== null ? `"${record.target.textContent}"` : 'null'; 184 summary += ` | Data changed from ${oldText} to ${newText}`; 185 break; 186 187 case 'childList': 188 if (record.addedNodes.length > 0) { 189 const added = Array.from(record.addedNodes).map(getNodeRepresentation).join(', '); 190 summary += ` | Added: [${added}]`; 191 } 192 if (record.removedNodes.length > 0) { 193 const removed = Array.from(record.removedNodes).map(getNodeRepresentation).join(', '); 194 summary += ` | Removed: [${removed}]`; 195 } 196 if (record.previousSibling) { 197 summary += ` | After: ${getNodeRepresentation(record.previousSibling)}`; 198 } 199 if (record.nextSibling) { 200 summary += ` | Before: ${getNodeRepresentation(record.nextSibling)}`; 201 } 202 break; 203 204 default: 205 summary += ' | [Unknown mutation type]'; 206 break; 207 } 208 209 return summary; 210 } 211 212 function convertMutationRecords(records) { 213 const output = []; 214 for (const record of records) { 215 output.push(mutationRecordToString(record)); 216 } 217 return output; 218 } 219 220 const testIdToExpectations = { 221 test1: { 222 html: 223 `<select> 224 <button> 225 <selectedcontent><span>span</span> three</selectedcontent> 226 </button> 227 <option><span>span</span> one</option> 228 <option><span>span</span> two</option> 229 <option selected=""><span>span</span> three</option> 230 </select>`, 231 mutations: [ 232 "Type: childList | Target: div#test1 | Added: [#text: \"\"] | After: script", 233 "Type: childList | Target: div#test1 | Added: [select] | After: #text: \"\"", 234 "Type: childList | Target: select | Added: [#text: \"\"]", 235 "Type: childList | Target: select | Added: [button] | After: #text: \"\"", 236 "Type: childList | Target: button | Added: [#text: \"\"]", 237 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 238 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 239 "Type: childList | Target: select | Added: [#text: \"\"] | After: button", 240 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 241 "Type: childList | Target: option | Added: [span]", 242 "Type: childList | Target: span | Added: [#text: \"span\"]", 243 "Type: childList | Target: option | Added: [#text: \"one\"] | After: span", 244 "Type: childList | Target: selectedcontent | Added: [span, #text: \"one\"]", 245 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 246 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 247 "Type: childList | Target: option | Added: [span]", 248 "Type: childList | Target: span | Added: [#text: \"span\"]", 249 "Type: childList | Target: option | Added: [#text: \"two\"] | After: span", 250 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 251 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 252 "Type: childList | Target: selectedcontent | Removed: [span, #text: \"one\"]", 253 "Type: childList | Target: option | Added: [span]", 254 "Type: childList | Target: span | Added: [#text: \"span\"]", 255 "Type: childList | Target: option | Added: [#text: \"three\"] | After: span", 256 "Type: childList | Target: selectedcontent | Added: [span, #text: \"three\"]", 257 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 258 "Type: childList | Target: div#test1 | Added: [#text: \"\"] | After: select" 259 ] 260 }, 261 test2: { 262 html: 263 `<select> 264 <selectedcontent>option</selectedcontent> 265 <option>option</option> 266 </select>`, 267 mutations: [ 268 "Type: childList | Target: div#test2 | Added: [#text: \"\"] | After: script", 269 "Type: childList | Target: div#test2 | Added: [select] | After: #text: \"\"", 270 "Type: childList | Target: select | Added: [#text: \"\"]", 271 "Type: childList | Target: select | Added: [selectedcontent] | After: #text: \"\"", 272 "Type: childList | Target: select | Added: [#text: \"\"] | After: selectedcontent", 273 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 274 "Type: childList | Target: option | Added: [#text: \"option\"]", 275 "Type: childList | Target: selectedcontent | Added: [#text: \"option\"]", 276 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 277 "Type: childList | Target: div#test2 | Added: [#text: \"\"] | After: select" 278 ] 279 }, 280 test3: { 281 html: 282 `<select> 283 <button> 284 <selectedcontent>option</selectedcontent> 285 <selectedcontent>outer2 286 <selectedcontent>inner2</selectedcontent> 287 </selectedcontent> 288 </button> 289 <option>option</option> 290 </select>`, 291 mutations: [ 292 "Type: childList | Target: div#test3 | Added: [#text: \"\"] | After: script", 293 "Type: childList | Target: div#test3 | Added: [select] | After: #text: \"\"", 294 "Type: childList | Target: select | Added: [#text: \"\"]", 295 "Type: childList | Target: select | Added: [button] | After: #text: \"\"", 296 "Type: childList | Target: button | Added: [#text: \"\"]", 297 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 298 "Type: childList | Target: selectedcontent | Added: [#text: \"outer1\"]", 299 "Type: childList | Target: selectedcontent | Added: [selectedcontent] | After: #text: \"outer1\"", 300 "Type: childList | Target: selectedcontent | Added: [#text: \"inner1\"]", 301 "Type: childList | Target: selectedcontent | Added: [#text: \"\"] | After: selectedcontent", 302 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 303 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 304 "Type: childList | Target: selectedcontent | Added: [#text: \"outer2\"]", 305 "Type: childList | Target: selectedcontent | Added: [selectedcontent] | After: #text: \"outer2\"", 306 "Type: childList | Target: selectedcontent | Added: [#text: \"inner2\"]", 307 "Type: childList | Target: selectedcontent | Added: [#text: \"\"] | After: selectedcontent", 308 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 309 "Type: childList | Target: select | Added: [#text: \"\"] | After: button", 310 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 311 "Type: childList | Target: selectedcontent | Removed: [#text: \"outer1\", selectedcontent, #text: \"\"]", 312 "Type: childList | Target: option | Added: [#text: \"option\"]", 313 "Type: childList | Target: selectedcontent | Added: [#text: \"option\"]", 314 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 315 "Type: childList | Target: div#test3 | Added: [#text: \"\"] | After: select" 316 ] 317 }, 318 test4: { 319 html: 320 `<select> 321 <button> 322 <selectedcontent>two</selectedcontent> 323 </button> 324 <div> 325 <option>one</option> 326 </div> 327 <div> 328 <option selected="">two</option> 329 </div> 330 </select>`, 331 mutations: [ 332 "Type: childList | Target: div#test4 | Added: [#text: \"\"] | After: script", 333 "Type: childList | Target: div#test4 | Added: [select] | After: #text: \"\"", 334 "Type: childList | Target: select | Added: [#text: \"\"]", 335 "Type: childList | Target: select | Added: [button] | After: #text: \"\"", 336 "Type: childList | Target: button | Added: [#text: \"\"]", 337 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 338 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 339 "Type: childList | Target: select | Added: [#text: \"\"] | After: button", 340 "Type: childList | Target: select | Added: [div] | After: #text: \"\"", 341 "Type: childList | Target: div | Added: [#text: \"\"]", 342 "Type: childList | Target: div | Added: [option] | After: #text: \"\"", 343 "Type: childList | Target: option | Added: [#text: \"one\"]", 344 "Type: childList | Target: selectedcontent | Added: [#text: \"one\"]", 345 "Type: childList | Target: div | Added: [#text: \"\"] | After: option", 346 "Type: childList | Target: select | Added: [#text: \"\"] | After: div", 347 "Type: childList | Target: select | Added: [div] | After: #text: \"\"", 348 "Type: childList | Target: div | Added: [#text: \"\"]", 349 "Type: childList | Target: div | Added: [option] | After: #text: \"\"", 350 "Type: childList | Target: selectedcontent | Removed: [#text: \"one\"]", 351 "Type: childList | Target: option | Added: [#text: \"two\"]", 352 "Type: childList | Target: selectedcontent | Added: [#text: \"two\"]", 353 "Type: childList | Target: div | Added: [#text: \"\"] | After: option", 354 "Type: childList | Target: select | Added: [#text: \"\"] | After: div", 355 "Type: childList | Target: div#test4 | Added: [#text: \"\"] | After: select" 356 ] 357 }, 358 test5: { 359 html: 360 `<select> 361 <option>option</option> 362 <button> 363 <selectedcontent>option</selectedcontent> 364 </button> 365 </select>`, 366 mutations: [ 367 "Type: childList | Target: div#test5 | Added: [#text: \"\"] | After: script", 368 "Type: childList | Target: div#test5 | Added: [select] | After: #text: \"\"", 369 "Type: childList | Target: select | Added: [#text: \"\"]", 370 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 371 "Type: childList | Target: option | Added: [#text: \"option\"]", 372 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 373 "Type: childList | Target: select | Added: [button] | After: #text: \"\"", 374 "Type: childList | Target: button | Added: [#text: \"\"]", 375 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 376 "Type: childList | Target: selectedcontent | Added: [#text: \"option\"]", 377 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 378 "Type: childList | Target: select | Added: [#text: \"\"] | After: button", 379 "Type: childList | Target: div#test5 | Added: [#text: \"\"] | After: select" 380 ] 381 }, 382 test6: { 383 html: 384 `<select multiple="" size="1"> 385 <button> 386 <selectedcontent> 387 <selectedcontent></selectedcontent> 388 </selectedcontent> 389 <selectedcontent> 390 <selectedcontent></selectedcontent> 391 </selectedcontent> 392 </button> 393 <option>option</option> 394 </select>`, 395 mutations: [ 396 "Type: childList | Target: div#test6 | Added: [#text: \"\"] | After: script", 397 "Type: childList | Target: div#test6 | Added: [select] | After: #text: \"\"", 398 "Type: childList | Target: select | Added: [#text: \"\"]", 399 "Type: childList | Target: select | Added: [button] | After: #text: \"\"", 400 "Type: childList | Target: button | Added: [#text: \"\"]", 401 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 402 "Type: childList | Target: selectedcontent | Added: [#text: \"\"]", 403 "Type: childList | Target: selectedcontent | Added: [selectedcontent] | After: #text: \"\"", 404 "Type: childList | Target: selectedcontent | Added: [#text: \"\"] | After: selectedcontent", 405 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 406 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 407 "Type: childList | Target: selectedcontent | Added: [#text: \"\"]", 408 "Type: childList | Target: selectedcontent | Added: [selectedcontent] | After: #text: \"\"", 409 "Type: childList | Target: selectedcontent | Added: [#text: \"\"] | After: selectedcontent", 410 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 411 "Type: childList | Target: select | Added: [#text: \"\"] | After: button", 412 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 413 "Type: childList | Target: option | Added: [#text: \"option\"]", 414 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 415 "Type: childList | Target: div#test6 | Added: [#text: \"\"] | After: select" 416 ] 417 }, 418 test7: { 419 html: 420 `<select> 421 <button> 422 <selectedcontent>one</selectedcontent> 423 </button> 424 <option>one</option> 425 <div></div> 426 <option selected="">two</option><option selected="">three</option></select>`, 427 mutations: [ 428 "Type: childList | Target: div#test7 | Added: [#text: \"\"] | After: script", 429 "Type: childList | Target: div#test7 | Added: [select] | After: #text: \"\"", 430 "Type: childList | Target: select | Added: [#text: \"\"]", 431 "Type: childList | Target: select | Added: [button] | After: #text: \"\"", 432 "Type: childList | Target: button | Added: [#text: \"\"]", 433 "Type: childList | Target: button | Added: [selectedcontent] | After: #text: \"\"", 434 "Type: childList | Target: button | Added: [#text: \"\"] | After: selectedcontent", 435 "Type: childList | Target: select | Added: [#text: \"\"] | After: button", 436 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 437 "Type: childList | Target: option | Added: [#text: \"one\"]", 438 "Type: childList | Target: selectedcontent | Added: [#text: \"one\"]", 439 "Type: childList | Target: select | Added: [#text: \"\"] | After: option", 440 "Type: childList | Target: select | Added: [div] | After: #text: \"\"", 441 "Type: childList | Target: select | Added: [#text: \"\"] | After: div", 442 "Type: childList | Target: div#test7 | Added: [#text: \"\"] | After: select", 443 "Type: childList | Target: selectedcontent | Added: [#text: \"two\"] | Removed: [#text: \"one\"]", 444 "Type: childList | Target: select | Added: [option] | After: #text: \"\"", 445 "Type: childList | Target: selectedcontent | Added: [#text: \"three\"] | Removed: [#text: \"two\"]", 446 "Type: childList | Target: select | Added: [option] | After: option", 447 "Type: childList | Target: selectedcontent | Added: [#text: \"one\"] | Removed: [#text: \"three\"]" 448 ] 449 } 450 }; 451 452 for (const testId of Object.keys(testIdToExpectations)) { 453 test(() => { 454 const testContainer = document.getElementById(testId); 455 // Remove the script element to minimize the expectations a bit. 456 testContainer.querySelector('script').remove(); 457 const actualHtml = testContainer.innerHTML.trim(); 458 const actualMutations = convertMutationRecords(window.testIdToMutationRecords.get(testId)); 459 460 const expectedHtml = testIdToExpectations[testId].html.trim(); 461 const expectedMutations = testIdToExpectations[testId].mutations; 462 463 assert_equals(actualHtml, expectedHtml, testId); 464 assert_array_equals(actualMutations, expectedMutations, testId); 465 }, `MutationObserver records during parsing of <select> with <selectedcontent>: ${testId}`); 466 } 467 </script>