mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-03 14:50:18 +00:00
Tests: Import WPT test for edge inclusivity in IntersectionObserver
This commit is contained in:
parent
105096e75a
commit
4e653c99bb
Notes:
github-actions[bot]
2025-02-16 17:10:08 +00:00
Author: https://github.com/awesomekling
Commit: 4e653c99bb
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3593
3 changed files with 295 additions and 0 deletions
|
@ -0,0 +1,11 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 5 tests
|
||||||
|
|
||||||
|
2 Pass
|
||||||
|
3 Fail
|
||||||
|
Pass IntersectionObserver should detect and report edge-adjacent and zero-area intersections.
|
||||||
|
Pass First rAF.
|
||||||
|
Fail Set transform=translateY(200px) on target.
|
||||||
|
Fail Set transform=translateY(201px) on target.
|
||||||
|
Fail Set transform=translateY(185px) on target.
|
|
@ -0,0 +1,67 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<script src="../resources/testharness.js"></script>
|
||||||
|
<script src="../resources/testharnessreport.js"></script>
|
||||||
|
<script src="./resources/intersection-observer-test-utils.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
pre, #log {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 200px;
|
||||||
|
}
|
||||||
|
#root {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
#target {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="root">
|
||||||
|
<div id="target" style="width: 100px; height: 100px; transform: translateY(250px)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var entries = [];
|
||||||
|
|
||||||
|
runTestCycle(function() {
|
||||||
|
var root = document.getElementById('root');
|
||||||
|
assert_true(!!root, "root element exists.");
|
||||||
|
var target = document.getElementById('target');
|
||||||
|
assert_true(!!target, "target element exists.");
|
||||||
|
var observer = new IntersectionObserver(function(changes) {
|
||||||
|
entries = entries.concat(changes);
|
||||||
|
}, { root: root });
|
||||||
|
observer.observe(target);
|
||||||
|
entries = entries.concat(observer.takeRecords());
|
||||||
|
assert_equals(entries.length, 0, "No initial notifications.");
|
||||||
|
runTestCycle(step0, "First rAF.");
|
||||||
|
}, "IntersectionObserver should detect and report edge-adjacent and zero-area intersections.");
|
||||||
|
|
||||||
|
function step0() {
|
||||||
|
runTestCycle(step1, "Set transform=translateY(200px) on target.");
|
||||||
|
checkLastEntry(entries, 0, [8, 108, 258, 358, 0, 0, 0, 0, 8, 208, 8, 208, false]);
|
||||||
|
target.style.transform = "translateY(200px)";
|
||||||
|
}
|
||||||
|
|
||||||
|
function step1() {
|
||||||
|
runTestCycle(step2, "Set transform=translateY(201px) on target.");
|
||||||
|
checkLastEntry(entries, 1, [8, 108, 208, 308, 8, 108, 208, 208, 8, 208, 8, 208, true]);
|
||||||
|
target.style.transform = "translateY(201px)";
|
||||||
|
}
|
||||||
|
|
||||||
|
function step2() {
|
||||||
|
runTestCycle(step3, "Set transform=translateY(185px) on target.");
|
||||||
|
checkLastEntry(entries, 2);
|
||||||
|
target.style.height = "0px";
|
||||||
|
target.style.width = "300px";
|
||||||
|
target.style.transform = "translateY(185px)";
|
||||||
|
}
|
||||||
|
|
||||||
|
function step3() {
|
||||||
|
checkLastEntry(entries, 3, [8, 308, 193, 193, 8, 208, 193, 193, 8, 208, 8, 208, true]);
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -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, epsilon = 0) {
|
||||||
|
if (!expected.length)
|
||||||
|
return;
|
||||||
|
assert_approx_equals(actual.left, expected[0], epsilon, description + '.left');
|
||||||
|
assert_approx_equals(actual.right, expected[1], epsilon, description + '.right');
|
||||||
|
assert_approx_equals(actual.top, expected[2], epsilon, description + '.top');
|
||||||
|
assert_approx_equals(actual.bottom, expected[3], epsilon, description + '.bottom');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLastEntry(entries, i, expected, epsilon = 0) {
|
||||||
|
assert_equals(entries.length, i + 1, 'entries.length');
|
||||||
|
if (expected) {
|
||||||
|
checkRect(
|
||||||
|
entries[i].boundingClientRect, expected.slice(0, 4),
|
||||||
|
'entries[' + i + '].boundingClientRect', epsilon);
|
||||||
|
checkRect(
|
||||||
|
entries[i].intersectionRect, expected.slice(4, 8),
|
||||||
|
'entries[' + i + '].intersectionRect', epsilon);
|
||||||
|
checkRect(
|
||||||
|
entries[i].rootBounds, expected.slice(8, 12),
|
||||||
|
'entries[' + i + '].rootBounds', epsilon);
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue