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