summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/intersection-observer/resources/intersection-observer-test-utils.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/intersection-observer/resources/intersection-observer-test-utils.js
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/intersection-observer/resources/intersection-observer-test-utils.js')
-rw-r--r--testing/web-platform/tests/intersection-observer/resources/intersection-observer-test-utils.js217
1 files changed, 217 insertions, 0 deletions
diff --git a/testing/web-platform/tests/intersection-observer/resources/intersection-observer-test-utils.js b/testing/web-platform/tests/intersection-observer/resources/intersection-observer-test-utils.js
new file mode 100644
index 0000000000..c26ccea030
--- /dev/null
+++ b/testing/web-platform/tests/intersection-observer/resources/intersection-observer-test-utils.js
@@ -0,0 +1,217 @@
+// Here's how waitForNotification works:
+//
+// - myTestFunction0()
+// - waitForNotification(myTestFunction1)
+// - requestAnimationFrame()
+// - Modify DOM in a way that should trigger an IntersectionObserver callback.
+// - BeginFrame
+// - requestAnimationFrame handler runs
+// - Second requestAnimationFrame()
+// - Style, layout, paint
+// - IntersectionObserver generates new notifications
+// - Posts a task to deliver notifications
+// - Task to deliver IntersectionObserver notifications runs
+// - IntersectionObserver callbacks run
+// - Second requestAnimationFrameHandler runs
+// - step_timeout()
+// - step_timeout handler runs
+// - myTestFunction1()
+// - [optional] waitForNotification(myTestFunction2)
+// - requestAnimationFrame()
+// - Verify newly-arrived IntersectionObserver notifications
+// - [optional] Modify DOM to trigger new notifications
+//
+// Ideally, it should be sufficient to use requestAnimationFrame followed
+// by two step_timeouts, with the first step_timeout firing in between the
+// requestAnimationFrame handler and the task to deliver notifications.
+// However, the precise timing of requestAnimationFrame, the generation of
+// a new display frame (when IntersectionObserver notifications are
+// generated), and the delivery of these events varies between engines, making
+// this tricky to test in a non-flaky way.
+//
+// In particular, in WebKit, requestAnimationFrame and the generation of
+// a display frame are two separate tasks, so a step_timeout called within
+// requestAnimationFrame can fire before a display frame is generated.
+//
+// In Gecko, on the other hand, requestAnimationFrame and the generation of
+// a display frame are a single task, and IntersectionObserver notifications
+// are generated during this task. However, the task posted to deliver these
+// notifications can fire after the following requestAnimationFrame.
+//
+// This means that in general, by the time the second requestAnimationFrame
+// handler runs, we know that IntersectionObservations have been generated,
+// and that a task to deliver these notifications has been posted (though
+// possibly not yet delivered). Then, by the time the step_timeout() handler
+// runs, these notifications have been delivered.
+//
+// Since waitForNotification uses a double-rAF, it is now possible that
+// IntersectionObservers may have generated more notifications than what is
+// under test, but have not yet scheduled the new batch of notifications for
+// delivery. As a result, observer.takeRecords should NOT be used in tests:
+//
+// - myTestFunction0()
+// - waitForNotification(myTestFunction1)
+// - requestAnimationFrame()
+// - Modify DOM in a way that should trigger an IntersectionObserver callback.
+// - BeginFrame
+// - requestAnimationFrame handler runs
+// - Second requestAnimationFrame()
+// - Style, layout, paint
+// - IntersectionObserver generates a batch of notifications
+// - Posts a task to deliver notifications
+// - Task to deliver IntersectionObserver notifications runs
+// - IntersectionObserver callbacks run
+// - BeginFrame
+// - Second requestAnimationFrameHandler runs
+// - step_timeout()
+// - IntersectionObserver generates another batch of notifications
+// - Post task to deliver notifications
+// - step_timeout handler runs
+// - myTestFunction1()
+// - At this point, observer.takeRecords will get the second batch of
+// notifications.
+function waitForNotification(t, f) {
+ return new Promise(resolve => {
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ let callback = function() {
+ resolve();
+ if (f) {
+ f();
+ }
+ };
+ if (t) {
+ t.step_timeout(callback);
+ } else {
+ setTimeout(callback);
+ }
+ });
+ });
+ });
+}
+
+// If you need to wait until the IntersectionObserver algorithm has a chance
+// to run, but don't need to wait for delivery of the notifications...
+function waitForFrame(t, f) {
+ return new Promise(resolve => {
+ requestAnimationFrame(function() {
+ t.step_timeout(function() {
+ resolve();
+ if (f) {
+ f();
+ }
+ });
+ });
+ });
+}
+
+// The timing of when runTestCycle is called is important. It should be
+// called:
+//
+// - Before or during the window load event, or
+// - Inside of a prior runTestCycle callback, *before* any assert_* methods
+// are called.
+//
+// Following these rules will ensure that the test suite will not abort before
+// all test steps have run.
+//
+// If the 'delay' parameter to the IntersectionObserver constructor is used,
+// tests will need to add the same delay to their runTestCycle invocations, to
+// wait for notifications to be generated and delivered.
+function runTestCycle(f, description, delay) {
+ async_test(function(t) {
+ if (delay) {
+ step_timeout(() => {
+ waitForNotification(t, t.step_func_done(f));
+ }, delay);
+ } else {
+ waitForNotification(t, t.step_func_done(f));
+ }
+ }, description);
+}
+
+// Root bounds for a root with an overflow clip as defined by:
+// http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle
+function contentBounds(root) {
+ var left = root.offsetLeft + root.clientLeft;
+ var right = left + root.clientWidth;
+ var top = root.offsetTop + root.clientTop;
+ var bottom = top + root.clientHeight;
+ return [left, right, top, bottom];
+}
+
+// Root bounds for a root without an overflow clip as defined by:
+// http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle
+function borderBoxBounds(root) {
+ var left = root.offsetLeft;
+ var right = left + root.offsetWidth;
+ var top = root.offsetTop;
+ var bottom = top + root.offsetHeight;
+ return [left, right, top, bottom];
+}
+
+function clientBounds(element) {
+ var rect = element.getBoundingClientRect();
+ return [rect.left, rect.right, rect.top, rect.bottom];
+}
+
+function rectArea(rect) {
+ return (rect.left - rect.right) * (rect.bottom - rect.top);
+}
+
+function checkRect(actual, expected, description, all) {
+ if (!expected.length)
+ return;
+ assert_equals(actual.left | 0, expected[0] | 0, description + '.left');
+ assert_equals(actual.right | 0, expected[1] | 0, description + '.right');
+ assert_equals(actual.top | 0, expected[2] | 0, description + '.top');
+ assert_equals(actual.bottom | 0, expected[3] | 0, description + '.bottom');
+}
+
+function checkLastEntry(entries, i, expected) {
+ assert_equals(entries.length, i + 1, 'entries.length');
+ if (expected) {
+ checkRect(
+ entries[i].boundingClientRect, expected.slice(0, 4),
+ 'entries[' + i + '].boundingClientRect', entries[i]);
+ checkRect(
+ entries[i].intersectionRect, expected.slice(4, 8),
+ 'entries[' + i + '].intersectionRect', entries[i]);
+ checkRect(
+ entries[i].rootBounds, expected.slice(8, 12),
+ 'entries[' + i + '].rootBounds', entries[i]);
+ if (expected.length > 12) {
+ assert_equals(
+ entries[i].isIntersecting, expected[12],
+ 'entries[' + i + '].isIntersecting');
+ }
+ }
+}
+
+function checkJsonEntry(actual, expected) {
+ checkRect(
+ actual.boundingClientRect, expected.boundingClientRect,
+ 'entry.boundingClientRect');
+ checkRect(
+ actual.intersectionRect, expected.intersectionRect,
+ 'entry.intersectionRect');
+ if (actual.rootBounds == 'null')
+ assert_equals(expected.rootBounds, 'null', 'rootBounds is null');
+ else
+ checkRect(actual.rootBounds, expected.rootBounds, 'entry.rootBounds');
+ assert_equals(actual.isIntersecting, expected.isIntersecting);
+ assert_equals(actual.target, expected.target);
+}
+
+function checkJsonEntries(actual, expected, description) {
+ test(function() {
+ assert_equals(actual.length, expected.length);
+ for (var i = 0; i < actual.length; i++)
+ checkJsonEntry(actual[i], expected[i]);
+ }, description);
+}
+
+function checkIsIntersecting(entries, i, expected) {
+ assert_equals(entries[i].isIntersecting, expected,
+ 'entries[' + i + '].target.isIntersecting equals ' + expected);
+}