beforematch-element-fragment-navigation.html (8299B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org"> 4 <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute:event-beforematch"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 8 <div id=parentid> 9 <div id=hiddenid> 10 <div id=childid>hello</div> 11 </div> 12 </div> 13 14 <div id=spacer style="height:4000px">spacer</div> 15 16 <script> 17 test(() => { 18 window.location.hash = ''; 19 hiddenid.hidden = 'until-found'; 20 window.location.hash = '#hiddenid'; 21 assert_false(hiddenid.hasAttribute('hidden')); 22 }, 'Verifies that fragment navigation reveals hidden=until-found elements.'); 23 24 test(() => { 25 window.location.hash = ''; 26 parentid.hidden = 'until-found'; 27 hiddenid.hidden = 'until-found'; 28 childid.hidden = 'until-found'; 29 window.location.hash = 'childid'; 30 assert_false(parentid.hasAttribute('hidden'), 'parentid should not have the hidden attribute.'); 31 assert_false(hiddenid.hasAttribute('hidden'), 'hiddenid should not have the hidden attribute.'); 32 assert_false(childid.hasAttribute('hidden'), 'childid should not have the hidden attribute.'); 33 }, 'Verifies that fragment navigation reveals all parent hidden=until-found elements.'); 34 35 test(() => { 36 window.location.hash = ''; 37 hiddenid.hidden = 'until-found'; 38 let beforematchFiredOnParent = false; 39 let beforematchFiredOnHidden = false; 40 let beforematchFiredOnChild = false; 41 parentid.onbeforematch = () => beforematchFiredOnParent = true; 42 hiddenid.onbeforematch = () => beforematchFiredOnHidden = true; 43 childid.onbeforematch = () => beforematchFiredOnChild = true; 44 45 window.location.hash = '#childid'; 46 assert_true(beforematchFiredOnParent, 'beforematch should have been fired on parentid.'); 47 assert_true(beforematchFiredOnHidden, 'beforematch should have been fired on hiddenid.'); 48 assert_false(beforematchFiredOnChild, 'beforematch should not have been fired on childid.'); 49 }, 'Verifies that the beforematch event is fired synchronously and bubbles after fragment navigation.'); 50 51 test(t => { 52 window.location.hash = ''; 53 window.scrollTo(0, 0); 54 assert_true(window.pageYOffset === 0, 'Scroll should reset at the beginning of the test.'); 55 56 const target = document.createElement('div'); 57 target.textContent = 'target'; 58 target.id = 'target'; 59 target.hidden = 'until-found'; 60 document.body.appendChild(target); 61 const spacer = document.createElement('div'); 62 spacer.style.height = '4000px'; 63 t.add_cleanup(() => { 64 target.remove(); 65 spacer.remove(); 66 }); 67 68 let beforematchCalled = false; 69 target.onbeforematch = () => { 70 assert_equals(window.pageYOffset, 0, 'scrolling should happen after beforematch is fired.'); 71 beforematchCalled = true; 72 // Move the target down the page. 73 document.body.appendChild(spacer); 74 target.remove(); 75 document.body.appendChild(target); 76 }; 77 78 window.location.hash = '#target'; 79 assert_true(beforematchCalled, 'The beforematch event should have been fired.'); 80 81 const offsetAfterMatch = window.pageYOffset; 82 assert_not_equals(offsetAfterMatch, 0, 'Fragment navigation should have scrolled down the page to the target element.'); 83 target.scrollIntoView(); 84 assert_equals(offsetAfterMatch, window.pageYOffset, `The scroll after beforematch should be the same as scrolling directly to the element's final destination.`); 85 }, 'Verifies that when a beforematch event handler moves a matching element, we scroll to its final location.'); 86 87 test(t => { 88 window.location.hash = ''; 89 const foo = document.createElement('div'); 90 foo.textContent = 'foo'; 91 foo.id = 'foo'; 92 foo.hidden = 'until-found'; 93 document.body.appendChild(foo); 94 95 const bar = document.createElement('div'); 96 bar.textContent = 'bar'; 97 bar.id = 'bar'; 98 bar.hidden = 'until-found'; 99 document.body.appendChild(bar); 100 101 t.add_cleanup(() => { 102 foo.remove(); 103 bar.remove(); 104 }); 105 106 let beforematchFiredOnFoo = false; 107 foo.onbeforematch = () => beforematchFiredOnFoo = true; 108 let beforematchFiredOnBar = false; 109 bar.onbeforematch = () => beforematchFiredOnBar = true; 110 111 window.location.hash = '#bar'; 112 113 assert_false(beforematchFiredOnFoo, 'foo was not navigated to, so it should not get the beforematch event.'); 114 assert_true(beforematchFiredOnBar, 'bar was navigated to, so it should get the beforematch event.'); 115 assert_true(window.pageYOffset > 0, 'the page should be scrolled down to bar.'); 116 }, 'Verifies that the beforematch event is fired on the right element when there are multiple hidden=until-found elements.'); 117 118 test(t => { 119 window.location.hash = ''; 120 window.scrollTo(0, 0); 121 assert_true(window.pageYOffset === 0, 'Scroll should reset at the beginning of the test.'); 122 123 const div = document.createElement('div'); 124 div.textContent = 'detach'; 125 div.id = 'detach'; 126 div.hidden = 'until-found'; 127 document.body.appendChild(div); 128 t.add_cleanup(() => div.remove()); 129 130 let beforematchCalled = false; 131 div.onbeforematch = () => { 132 div.remove(); 133 beforematchCalled = true; 134 }; 135 136 window.location.hash = '#detach'; 137 138 assert_true(beforematchCalled, 'beforematch should be called when window.location.hash is set to #detach.'); 139 assert_true(window.pageYOffset === 0, 'The page should not be scrolled down to where #detach used to be.'); 140 }, 'Verifies that no scrolling occurs when an element selected by the fragment identifier is detached by the beforematch event handler.'); 141 142 test(t => { 143 window.location.hash = ''; 144 window.scrollTo(0, 0); 145 assert_true(window.pageYOffset === 0, 'Scroll should reset at the beginning of the test.'); 146 147 const div = document.createElement('div'); 148 div.textContent = 'displaynone'; 149 div.id = 'displaynone'; 150 div.hidden = 'until-found'; 151 document.body.appendChild(div); 152 t.add_cleanup(() => div.remove()); 153 154 let beforematchCalled = false; 155 div.addEventListener('beforematch', () => { 156 div.style = 'display: none'; 157 beforematchCalled = true; 158 }); 159 160 window.location.hash = '#displaynone'; 161 162 assert_true(beforematchCalled, 'beforematch should be called when window.location.hash is set to #displaynone.'); 163 assert_true(window.pageYOffset === 0, 'The page should not be scrolled down to where #displaynone used to be.'); 164 }, `No scrolling should occur when the beforematch event handler sets the target element's style to display: none.`); 165 166 test(t => { 167 window.location.hash = ''; 168 window.scrollTo(0, 0); 169 assert_true(window.pageYOffset === 0, 'Scroll should reset at the beginning of the test.'); 170 171 const div = document.createElement('div'); 172 div.textContent = 'visibilityhidden'; 173 div.id = 'visibilityhidden'; 174 div.hidden = 'until-found'; 175 document.body.appendChild(div); 176 t.add_cleanup(() => div.remove()); 177 178 let beforematchCalled = false; 179 div.addEventListener('beforematch', () => { 180 div.style = 'visibility: hidden'; 181 beforematchCalled = true; 182 }); 183 184 window.location.hash = '#visibilityhidden'; 185 186 assert_true(beforematchCalled, 'beforematch should be called when window.location.hash is set to #visibilityhidden.'); 187 assert_true(window.pageYOffset !== 0, 'The page should be scrolled down to where #visibilityhidden is.'); 188 }, `Scrolling should still occur when beforematch sets visiblity:hidden on the target element.`); 189 190 test(t => { 191 window.location.hash = ''; 192 const div = document.createElement('div'); 193 div.id = 'target'; 194 div.textContent = 'target'; 195 document.body.appendChild(div); 196 t.add_cleanup(() => div.remove()); 197 div.addEventListener('beforematch', t.unreached_func('beforematch should not be fired without hidden=until-found.')); 198 window.location.hash = '#target'; 199 }, 'Verifies that the beforematch event is not fired on elements without hidden=until-found.'); 200 201 test(t => { 202 window.location.hash = ''; 203 const div = document.createElement('div'); 204 div.id = 'target'; 205 div.textContent = 'target'; 206 div.hidden = 'until-found'; 207 document.body.appendChild(div); 208 t.add_cleanup(() => div.remove()); 209 210 let hiddenAttributeSet = false; 211 div.addEventListener('beforematch', () => { 212 hiddenAttributeSet = div.hasAttribute('hidden'); 213 }); 214 window.location.hash = '#target'; 215 assert_true(hiddenAttributeSet); 216 }, 'The hidden attribute should still be set inside the beforematch event handler.'); 217 </script>