tor-browser

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

intersection-observer-test-utils.js (8182B)


      1 // Here's how waitForNotification works:
      2 //
      3 // - myTestFunction0()
      4 //   - waitForNotification(myTestFunction1)
      5 //     - requestAnimationFrame()
      6 //   - Modify DOM in a way that should trigger an IntersectionObserver callback.
      7 // - BeginFrame
      8 //   - requestAnimationFrame handler runs
      9 //     - Second requestAnimationFrame()
     10 //   - Style, layout, paint
     11 //   - IntersectionObserver generates new notifications
     12 //     - Posts a task to deliver notifications
     13 // - Task to deliver IntersectionObserver notifications runs
     14 //   - IntersectionObserver callbacks run
     15 // - Second requestAnimationFrameHandler runs
     16 //     - step_timeout()
     17 // - step_timeout handler runs
     18 //   - myTestFunction1()
     19 //     - [optional] waitForNotification(myTestFunction2)
     20 //       - requestAnimationFrame()
     21 //     - Verify newly-arrived IntersectionObserver notifications
     22 //     - [optional] Modify DOM to trigger new notifications
     23 //
     24 // Ideally, it should be sufficient to use requestAnimationFrame followed
     25 // by two step_timeouts, with the first step_timeout firing in between the
     26 // requestAnimationFrame handler and the task to deliver notifications.
     27 // However, the precise timing of requestAnimationFrame, the generation of
     28 // a new display frame (when IntersectionObserver notifications are
     29 // generated), and the delivery of these events varies between engines, making
     30 // this tricky to test in a non-flaky way.
     31 //
     32 // In particular, in WebKit, requestAnimationFrame and the generation of
     33 // a display frame are two separate tasks, so a step_timeout called within
     34 // requestAnimationFrame can fire before a display frame is generated.
     35 //
     36 // In Gecko, on the other hand, requestAnimationFrame and the generation of
     37 // a display frame are a single task, and IntersectionObserver notifications
     38 // are generated during this task. However, the task posted to deliver these
     39 // notifications can fire after the following requestAnimationFrame.
     40 //
     41 // This means that in general, by the time the second requestAnimationFrame
     42 // handler runs, we know that IntersectionObservations have been generated,
     43 // and that a task to deliver these notifications has been posted (though
     44 // possibly not yet delivered). Then, by the time the step_timeout() handler
     45 // runs, these notifications have been delivered.
     46 //
     47 // Since waitForNotification uses a double-rAF, it is now possible that
     48 // IntersectionObservers may have generated more notifications than what is
     49 // under test, but have not yet scheduled the new batch of notifications for
     50 // delivery. As a result, observer.takeRecords should NOT be used in tests:
     51 //
     52 // - myTestFunction0()
     53 //   - waitForNotification(myTestFunction1)
     54 //     - requestAnimationFrame()
     55 //   - Modify DOM in a way that should trigger an IntersectionObserver callback.
     56 // - BeginFrame
     57 //   - requestAnimationFrame handler runs
     58 //     - Second requestAnimationFrame()
     59 //   - Style, layout, paint
     60 //   - IntersectionObserver generates a batch of notifications
     61 //     - Posts a task to deliver notifications
     62 // - Task to deliver IntersectionObserver notifications runs
     63 //   - IntersectionObserver callbacks run
     64 // - BeginFrame
     65 //   - Second requestAnimationFrameHandler runs
     66 //     - step_timeout()
     67 //   - IntersectionObserver generates another batch of notifications
     68 //     - Post task to deliver notifications
     69 // - step_timeout handler runs
     70 //   - myTestFunction1()
     71 //     - At this point, observer.takeRecords will get the second batch of
     72 //       notifications.
     73 function waitForNotification(t, f) {
     74  return new Promise(resolve => {
     75    requestAnimationFrame(function() {
     76      requestAnimationFrame(function() {
     77        let callback = function() {
     78          resolve();
     79          if (f) {
     80            f();
     81          }
     82        };
     83        if (t) {
     84          t.step_timeout(callback);
     85        } else {
     86          setTimeout(callback);
     87        }
     88      });
     89    });
     90  });
     91 }
     92 
     93 // If you need to wait until the IntersectionObserver algorithm has a chance
     94 // to run, but don't need to wait for delivery of the notifications...
     95 function waitForFrame(t, f) {
     96  return new Promise(resolve => {
     97    requestAnimationFrame(function() {
     98      t.step_timeout(function() {
     99        resolve();
    100        if (f) {
    101          f();
    102        }
    103      });
    104    });
    105  });
    106 }
    107 
    108 // The timing of when runTestCycle is called is important.  It should be
    109 // called:
    110 //
    111 //   - Before or during the window load event, or
    112 //   - Inside of a prior runTestCycle callback, *before* any assert_* methods
    113 //     are called.
    114 //
    115 // Following these rules will ensure that the test suite will not abort before
    116 // all test steps have run.
    117 //
    118 // If the 'delay' parameter to the IntersectionObserver constructor is used,
    119 // tests will need to add the same delay to their runTestCycle invocations, to
    120 // wait for notifications to be generated and delivered.
    121 function runTestCycle(f, description, delay) {
    122  async_test(function(t) {
    123    if (delay) {
    124      step_timeout(() => {
    125        waitForNotification(t, t.step_func_done(f));
    126      }, delay);
    127    } else {
    128      waitForNotification(t, t.step_func_done(f));
    129    }
    130  }, description);
    131 }
    132 
    133 // Root bounds for a root with an overflow clip as defined by:
    134 //   http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle
    135 function contentBounds(root) {
    136  var left = root.offsetLeft + root.clientLeft;
    137  var right = left + root.clientWidth;
    138  var top = root.offsetTop + root.clientTop;
    139  var bottom = top + root.clientHeight;
    140  return [left, right, top, bottom];
    141 }
    142 
    143 // Root bounds for a root without an overflow clip as defined by:
    144 //   http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle
    145 function borderBoxBounds(root) {
    146  var left = root.offsetLeft;
    147  var right = left + root.offsetWidth;
    148  var top = root.offsetTop;
    149  var bottom = top + root.offsetHeight;
    150  return [left, right, top, bottom];
    151 }
    152 
    153 function clientBounds(element) {
    154  var rect = element.getBoundingClientRect();
    155  return [rect.left, rect.right, rect.top, rect.bottom];
    156 }
    157 
    158 function rectArea(rect) {
    159  return (rect.left - rect.right) * (rect.bottom - rect.top);
    160 }
    161 
    162 function checkRect(actual, expected, description, epsilon = 0) {
    163  if (!expected.length)
    164    return;
    165  assert_approx_equals(actual.left, expected[0], epsilon, description + '.left');
    166  assert_approx_equals(actual.right, expected[1], epsilon, description + '.right');
    167  assert_approx_equals(actual.top, expected[2], epsilon, description + '.top');
    168  assert_approx_equals(actual.bottom, expected[3], epsilon, description + '.bottom');
    169 }
    170 
    171 function checkLastEntry(entries, i, expected, epsilon = 0) {
    172  assert_equals(entries.length, i + 1, 'entries.length');
    173  if (expected) {
    174    checkRect(
    175        entries[i].boundingClientRect, expected.slice(0, 4),
    176        'entries[' + i + '].boundingClientRect', epsilon);
    177    checkRect(
    178        entries[i].intersectionRect, expected.slice(4, 8),
    179        'entries[' + i + '].intersectionRect', epsilon);
    180    checkRect(
    181        entries[i].rootBounds, expected.slice(8, 12),
    182        'entries[' + i + '].rootBounds', epsilon);
    183    if (expected.length > 12) {
    184      assert_equals(
    185          entries[i].isIntersecting, expected[12],
    186          'entries[' + i + '].isIntersecting');
    187    }
    188  }
    189 }
    190 
    191 function checkJsonEntry(actual, expected) {
    192  checkRect(
    193      actual.boundingClientRect, expected.boundingClientRect,
    194      'entry.boundingClientRect');
    195  checkRect(
    196      actual.intersectionRect, expected.intersectionRect,
    197      'entry.intersectionRect');
    198  if (actual.rootBounds == 'null')
    199    assert_equals(expected.rootBounds, 'null', 'rootBounds is null');
    200  else
    201    checkRect(actual.rootBounds, expected.rootBounds, 'entry.rootBounds');
    202  assert_equals(actual.isIntersecting, expected.isIntersecting);
    203  assert_equals(actual.target, expected.target);
    204 }
    205 
    206 function checkJsonEntries(actual, expected, description) {
    207  test(function() {
    208    assert_equals(actual.length, expected.length);
    209    for (var i = 0; i < actual.length; i++)
    210      checkJsonEntry(actual[i], expected[i]);
    211  }, description);
    212 }
    213 
    214 function checkIsIntersecting(entries, i, expected) {
    215  assert_equals(entries[i].isIntersecting, expected,
    216    'entries[' + i + '].target.isIntersecting equals ' + expected);
    217 }