tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>