event-inside-slotted-node.html (14764B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Shadow DOM: Firing an event inside a node assigned to a slot</title> 5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> 6 <meta name="assert" content="The event path calculation algorithm must be used to determine event path"> 7 <link rel="help" href="https://w3c.github.io/webcomponents/spec/shadow/#event-paths"> 8 <script src="/resources/testharness.js"></script> 9 <script src="/resources/testharnessreport.js"></script> 10 </head> 11 <body> 12 <div id="log"></div> 13 <script> 14 15 function dispatchEventWithLog(shadow, event) { 16 var log = []; 17 18 var attachedNodes = []; 19 for (var nodeKey in shadow) { 20 var startingNode = shadow[nodeKey]; 21 for (var node = startingNode; node; node = node.parentNode) { 22 if (attachedNodes.indexOf(node) >= 0) 23 continue; 24 attachedNodes.push(node); 25 node.addEventListener(event.type, (function (event) { 26 log.push([this, event.target]); 27 }).bind(node)); 28 } 29 } 30 31 shadow.target.dispatchEvent(event); 32 33 return log; 34 } 35 36 function element(name, children, className) { 37 var element = document.createElement(name); 38 if (className) 39 element.className = className; 40 if (children) { 41 for (var child of children) 42 element.appendChild(child); 43 } 44 return element; 45 } 46 47 function attachShadow(host, mode, children) { 48 var shadowRoot = host.attachShadow({mode: mode}); 49 if (children) { 50 for (var child of children) 51 shadowRoot.appendChild(child); 52 } 53 return shadowRoot; 54 } 55 56 function createShadowHostWithAssignedGrandChild(mode) { 57 var host = element('div', [ 58 element('b', [ 59 element('i') 60 ]) 61 ]); 62 63 var root = attachShadow(host, mode, [ 64 element('span', [ 65 element('slot') 66 ]) 67 ]); 68 69 return {target: host.querySelector('i'), targetParent: host.querySelector('b'), host: host, 70 slot: root.querySelector('slot'), slotParent: root.querySelector('span'), root: root}; 71 } 72 73 function testEventInDetachedShadowHostDescendant(mode) { 74 test(function () { 75 var shadow = createShadowHostWithAssignedGrandChild(mode); 76 77 log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true, composed: true})); 78 79 assert_equals(log.length, 6, 'EventPath must contain [target, target parent, slot, slot parent, shadow root, shadow host]'); 80 assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target'); 81 assert_array_equals(log[1], [shadow.targetParent, shadow.target], 'EventPath[1] must be the parent of the target'); 82 assert_array_equals(log[2], [shadow.slot, shadow.target], 'EventPath[2] must be the slot'); 83 assert_array_equals(log[3], [shadow.slotParent, shadow.target], 'EventPath[3] must be the parent of the slot'); 84 assert_array_equals(log[4], [shadow.root, shadow.target], 'EventPath[4] must be the shadow root'); 85 assert_array_equals(log[5], [shadow.host, shadow.target], 'EventPath[5] must be the shadow host'); 86 87 }, 'Firing an event inside a grand child of a detached ' + mode + ' mode shadow host'); 88 } 89 90 testEventInDetachedShadowHostDescendant('open'); 91 testEventInDetachedShadowHostDescendant('closed'); 92 93 function testEventInShadowHostDescendantInsideDocument(mode) { 94 test(function () { 95 var shadow = createShadowHostWithAssignedGrandChild(mode); 96 document.body.appendChild(shadow.host); 97 98 log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true, composed: true})); 99 100 assert_equals(log.length, 9, 'EventPath must contain [target, target parent, slot, slot parent, shadow root, shadow host, body, html, document]'); 101 assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target'); 102 assert_array_equals(log[1], [shadow.targetParent, shadow.target], 'EventPath[1] must be the parent of the target'); 103 assert_array_equals(log[2], [shadow.slot, shadow.target], 'EventPath[2] must be the slot'); 104 assert_array_equals(log[3], [shadow.slotParent, shadow.target], 'EventPath[3] must be the parent of the slot'); 105 assert_array_equals(log[4], [shadow.root, shadow.target], 'EventPath[4] must be the shadow root'); 106 assert_array_equals(log[5], [shadow.host, shadow.target], 'EventPath[5] must be the shadow host'); 107 assert_array_equals(log[6], [document.body, shadow.target], 'EventPath[6] must be the body element'); 108 assert_array_equals(log[7], [document.documentElement, shadow.target], 'EventPath[7] must be the html element'); 109 assert_array_equals(log[8], [document, shadow.target], 'EventPath[8] must be the html element'); 110 111 }, 'Firing an event inside a grand child of an in-document ' + mode + ' mode shadow host'); 112 } 113 114 testEventInShadowHostDescendantInsideDocument('open'); 115 testEventInShadowHostDescendantInsideDocument('closed'); 116 117 function createNestedShadowTreesWithSlots(innerMode, outerUpperMode, outerLowerMode) { 118 var upperHost = element('upper-host', [ 119 element('p', [ 120 element('lower-host', [ 121 element('a') 122 ]) 123 ]) 124 ]); 125 126 var upperShadow = attachShadow(upperHost, outerUpperMode, [ 127 element('b', [ 128 element('slot', [], 'upper-slot') 129 ]) 130 ]); 131 132 var lowerHost = upperHost.querySelector('lower-host'); 133 var lowerShadow = attachShadow(lowerHost, outerLowerMode, [ 134 element('em', [ 135 element('inner-host', [ 136 element('span', [ 137 element('slot', [], 'lower-slot') 138 ]) 139 ]) 140 ]) 141 ]); 142 143 innerShadow = attachShadow(lowerShadow.querySelector('inner-host'), innerMode, [ 144 element('i', [ 145 element('slot', [], 'inner-slot') 146 ]) 147 ]); 148 149 return { 150 host: upperHost, 151 target: upperHost.querySelector('a'), 152 upperShadow: upperShadow, 153 upperSlot: upperShadow.querySelector('slot'), 154 lowerShadow: lowerShadow, 155 lowerSlot: lowerShadow.querySelector('slot'), 156 innerShadow: innerShadow, 157 innerSlot: innerShadow.querySelector('slot'), 158 }; 159 } 160 161 /* 162 upper-host (14) -- (upperShadow; 13) 163 + p (10) + b (12) 164 | + slot (upperSlot; 11) 165 + lower-host (9) -- (lowerShadow; 8) 166 + a (target; 0) + em (7) 167 + inner-host (6) -------- (innerShadow; 5) 168 + span (2) + i (4) 169 + slot (lowerSlot; 1) + slot (innerSlot; 3) 170 */ 171 172 function testEventUnderTwoShadowRoots(outerUpperMode, outerLowerMode, innerMode) { 173 test(function () { 174 var shadow = createNestedShadowTreesWithSlots(innerMode, outerUpperMode, outerLowerMode); 175 176 log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true, composed: true})); 177 178 assert_equals(log.length, 15, 'EventPath must contain 15 targets'); 179 180 assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target'); 181 assert_array_equals(log[1], [shadow.lowerSlot, shadow.target], 'EventPath[1] must be the slot inside the lower shadow tree'); 182 assert_array_equals(log[2], [shadow.lowerSlot.parentNode, shadow.target], 'EventPath[2] must be the parent of the slot inside the lower shadow tree'); 183 assert_array_equals(log[3], [shadow.innerSlot, shadow.target], 'EventPath[3] must be the slot inside the shadow tree inside the lower shadow tree'); 184 assert_array_equals(log[4], [shadow.innerSlot.parentNode, shadow.target], 'EventPath[4] must be the child of the inner shadow root'); 185 assert_array_equals(log[5], [shadow.innerShadow, shadow.target], 'EventPath[5] must be the inner shadow root'); 186 assert_array_equals(log[6], [shadow.innerShadow.host, shadow.target], 'EventPath[6] must be the host of the inner shadow tree'); 187 assert_array_equals(log[7], [shadow.lowerShadow.firstChild, shadow.target], 'EventPath[7] must be the parent of the inner shadow host'); 188 assert_array_equals(log[8], [shadow.lowerShadow, shadow.target], 'EventPath[8] must be the lower shadow root'); 189 assert_array_equals(log[9], [shadow.lowerShadow.host, shadow.target], 'EventPath[9] must be the lower shadow host'); 190 assert_array_equals(log[10], [shadow.host.firstChild, shadow.target], 'EventPath[10] must be the parent of the grand parent of the target'); 191 assert_array_equals(log[11], [shadow.upperSlot, shadow.target], 'EventPath[11] must be the slot inside the upper shadow tree'); 192 assert_array_equals(log[12], [shadow.upperSlot.parentNode, shadow.target], 'EventPath[12] must be the parent of the slot inside the upper shadow tree'); 193 assert_array_equals(log[13], [shadow.upperShadow, shadow.target], 'EventPath[13] must be the upper shadow root'); 194 assert_array_equals(log[14], [shadow.host, shadow.target], 'EventPath[14] must be the host'); 195 196 }, 'Firing an event on a node with two ancestors with a detached ' + outerUpperMode + ' and ' + outerLowerMode 197 + ' shadow trees with an inner ' + innerMode + ' shadow tree'); 198 } 199 200 testEventUnderTwoShadowRoots('open', 'open', 'open'); 201 testEventUnderTwoShadowRoots('open', 'open', 'closed'); 202 testEventUnderTwoShadowRoots('open', 'closed', 'open'); 203 testEventUnderTwoShadowRoots('open', 'closed', 'closed'); 204 testEventUnderTwoShadowRoots('closed', 'open', 'open'); 205 testEventUnderTwoShadowRoots('closed', 'open', 'closed'); 206 testEventUnderTwoShadowRoots('closed', 'closed', 'open'); 207 testEventUnderTwoShadowRoots('closed', 'closed', 'closed'); 208 209 /* 210 upper-host (11) -- (upperShadow; 10) 211 + p (7) + b (9) 212 | + slot (upperSlot; 8) 213 + lower-host (6) -- (lowerShadow; 5) 214 + a + em (4) 215 + inner-host (3) -- (innerShadow; 2) 216 + span + i (1) 217 + slot + slot (innerSlot, target; 0) 218 */ 219 220 function testEventInsideNestedShadowsUnderAnotherShadow(outerUpperMode, outerLowerMode, innerMode) { 221 test(function () { 222 var shadow = createNestedShadowTreesWithSlots(innerMode, outerUpperMode, outerLowerMode); 223 shadow.deepestNodeInLightDOM = shadow.target; // Needed for dispatchEventWithLog to attach event listeners. 224 shadow.target = shadow.innerSlot; 225 226 log = dispatchEventWithLog(shadow, new Event('foo', {bubbles: true, composed: true})); 227 228 assert_equals(log.length, 12, 'EventPath must contain 12 targets'); 229 230 assert_array_equals(log[0], [shadow.target, shadow.target], 'EventPath[0] must be the target'); 231 assert_array_equals(log[1], [shadow.target.parentNode, shadow.target], 'EventPath[1] must be the parent of the target'); 232 assert_array_equals(log[2], [shadow.innerShadow, shadow.target], 'EventPath[2] must be the inner shadow root'); 233 assert_array_equals(log[3], [shadow.innerShadow.host, shadow.innerShadow.host], 'EventPath[3] must be the inner shadow host'); 234 assert_array_equals(log[4], [shadow.lowerShadow.firstChild, shadow.innerShadow.host], 'EventPath[4] must be the parent of the inner shadow host'); 235 assert_array_equals(log[5], [shadow.lowerShadow, shadow.innerShadow.host], 'EventPath[5] must be the lower (but outer) shadow root'); 236 assert_array_equals(log[6], [shadow.lowerShadow.host, shadow.lowerShadow.host], 'EventPath[6] must be the lower (but outer) shadow root'); 237 assert_array_equals(log[7], [shadow.host.firstChild, shadow.lowerShadow.host], 'EventPath[7] must be the slot inside the upper shadow tree'); 238 assert_array_equals(log[8], [shadow.upperSlot, shadow.lowerShadow.host], 'EventPath[8] must be the slot inside the upper shadow tree'); 239 assert_array_equals(log[9], [shadow.upperSlot.parentNode, shadow.lowerShadow.host], 'EventPath[9] must be the parent of the slot inside the upper shadow tree'); 240 assert_array_equals(log[10], [shadow.upperShadow, shadow.lowerShadow.host], 'EventPath[10] must be the upper shadow root'); 241 assert_array_equals(log[11], [shadow.upperShadow.host, shadow.lowerShadow.host], 'EventPath[11] must be the host'); 242 243 }, 'Firing an event on a node within a ' + innerMode + ' shadow tree that is itself a ' + outerLowerMode 244 + ' shadow tree (the latter being the descendent of a host for a separate ' + outerUpperMode + ' shadow tree)'); 245 } 246 247 testEventInsideNestedShadowsUnderAnotherShadow('open', 'open', 'open'); 248 testEventInsideNestedShadowsUnderAnotherShadow('open', 'open', 'closed'); 249 testEventInsideNestedShadowsUnderAnotherShadow('open', 'closed', 'open'); 250 testEventInsideNestedShadowsUnderAnotherShadow('open', 'closed', 'closed'); 251 testEventInsideNestedShadowsUnderAnotherShadow('closed', 'open', 'open'); 252 testEventInsideNestedShadowsUnderAnotherShadow('closed', 'open', 'closed'); 253 testEventInsideNestedShadowsUnderAnotherShadow('closed', 'closed', 'open'); 254 testEventInsideNestedShadowsUnderAnotherShadow('closed', 'closed', 'closed'); 255 256 </script> 257 </body> 258 </html>