declarative-shadow-dom-basic.html (16529B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>Declarative Shadow DOM</title> 4 <link rel="author" href="mailto:masonf@chromium.org"> 5 <link rel="help" href="https://github.com/whatwg/dom/issues/831"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 9 <div id="host" style="display:none"> 10 <template shadowrootmode="open"> 11 <slot id="s1" name="slot1"></slot> 12 </template> 13 <div id="c1" slot="slot1"></div> 14 </div> 15 16 <script> 17 18 // Uncaught exceptions (which will manifest as test harness 19 // errors) constitute a failure of this test. No parsing 20 // operations, whether imperative (setHTMLUnsafe) or declarative 21 // (main document parsing) should ever throw exceptions. 22 // More context: 23 // https://github.com/whatwg/html/issues/10527#issuecomment-2275253439 24 25 test(() => { 26 const host = document.querySelector('#host'); 27 const c1 = host.querySelector('#c1'); 28 assert_true(!!c1); 29 assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root"); 30 assert_equals(host.querySelector('template'), null, "No leftover template node"); 31 assert_true(!!host.shadowRoot,"No shadow root found"); 32 const s1 = host.shadowRoot.querySelector('#s1'); 33 assert_equals(c1.assignedSlot, s1); 34 assert_array_equals(s1.assignedNodes(), [c1]); 35 }, 'Declarative Shadow DOM: Basic test'); 36 37 test(() => { 38 assert_true(HTMLTemplateElement.prototype.hasOwnProperty("shadowRootMode"),'Unable to feature detect'); 39 }, 'Declarative Shadow DOM: Feature detection'); 40 41 test(() => { 42 const t = document.createElement('template'); 43 t.setAttribute('shadowrootmode','open'); 44 assert_equals(t.shadowRootMode,'open','The shadowRootMode IDL should reflect the content attribute'); 45 t.setAttribute('shadowrootmode','closed'); 46 assert_equals(t.shadowRootMode,'closed','"open" and "closed" should both be valid values'); 47 t.setAttribute('shadowrootmode','OpEn'); 48 assert_equals(t.shadowRootMode,'open','Case insensitive'); 49 t.setAttribute('shadowrootmode','INVALID'); 50 assert_equals(t.shadowRootMode,'','Invalid values map to empty string'); 51 t.removeAttribute('shadowrootmode'); 52 assert_equals(t.shadowRootMode,'','No shadowrootmode attribute maps to empty string'); 53 }, 'Shadowrootmode reflection'); 54 55 test(() => { 56 const t = document.createElement('template'); 57 t.shadowRootMode = 'blah'; 58 assert_equals(t.shadowRootMode, ''); 59 t.getAttribute('shadowrootmode', 'blah'); 60 t.shadowRootMode = 'CLOSED'; 61 assert_equals(t.shadowRootMode, 'closed'); 62 t.getAttribute('shadowrootmode', 'CLOSED'); 63 }, 'Shadowrootmode reflection, setter'); 64 65 test(() => { 66 const t = document.createElement('template'); 67 t.setAttribute('shadowrootdelegatesfocus',''); 68 assert_equals(t.shadowRootDelegatesFocus,true,'The shadowRootDelegatesFocus IDL should reflect the content attribute'); 69 t.setAttribute('shadowrootdelegatesfocus','foobar'); 70 assert_equals(t.shadowRootDelegatesFocus,true,'The value doesn\'t matter'); 71 t.removeAttribute('shadowrootdelegatesfocus'); 72 assert_equals(t.shadowRootDelegatesFocus,false,'No shadowRootDelegatesFocus attribute maps to false'); 73 }, 'Shadowrootdelegatesfocus reflection'); 74 75 test(() => { 76 const t = document.createElement('template'); 77 assert_equals(t.getAttribute('shadowrootdelegatesfocus'), null); 78 t.shadowRootDelegatesFocus = true; 79 assert_equals(t.getAttribute('shadowrootdelegatesfocus'), ''); 80 assert_equals(t.shadowRootDelegatesFocus, true); 81 t.shadowRootDelegatesFocus = false; 82 assert_equals(t.getAttribute('shadowrootdelegatesfocus'), null); 83 assert_equals(t.shadowRootDelegatesFocus, false); 84 }, 'Shadowrootdelegatesfocus reflection, setter'); 85 86 87 test(() => { 88 const t = document.createElement('template'); 89 t.setAttribute('shadowrootclonable',''); 90 assert_equals(t.shadowRootClonable,true,'The shadowRootClonable IDL should reflect the content attribute'); 91 t.setAttribute('shadowrootclonable','foobar'); 92 assert_equals(t.shadowRootClonable,true,'The value doesn\'t matter'); 93 t.removeAttribute('shadowrootclonable'); 94 assert_equals(t.shadowRootClonable,false,'No shadowRootClonable attribute maps to false'); 95 }, 'Shadowrootclonable reflection'); 96 97 test(() => { 98 const t = document.createElement('template'); 99 assert_equals(t.getAttribute('shadowrootclonable'), null); 100 t.shadowRootClonable = true; 101 assert_equals(t.getAttribute('shadowrootclonable'), ''); 102 assert_equals(t.shadowRootClonable, true); 103 t.shadowRootClonable = false; 104 assert_equals(t.getAttribute('shadowrootclonable'), null); 105 assert_equals(t.shadowRootClonable, false); 106 }, 'Shadowrootclonable reflection, setter'); 107 108 test(() => { 109 const div = document.createElement('div'); 110 div.setHTMLUnsafe(` 111 <div id="host"> 112 <template shadowrootmode="open"> 113 <slot id="s1" name="slot1"></slot> 114 </template> 115 <div id="c1" slot="slot1"></div> 116 </div> 117 `); 118 const host = div.querySelector('#host'); 119 const c1 = host.querySelector('#c1'); 120 assert_true(!!c1); 121 assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root"); 122 assert_equals(host.querySelector('template'), null, "No leftover template node"); 123 assert_true(!!host.shadowRoot,"No shadow root found"); 124 const s1 = host.shadowRoot.querySelector('#s1'); 125 assert_equals(c1.assignedSlot, s1); 126 assert_array_equals(s1.assignedNodes(), [c1]); 127 }, 'Declarative Shadow DOM: Fragment parser basic test'); 128 129 test(() => { 130 const div = document.createElement('div'); 131 div.setHTMLUnsafe(` 132 <div id="host"> 133 <template shadowrootmode="invalid"> 134 </template> 135 </div> 136 `); 137 const host = div.querySelector('#host'); 138 assert_equals(host.shadowRoot, null, "Shadow root was found"); 139 const tmpl = host.querySelector('template'); 140 assert_true(!!tmpl,"Template should still be present"); 141 const shadowrootAttr = tmpl.getAttribute('shadowrootmode'); 142 assert_equals(shadowrootAttr,"invalid","'shadowrootmode' attribute should still be present"); 143 }, 'Declarative Shadow DOM: Invalid shadow root attribute'); 144 145 test(() => { 146 const div = document.createElement('div'); 147 div.setHTMLUnsafe(` 148 <div id="host"> 149 <template shadowrootmode="closed"> 150 </template> 151 </div> 152 `); 153 const host = div.querySelector('#host'); 154 assert_equals(host.shadowRoot, null, "Closed shadow root"); 155 assert_equals(host.querySelector('template'), null, "No template - converted to shadow root"); 156 }, 'Declarative Shadow DOM: Closed shadow root attribute'); 157 158 test(() => { 159 const div = document.createElement('div'); 160 div.setHTMLUnsafe(` 161 <div id="host"> 162 <template shadowrootmode="open"> 163 <slot id="s1" name="slot1"></slot> 164 </div> 165 `); 166 const host = div.querySelector('#host'); 167 assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root"); 168 assert_equals(host.querySelector('template'), null, "No leftover template node"); 169 assert_true(!!host.shadowRoot,"No shadow root found"); 170 const s1 = host.shadowRoot.querySelector('#s1'); 171 assert_true(!!s1,"Slot should be inside the shadow root"); 172 }, 'Declarative Shadow DOM: Missing closing tag'); 173 174 test(() => { 175 const div = document.createElement('div'); 176 div.setHTMLUnsafe(` 177 <div id="host"> 178 <template shadowrootmode="open" shadowrootdelegatesfocus> 179 </template> 180 </div> 181 `); 182 var host = div.querySelector('#host'); 183 assert_true(!!host.shadowRoot,"No shadow root found"); 184 assert_true(host.shadowRoot.delegatesFocus,"delegatesFocus should be true"); 185 div.setHTMLUnsafe(` 186 <div id="host"> 187 <template shadowrootmode="open"> 188 </template> 189 </div> 190 `); 191 host = div.querySelector('#host'); 192 assert_true(!!host.shadowRoot,"No shadow root found"); 193 assert_false(host.shadowRoot.delegatesFocus,"delegatesFocus should be false without the shadowrootdelegatesfocus attribute"); 194 }, 'Declarative Shadow DOM: delegates focus attribute'); 195 196 test(() => { 197 const div = document.createElement('div'); 198 div.setHTMLUnsafe(` 199 <div id="host"> 200 <template shadowrootmode="open" shadowrootclonable> 201 </template> 202 </div> 203 `); 204 var host = div.querySelector('#host'); 205 assert_true(!!host.shadowRoot,"No shadow root found"); 206 assert_true(host.shadowRoot.clonable,"clonable should be true"); 207 div.setHTMLUnsafe(` 208 <div id="host"> 209 <template shadowrootmode="open"> 210 </template> 211 </div> 212 `); 213 host = div.querySelector('#host'); 214 assert_true(!!host.shadowRoot,"No shadow root found"); 215 assert_false(host.shadowRoot.clonable,"clonable should be false without the shadowrootclonable attribute"); 216 }, 'Declarative Shadow DOM: clonable attribute'); 217 </script> 218 219 <div id="multi-host" style="display:none"> 220 <template shadowrootmode="open"> 221 <span>root 1</span> 222 </template> 223 <template shadowrootmode="closed"> 224 <span>root 2</span> 225 </template> 226 </div> 227 <script> 228 test(() => { 229 const host = document.querySelector('#multi-host'); 230 const leftover = host.querySelector('template'); 231 assert_true(!!leftover, "The second (duplicate) template should be left in the DOM"); 232 assert_true(leftover instanceof HTMLTemplateElement); 233 assert_equals(leftover.getAttribute('shadowrootmode'),"closed"); 234 assert_equals(leftover.shadowRootMode,"closed"); 235 assert_true(!!host.shadowRoot,"No open shadow root found - first root should remain"); 236 const innerSpan = host.shadowRoot.querySelector('span'); 237 assert_equals(innerSpan.textContent, 'root 1', "Content should come from first declarative shadow root"); 238 }, 'Declarative Shadow DOM: Multiple roots'); 239 240 </script> 241 242 <template id="template-containing-shadow"> 243 <div class="innerdiv"> 244 <template shadowrootmode=open shadowrootclonable>Content</template> 245 </div> 246 </template> 247 <script> 248 test(() => { 249 const template = document.querySelector('#template-containing-shadow'); 250 const container1 = document.createElement('div'); 251 container1.style.display = 'none'; 252 document.body.appendChild(container1); 253 container1.appendChild(template.content.cloneNode(true)); 254 let innerDiv = container1.querySelector('div.innerdiv'); 255 const shadowRoot1 = innerDiv.shadowRoot; 256 assert_true(!!shadowRoot1,"Inner div should have a shadow root"); 257 assert_equals(innerDiv.querySelector('template'), null, "No leftover template node"); 258 259 const container2 = document.createElement('div'); 260 container2.style.display = 'none'; 261 document.body.appendChild(container2); 262 container2.appendChild(template.content.cloneNode(true)); 263 innerDiv = container2.querySelector('div.innerdiv'); 264 const shadowRoot2 = innerDiv.shadowRoot; 265 assert_true(!!shadowRoot2,"Inner div should have a shadow root"); 266 assert_equals(innerDiv.querySelector('template'), null, "No leftover template node"); 267 268 assert_not_equals(shadowRoot1,shadowRoot2,'Should not get back the same shadow root'); 269 270 // Make sure importNode also works. 271 const container3 = document.createElement('div'); 272 container3.style.display = 'none'; 273 document.body.appendChild(container3); 274 container3.appendChild(document.importNode(template.content,true)); 275 innerDiv = container3.querySelector('div.innerdiv'); 276 const shadowRoot3 = innerDiv.shadowRoot; 277 assert_true(!!shadowRoot3,"Inner div should have a shadow root"); 278 assert_equals(innerDiv.querySelector('template'), null, "No leftover template node"); 279 assert_not_equals(shadowRoot1,shadowRoot3,'Should not get back the same shadow root'); 280 281 }, 'Declarative Shadow DOM: template containing declarative shadow root (with shadowrootclonable)'); 282 </script> 283 284 <template id="template-containing-deep-shadow"> 285 <div><div><div><div><div> 286 <div class="innerdiv"> 287 <template shadowrootmode=open shadowrootclonable>Content</template> 288 </div> 289 </div></div></div></div></div> 290 </template> 291 <script> 292 test(() => { 293 const template = document.querySelector('#template-containing-deep-shadow'); 294 const host = document.createElement('div'); 295 host.style.display = 'none'; 296 document.body.appendChild(host); 297 host.appendChild(template.content.cloneNode(true)); 298 assert_true(!!host.querySelector('div.innerdiv').shadowRoot,"Inner div should have a shadow root"); 299 }, 'Declarative Shadow DOM: template containing (deeply nested) declarative shadow root'); 300 </script> 301 302 <template id="template-containing-template"> 303 <div> 304 <template id="inner-template"> 305 <div class="innerdiv"> 306 <template shadowrootmode=open shadowrootclonable>Content</template> 307 </div> 308 </template> 309 </div> 310 </template> 311 <script> 312 test(() => { 313 const template = document.querySelector('#template-containing-template'); 314 const host = document.createElement('div'); 315 host.style.display = 'none'; 316 document.body.appendChild(host); 317 host.appendChild(template.content.cloneNode(true)); 318 const innerTemplate = host.querySelector('#inner-template'); 319 assert_true(!!innerTemplate.content.querySelector('div.innerdiv').shadowRoot,"Inner div should have a shadow root"); 320 }, 'Declarative Shadow DOM: template containing a template containing declarative shadow root'); 321 </script> 322 323 <template id="template-containing-ua-shadow"> 324 <div class="innerdiv"> 325 <template shadowrootmode=open shadowrootclonable> 326 <video></video> <!--Assumed to have UA shadow root--> 327 </template> 328 </div> 329 </template> 330 <script> 331 test(() => { 332 const template = document.querySelector('#template-containing-ua-shadow'); 333 const host = document.createElement('div'); 334 host.style.display = 'none'; 335 document.body.appendChild(host); 336 // Mostly make sure clone of template *does* clone the 337 // shadow root, and doesn't crash on cloning the <video>. 338 host.appendChild(template.content.cloneNode(true)); 339 let innerDiv = host.querySelector('div.innerdiv'); 340 const shadowRoot = innerDiv.shadowRoot; 341 assert_true(!!shadowRoot,"Inner div should have a shadow root"); 342 assert_equals(innerDiv.querySelector('template'), null, "No leftover template node"); 343 assert_true(!!innerDiv.shadowRoot.querySelector('video'),'Video element should be present'); 344 }, 'Declarative Shadow DOM: template containing declarative shadow root and UA shadow root'); 345 </script> 346 347 <template id="template-containing-ua-shadow-closed"> 348 <div class="innerdiv"> 349 <template shadowrootmode=closed> 350 <video></video> <!--Assumed to have UA shadow root--> 351 </template> 352 </div> 353 </template> 354 <script> 355 test(() => { 356 const template = document.querySelector('#template-containing-ua-shadow-closed'); 357 const host = document.createElement('div'); 358 host.style.display = 'none'; 359 document.body.appendChild(host); 360 host.appendChild(template.content.cloneNode(true)); 361 let innerDiv = host.querySelector('div.innerdiv'); 362 assert_true(!innerDiv.shadowRoot,"Inner div should have a closed shadow root"); 363 }, 'Declarative Shadow DOM: template containing closed declarative shadow root and UA shadow root'); 364 </script> 365 366 <template id="root-element-shadow"> 367 <template shadowrootmode=open>Content</template> 368 </template> 369 <script> 370 test(() => { 371 // Root element of this template is a <template shadowroot>: 372 const template = document.querySelector('#root-element-shadow'); 373 const host = document.createElement('div'); 374 host.appendChild(template.content.cloneNode(true)); 375 assert_equals(host.shadowRoot, null, "Shadow root should not be present"); 376 const tmpl = host.querySelector('template'); 377 assert_true(!!tmpl,"Template should still be present"); 378 assert_equals(tmpl.getAttribute('shadowrootmode'),"open","'shadowrootmode' attribute should still be present"); 379 }, 'Declarative Shadow DOM: declarative shadow roots are not supported by the template element'); 380 </script> 381 382 <script> 383 let gotError = false; 384 window.addEventListener('error',() => {gotError = true;}); 385 </script> 386 <progress id="invalid-element-exception"> 387 <template shadowrootmode=open>Content</template> 388 </progress> 389 <script> 390 test(() => { 391 assert_false(gotError,'Exceptions should not be thrown by the parser'); 392 const host = document.querySelector('#invalid-element-exception'); 393 const leftover = host.querySelector('template'); 394 assert_true(!host.shadowRoot,"Progress elements don't allow shadow roots"); 395 assert_true(!!leftover, "The template should be left in the DOM"); 396 // This also should not throw exceptions: 397 const div = document.createElement('div'); 398 document.body.appendChild(div); 399 div.setHTMLUnsafe('<progress><template shadowrootmode=open></template></progress>'); 400 assert_false(gotError,'Exceptions should not be thrown by the parser'); 401 assert_true(!!div.querySelector('template'),'parsing should have succeeded and left the template child'); 402 host.remove(); 403 div.remove(); 404 }, 'Declarative Shadow DOM: explicit test that exceptions are not thrown'); 405 </script>