offsetParent-across-shadow-boundaries.html (13115B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> 5 <meta name="assert" content="offsetParent should only return nodes that are shadow including ancestor"> 6 <link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent"> 7 <link rel="help" href="https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor"> 8 <script src="/resources/testharness.js"></script> 9 <script src="/resources/testharnessreport.js"></script> 10 <script src="resources/event-path-test-helpers.js"></script> 11 </head> 12 <body> 13 <div id="log"></div> 14 <div id="container" style="position: relative"></div> 15 <script> 16 17 const container = document.getElementById('container'); 18 19 function testOffsetParentInShadowTree(mode) { 20 test(function () { 21 const host = document.createElement('div'); 22 container.appendChild(host); 23 this.add_cleanup(() => host.remove()); 24 const shadowRoot = host.attachShadow({mode}); 25 shadowRoot.innerHTML = '<div id="relativeParent" style="position: relative; padding-left: 100px; padding-top: 70px;"><div id="target"></div></div>'; 26 const relativeParent = shadowRoot.getElementById('relativeParent'); 27 28 assert_true(relativeParent instanceof HTMLDivElement); 29 const target = shadowRoot.getElementById('target'); 30 assert_equals(target.offsetParent, relativeParent); 31 assert_equals(target.offsetLeft, 100); 32 assert_equals(target.offsetTop, 70); 33 }, `offsetParent must return the offset parent in the same shadow tree of ${mode} mode`); 34 } 35 36 testOffsetParentInShadowTree('open'); 37 testOffsetParentInShadowTree('closed'); 38 39 function testOffsetParentInNestedShadowTrees(mode) { 40 test(function () { 41 const outerHost = document.createElement('section'); 42 container.appendChild(outerHost); 43 this.add_cleanup(() => outerHost.remove()); 44 const outerShadow = outerHost.attachShadow({mode}); 45 outerShadow.innerHTML = '<section id="outerParent" style="position: absolute; top: 50px; left: 50px;"></section>'; 46 47 const innerHost = document.createElement('div'); 48 outerShadow.firstChild.appendChild(innerHost); 49 const innerShadow = innerHost.attachShadow({mode}); 50 innerShadow.innerHTML = '<div id="innerParent" style="position: relative; padding-left: 60px; padding-top: 40px;"><div id="target"></div></div>'; 51 const innerParent = innerShadow.getElementById('innerParent'); 52 53 const target = innerShadow.getElementById('target'); 54 assert_true(innerParent instanceof HTMLDivElement); 55 assert_equals(target.offsetParent, innerParent); 56 assert_equals(target.offsetLeft, 60); 57 assert_equals(target.offsetTop, 40); 58 59 outerHost.remove(); 60 }, `offsetParent must return the offset parent in the same shadow tree of ${mode} mode even when nested`); 61 } 62 63 testOffsetParentInNestedShadowTrees('open'); 64 testOffsetParentInNestedShadowTrees('closed'); 65 66 function testOffsetParentOnElementAssignedToSlotInsideOffsetParent(mode) { 67 test(function () { 68 const host = document.createElement('div'); 69 host.innerHTML = '<div id="target"></div>' 70 container.appendChild(host); 71 this.add_cleanup(() => host.remove()); 72 const shadowRoot = host.attachShadow({mode}); 73 shadowRoot.innerHTML = '<div style="position: relative; padding-left: 85px; padding-top: 45px;"><slot></slot></div>'; 74 const target = host.querySelector('#target'); 75 assert_equals(target.offsetParent, container); 76 assert_equals(target.offsetLeft, 85); 77 assert_equals(target.offsetTop, 45); 78 }, `offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of ${mode} mode`); 79 } 80 81 testOffsetParentOnElementAssignedToSlotInsideOffsetParent('open'); 82 testOffsetParentOnElementAssignedToSlotInsideOffsetParent('closed'); 83 84 function testOffsetParentOnElementAssignedToSlotInsideFixedPositionWithContainingBlock(mode) { 85 test(function () { 86 const host = document.createElement('div'); 87 host.innerHTML = '<div id="target"></div>'; 88 container.appendChild(host); 89 this.add_cleanup(() => host.remove()); 90 const shadowRoot = host.attachShadow({mode}); 91 shadowRoot.innerHTML = [ 92 '<div style="transform: translate(10px, 10px);" id="wrapper">', 93 '<div style="position: fixed; padding-left: 85px; padding-top: 45px;">', 94 '<slot></slot>', 95 '</div></div>'].join(''); 96 const target = host.querySelector('#target'); 97 assert_equals(target.offsetParent, container); 98 assert_equals(target.offsetLeft, 85); 99 assert_equals(target.offsetTop, 45); 100 }, `offsetParent must return the fixed position containing block of an element when the context object is assigned to a slot within a fixed containing block in shadow tree of ${mode} mode`); 101 } 102 103 testOffsetParentOnElementAssignedToSlotInsideFixedPositionWithContainingBlock('open'); 104 testOffsetParentOnElementAssignedToSlotInsideFixedPositionWithContainingBlock('closed'); 105 106 function testOffsetParentOnFixedElementAssignedToSlotInsideFixedPositionWithContainingBlock(mode) { 107 test(function () { 108 const host = document.createElement('div'); 109 host.innerHTML = '<div id="target" style="position: fixed;"></div>'; 110 container.appendChild(host); 111 this.add_cleanup(() => host.remove()); 112 const shadowRoot = host.attachShadow({mode}); 113 shadowRoot.innerHTML = [ 114 '<div style="transform: translate(10px, 10px);" id="wrapper">', 115 '<div style="position: fixed; padding-left: 85px; padding-top: 45px;">', 116 '<slot></slot>', 117 '</div></div>'].join(''); 118 const target = host.querySelector('#target'); 119 assert_equals(target.offsetParent, container); 120 assert_equals(target.offsetLeft, 85); 121 assert_equals(target.offsetTop, 45); 122 }, `offsetParent must return the fixed position containing block of a fixed element when the context object is assigned to a slot within a fixed containing block in shadow tree of ${mode} mode`); 123 } 124 125 testOffsetParentOnFixedElementAssignedToSlotInsideFixedPositionWithContainingBlock('open'); 126 testOffsetParentOnFixedElementAssignedToSlotInsideFixedPositionWithContainingBlock('closed'); 127 128 function testOffsetParentOnElementAssignedToSlotInsideFixedPosition(mode) { 129 test(function () { 130 const host = document.createElement('div'); 131 host.innerHTML = '<div id="target"></div>'; 132 container.appendChild(host); 133 this.add_cleanup(() => host.remove()); 134 const shadowRoot = host.attachShadow({mode}); 135 shadowRoot.innerHTML = [ 136 '<div id="fixed" style="position: fixed; padding-left: 85px; padding-top: 45px;">', 137 '<slot></slot>', 138 '</div>'].join(''); 139 const target = host.querySelector('#target'); 140 const fixed = shadowRoot.querySelector('#fixed'); 141 assert_equals(target.offsetParent, null); 142 assert_equals(target.offsetLeft, 85 + fixed.offsetLeft); 143 assert_equals(target.offsetTop, 45 + fixed.offsetTop); 144 }, `offsetParent must return null when the context object is assigned to a slot without a fixed containing block in shadow tree of ${mode} mode`); 145 } 146 147 testOffsetParentOnElementAssignedToSlotInsideFixedPosition('open'); 148 testOffsetParentOnElementAssignedToSlotInsideFixedPosition('closed'); 149 150 function testOffsetParentOnElementAssignedToSlotInsideNestedOffsetParents(mode) { 151 test(function () { 152 const host = document.createElement('div'); 153 host.innerHTML = '<div id="target" style="border:solid 1px blue;">hi</div>'; 154 const previousBlock = document.createElement('div'); 155 previousBlock.style.height = '12px'; 156 container.append(previousBlock, host); 157 this.add_cleanup(() => { container.innerHTML = ''; }); 158 const shadowRoot = host.attachShadow({mode}); 159 shadowRoot.innerHTML = '<section style="position: relative; margin-left: 20px; margin-top: 100px; background: #ccc"><div style="position: absolute; top: 10px; left: 10px;"><slot></slot></div></section>'; 160 const target = host.querySelector('#target'); 161 assert_equals(target.offsetParent, container); 162 assert_equals(target.offsetLeft, 30); 163 assert_equals(target.offsetTop, 122); 164 }, `offsetParent must skip multiple offset parents of an element when the context object is assigned to a slot in a shadow tree of ${mode} mode`); 165 } 166 167 testOffsetParentOnElementAssignedToSlotInsideNestedOffsetParents('open'); 168 testOffsetParentOnElementAssignedToSlotInsideNestedOffsetParents('closed'); 169 170 function testOffsetParentOnElementAssignedToSlotInsideNestedShadowTrees(mode) { 171 test(function () { 172 const outerHost = document.createElement('section'); 173 outerHost.innerHTML = '<div id="target"></div>'; 174 container.appendChild(outerHost); 175 this.add_cleanup(() => outerHost.remove()); 176 const outerShadow = outerHost.attachShadow({mode}); 177 outerShadow.innerHTML = '<section style="position: absolute; top: 40px; left: 50px;"><div id="innerHost"><slot></slot></div></section>'; 178 179 const innerShadow = outerShadow.getElementById('innerHost').attachShadow({mode}); 180 innerShadow.innerHTML = '<div style="position: absolute; top: 200px; margin-left: 100px;"><slot></slot></div>'; 181 182 const target = outerHost.querySelector('#target'); 183 assert_equals(target.offsetParent, container); 184 assert_equals(target.offsetLeft, 150); 185 assert_equals(target.offsetTop, 240); 186 outerHost.remove(); 187 }, `offsetParent must skip offset parents of an element when the context object is assigned to a slot in nested shadow trees of ${mode} mode`); 188 } 189 190 testOffsetParentOnElementAssignedToSlotInsideNestedShadowTrees('open'); 191 testOffsetParentOnElementAssignedToSlotInsideNestedShadowTrees('closed'); 192 193 function testOffsetParentOnElementInsideShadowTreeWithoutOffsetParent(mode) { 194 test(function () { 195 const outerHost = document.createElement('section'); 196 container.appendChild(outerHost); 197 this.add_cleanup(() => outerHost.remove()); 198 const outerShadow = outerHost.attachShadow({mode}); 199 outerShadow.innerHTML = '<div id="innerHost"><div id="target"></div></div>'; 200 201 const innerShadow = outerShadow.getElementById('innerHost').attachShadow({mode}); 202 innerShadow.innerHTML = '<div style="position: absolute; top: 23px; left: 24px;"><slot></slot></div>'; 203 204 const target = outerShadow.querySelector('#target'); 205 assert_equals(target.offsetParent, container); 206 assert_equals(target.offsetLeft, 24); 207 assert_equals(target.offsetTop, 23); 208 }, `offsetParent must find the first offset parent which is a shadow-including ancestor of the context object even some shadow tree of ${mode} mode did not have any offset parent`); 209 } 210 211 testOffsetParentOnElementInsideShadowTreeWithoutOffsetParent('open'); 212 testOffsetParentOnElementInsideShadowTreeWithoutOffsetParent('closed'); 213 214 function testOffsetParentOnUnassignedChild(mode) { 215 test(function () { 216 const host = document.createElement('section'); 217 host.innerHTML = '<div id="target"></div>'; 218 this.add_cleanup(() => host.remove()); 219 container.appendChild(host); 220 const shadowRoot = host.attachShadow({mode}); 221 shadowRoot.innerHTML = '<section style="position: absolute; top: 50px; left: 50px;">content</section>'; 222 const target = host.querySelector('#target'); 223 assert_equals(target.offsetParent, null); 224 assert_equals(target.offsetLeft, 0); 225 assert_equals(target.offsetTop, 0); 226 }, `offsetParent must return null on a child element of a shadow host for the shadow tree in ${mode} mode which is not assigned to any slot`); 227 } 228 229 testOffsetParentOnUnassignedChild('open'); 230 testOffsetParentOnUnassignedChild('closed'); 231 232 function testOffsetParentOnAssignedChildNotInFlatTree(mode) { 233 test(function () { 234 const outerHost = document.createElement('section'); 235 outerHost.innerHTML = '<div id="target"></div>'; 236 container.appendChild(outerHost); 237 this.add_cleanup(() => outerHost.remove()); 238 const outerShadow = outerHost.attachShadow({mode}); 239 outerShadow.innerHTML = '<div id="innerHost"><div style="position: absolute; top: 50px; left: 50px;"><slot></slot></div></div>'; 240 241 const innerShadow = outerShadow.getElementById('innerHost').attachShadow({mode}); 242 innerShadow.innerHTML = '<div>content</div>'; 243 244 const target = outerHost.querySelector('#target'); 245 assert_equals(target.offsetParent, null); 246 assert_equals(target.offsetLeft, 0); 247 assert_equals(target.offsetTop, 0); 248 }, `offsetParent must return null on a child element of a shadow host for the shadow tree in ${mode} mode which is not in the flat tree`); 249 } 250 251 testOffsetParentOnAssignedChildNotInFlatTree('open'); 252 testOffsetParentOnAssignedChildNotInFlatTree('closed'); 253 254 </script> 255 </body> 256 </html>