diff options
Diffstat (limited to 'testing/web-platform/tests/layout-instability')
85 files changed, 3250 insertions, 0 deletions
diff --git a/testing/web-platform/tests/layout-instability/META.yml b/testing/web-platform/tests/layout-instability/META.yml new file mode 100644 index 0000000000..10c6aa36ce --- /dev/null +++ b/testing/web-platform/tests/layout-instability/META.yml @@ -0,0 +1,4 @@ +spec: https://wicg.github.io/layout-instability/ +suggested_reviewers: + - skobes + - npm1 diff --git a/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-contain.html b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-contain.html new file mode 100644 index 0000000000..749cfe172a --- /dev/null +++ b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-contain.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: parent and contained absolute child moved together</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="parent" style="position: relative; width: 100px; height: 100px; background: yellow"> + <div id="child" style="position: absolute; width: 100px; height: 100px; background: blue"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const parent = document.querySelector("#parent"); + parent.style.top = '100px'; + + // Parent contains child, so score only once. + const expectedScore = computeExpectedScore(100 * (100 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Parent and contained absolute child movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-negative-overflow.html b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-negative-overflow.html new file mode 100644 index 0000000000..21caa263c9 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-negative-overflow.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: parent and overflowing absolute child moved together</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="parent" style="position: relative; left: 400px; top: 300px; width: 100px; height: 100px; background: yellow"> + <div id="child" style="position: absolute; top: -300px; left: -400px; width: 100px; height: 100px; background: blue"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const parent = document.querySelector("#parent"); + parent.style.top = '400px'; + + // We should track parent and child separately. + const expectedScore = computeExpectedScore(100 * (100 + 100), 100) * 2; + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Parent and overflowing absolute child movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-overflow.html b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-overflow.html new file mode 100644 index 0000000000..24dda875c7 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-overflow.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: parent and overflowing absolute child moved together</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="parent" style="position: relative; width: 100px; height: 100px; background: yellow"> + <div id="child" style="position: absolute; top: 300px; left: 400px; width: 100px; height: 100px; background: blue"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const parent = document.querySelector("#parent"); + parent.style.top = '100px'; + + // We should track parent and child separately. + const expectedScore = computeExpectedScore(100 * (100 + 100), 100) * 2; + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Parent and overflowing absolute child movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-will-change.html b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-will-change.html new file mode 100644 index 0000000000..1597f10892 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/absolute-child-shift-with-parent-will-change.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: parent and overflowing absolute child moved together</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="parent" style="position: relative; width: 100px; height: 100px; background: yellow; will-change: transform"> + <div id="child" style="position: absolute; top: 300px; left: 400px; width: 100px; height: 100px; background: blue"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const parent = document.querySelector("#parent"); + parent.style.top = '100px'; + + // We should track parent and child separately. + const expectedScore = computeExpectedScore(100 * (100 + 100), 100) * 2; + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Parent and overflowing absolute child movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/add-remove-position-fixed.html b/testing/web-platform/tests/layout-instability/add-remove-position-fixed.html new file mode 100644 index 0000000000..19a109e8d7 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/add-remove-position-fixed.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Layout Instability: no shift for adding/removing position:fixed</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="width: 100px; height: 100px; background: green; + position: absolute; top: 200px"></div> +<div style="height: 2000px"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + window.scrollTo(0, 1000); + target.style.position = 'fixed'; + target.style.top = 0; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); + + window.scrollTo(0, 100); + target.style.position = 'absolute'; + target.style.top = '200px'; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "No shift for adding/removing position:fixed."); + +</script> + + diff --git a/testing/web-platform/tests/layout-instability/add-remove-position-sticky.html b/testing/web-platform/tests/layout-instability/add-remove-position-sticky.html new file mode 100644 index 0000000000..a269dd1569 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/add-remove-position-sticky.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Layout Instability: no shift for adding/removing position:sticky</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="width: 100px; height: 100px; background: green"></div> +<div style="height: 2000px"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + target.style.position = 'sticky'; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); + + target.style.position = 'static'; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "No shift for adding/removing position:sticky."); + +</script> + + diff --git a/testing/web-platform/tests/layout-instability/body-display-change.html b/testing/web-platform/tests/layout-instability/body-display-change.html new file mode 100644 index 0000000000..0576bd6865 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/body-display-change.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>Layout Instability: shift accompanied by body display change</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#cont { + background: white; + width: 300px; + height: 200px; +} +#ch { + background: blue; + position: relative; + width: 300px; + height: 100px; + top: 100px; +} + +</style> +<style id="s"></style> +<div id="cont"> + <div id="ch"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + await waitForAnimationFrames(2); + + document.querySelector("#s").innerHTML = ` + body { + display: flex; + flex-direction: column; + } + #ch { top: 0; } + `; + + // An element of size (300 x 100) has shifted by 100px. + const expectedScore = computeExpectedScore(300 * (100 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Shift accompanied by body display change.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/buffer-layout-shift.html b/testing/web-platform/tests/layout-instability/buffer-layout-shift.html new file mode 100644 index 0000000000..1db0452497 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/buffer-layout-shift.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Layout Instability entries are not available via the performance timeline</title> +<body> +<style> +#myDiv { position: relative; width: 300px; height: 100px; background: blue; } +</style> +<div id='myDiv'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +promise_test(async t => { + assert_implements(window.LayoutShift, 'Layout Instability is not supported.'); + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + const startTime = performance.now(); + return new Promise(resolve => { + new PerformanceObserver(t.step_func(list => { + const endTime = performance.now(); + assert_equals(list.getEntries().length, 1); + const entry = list.getEntries()[0]; + assert_equals(entry.entryType, "layout-shift"); + assert_equals(entry.name, ""); + assert_greater_than_equal(entry.startTime, startTime); + assert_less_than_equal(entry.startTime, endTime); + assert_equals(entry.duration, 0.0); + // The layout shift value should be: + // 300 * (100 + 60) * (60 / maxDimension) / viewport size. + assert_equals(entry.value, computeExpectedScore(300 * (100 + 60), 60)); + + // The entry should not be available via getEntries* methods. + assert_equals(performance.getEntriesByType('layout-shift').length, 0, 'getEntriesByType should have no layout-shift entries'); + assert_equals(performance.getEntriesByName('', 'layout-shift').length, 0, 'getEntriesByName should have no layout-shift entries'); + assert_equals(performance.getEntries().filter(e => e.entryType === 'layout-shift').length, 0, 'getEntries should have no layout-shift entries'); + resolve(); + })).observe({type: 'layout-shift'}); + // Modify the position of the div. + document.getElementById('myDiv').style = "top: 60px"; + }); +}, 'Layout shift before onload is not buffered into the performance timeline.'); +</script> + +</body> diff --git a/testing/web-platform/tests/layout-instability/buffered-flag.html b/testing/web-platform/tests/layout-instability/buffered-flag.html new file mode 100644 index 0000000000..a2e91797ce --- /dev/null +++ b/testing/web-platform/tests/layout-instability/buffered-flag.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Layout Instability: PerformanceObserver sees entries with buffered flag</title> +<body> +<style> +#myDiv { position: relative; width: 300px; height: 100px; background: blue; } +</style> +<div id='myDiv'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +promise_test(async t => { + assert_implements(window.LayoutShift, 'Layout Instability is not supported.'); + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + const startTime = performance.now(); + return new Promise(resolve => { + // First observer creates second in callback to ensure the entry has been dispatched by the time + // the second observer begins observing. + new PerformanceObserver(() => { + const endTime = performance.now(); + // Second observer requires 'buffered: true' to see entries. + new PerformanceObserver(t.step_func(list => { + assert_equals(list.getEntries().length, 1); + const entry = list.getEntries()[0]; + assert_equals(entry.entryType, "layout-shift"); + assert_greater_than_equal(entry.startTime, startTime); + assert_less_than_equal(entry.startTime, endTime); + assert_equals(entry.duration, 0.0); + assert_equals(entry.value, computeExpectedScore(300 * (100 + 60), 60)); + resolve(); + })).observe({'type': 'layout-shift', buffered: true}); + }).observe({type: 'layout-shift'}); + // Modify the position of the div to cause a layout-shift entry. + document.getElementById('myDiv').style = "top: 60px"; + }); +}, 'PerformanceObserver with buffered flag sees previous layout-shift entry.'); +</script> +</body> diff --git a/testing/web-platform/tests/layout-instability/child-shift-with-parent-overflow-hidden.html b/testing/web-platform/tests/layout-instability/child-shift-with-parent-overflow-hidden.html new file mode 100644 index 0000000000..f7e9e022b9 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/child-shift-with-parent-overflow-hidden.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Layout Instability: parent (with overflow:hidden) and child moved together</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="parent" style="position: relative; width: 200px; height: 200px; + border: 50px solid blue; overflow: hidden"> + <div id="child" style="width: 400px; height: 400px; background: blue"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const parent = document.querySelector("#parent"); + parent.style.top = '100px'; + + // Only the parent area should be reported. + const expectedScore = computeExpectedScore(300 * (300 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Parent (with overflow:hidden) and child moved together.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/child-shift-with-parent-overflow-x-clip.html b/testing/web-platform/tests/layout-instability/child-shift-with-parent-overflow-x-clip.html new file mode 100644 index 0000000000..7a85d16668 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/child-shift-with-parent-overflow-x-clip.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Layout Instability: parent/child moved together with overflow-x: clip</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="parent" style="position: relative; width: 100px; height: 100px; border: 100px solid blue; overflow-x: clip"> + <div id="child" style="width: 1000px; height: 300px; background: blue"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const parent = document.querySelector("#parent"); + parent.style.top = '100px'; + + const expectedScore = computeExpectedScore(300 * (400 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Parent/child movement with overflow-x: clip.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/child-shift-with-parent.html b/testing/web-platform/tests/layout-instability/child-shift-with-parent.html new file mode 100644 index 0000000000..e23bfd0c94 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/child-shift-with-parent.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Layout Instability: parent/child moved together</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="parent" style="position: relative; width: 100px; height: 100px; border: 100px solid blue"> + <div id="child" style="height: 300px"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const parent = document.querySelector("#parent"); + parent.style.top = '100px'; + + const expectedScore = computeExpectedScore(300 * (400 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Parent/child movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/clip-negative-bottom-margin.html b/testing/web-platform/tests/layout-instability/clip-negative-bottom-margin.html new file mode 100644 index 0000000000..2c329d9fcd --- /dev/null +++ b/testing/web-platform/tests/layout-instability/clip-negative-bottom-margin.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Layout Instability: clip with negative bottom margin</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +#scroller { overflow: scroll; width: 200px; height: 500px; } +#space { height: 1000px; margin-bottom: -500px; } +#j { width: 150px; height: 150px; background: yellow; } + +</style> +<div id='scroller'> + <div id='space'></div> + <div id='j'></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Increase j's top margin by 100px. Since j is fully clipped by the scroller, + // this should not generate a shift. + document.querySelector("#j").style.marginTop = "100px"; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Clip with negative bottom margin."); + +</script> + + diff --git a/testing/web-platform/tests/layout-instability/composited-element-movement.html b/testing/web-platform/tests/layout-instability/composited-element-movement.html new file mode 100644 index 0000000000..c990690335 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/composited-element-movement.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title>Layout Instability: element with compositing layer hint</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +#shift { + position: relative; + width: 500px; + height: 200px; + background: yellow; +} +#container { + position: absolute; + width: 350px; + height: 400px; + right: 50px; + top: 100px; + background: #ccc; +} +.promote { will-change: transform; } + +</style> +<div id="container" class="promote"> + <div id="space"></div> + <div id="shift" class="promote"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Induce a shift. + document.querySelector("#space").style = "height: 100px"; + + // #shift is 400x200 after viewport intersection with correct application of + // composited #container offset, and 100px lower after shifting, so impact + // region is 400 * (200 + 100). + const expectedScore = computeExpectedScore(400 * (200 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, "Element with compositing layer hint."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/contain-paint-fully-clipped.html b/testing/web-platform/tests/layout-instability/contain-paint-fully-clipped.html new file mode 100644 index 0000000000..3c0ec726b2 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/contain-paint-fully-clipped.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>Layout Instability: fully clipped by contain:paint</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div style="contain: paint; height: 0; position: relative"> + <div id="target" style="position: absolute; top: 0; width: 400px; height: 400px; background: blue"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift target, for which no shift should be reported because it's hidden. + document.querySelector("#target").style.top = '200px'; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'fully clipped by contain:paint'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/content-visibility-auto-offscreen.html b/testing/web-platform/tests/layout-instability/content-visibility-auto-offscreen.html new file mode 100644 index 0000000000..9e4361b38b --- /dev/null +++ b/testing/web-platform/tests/layout-instability/content-visibility-auto-offscreen.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>Layout Instability: off-screen content-visibility:auto content</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +// These scripts need to be before the contents because we need to ensure no +// layout shifts during page load. +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + window.scrollTo(0, 100000 + 100); + await waitForAnimationFrames(2); + + assert_equals(watcher.score, 0); + + // This should report a layout shift as target is now visible. + target.style.top = '100100px'; + + await watcher.promise; + const expectedScore = computeExpectedScore(100 * 100, 100); + assert_equals(watcher.score, expectedScore); + + // No new layout shift should be reported when target is scrolled out of screeen. + window.scrollTo(0, 0); + await waitForAnimationFrames(2); + + assert_equals(watcher.score, expectedScore); +}, 'off-screen content-visibility:auto'); +</script> +<style> + .auto { + content-visibility: auto; + contain-intrinsic-size: 1px; + width: 100px; + } +</style> +<div class=auto> + <div style="width: 100px; height: 100px; background: blue"></div> +</div> +<div id="target" class=auto style="position: relative; top: 100000px"> + <div style="width: 100px; height: 100px; background: blue"></div> +</div>
\ No newline at end of file diff --git a/testing/web-platform/tests/layout-instability/content-visibility-auto-onscreen.html b/testing/web-platform/tests/layout-instability/content-visibility-auto-onscreen.html new file mode 100644 index 0000000000..a7fbd92995 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/content-visibility-auto-onscreen.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Layout Instability: on-screen content-visibility:auto content</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +// These scripts need to be before the contents because we need to ensure no +// layout shifts during page load. +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + assert_equals(watcher.score, 0); +}, 'on-screen content-visibility:auto'); +</script> +<style> + #target { + content-visibility: auto; + contain-intrinsic-size: 1px; + width: 100px; + } +</style> +<div id=target> + <div style="width: 100px; height: 100px; background: blue"></div> +</div> diff --git a/testing/web-platform/tests/layout-instability/content-visibility-auto-resize.html b/testing/web-platform/tests/layout-instability/content-visibility-auto-resize.html new file mode 100644 index 0000000000..bb2d2e5c71 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/content-visibility-auto-resize.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: resizing content-visibility:auto content</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +// These scripts need to be before the contents because we need to ensure no +// layout shifts during page load. +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + assert_equals(watcher.score, 0); +}, 'off-screen content-visibility:auto'); +</script> +<style> + .auto { + content-visibility: auto; + contain-intrinsic-size: 10px 3000px; + width: 100px; + } + .contained { + height: 100px; + background: blue; + } +</style> +<div class=auto><div class=contained></div></div> +<div class=auto><div class=contained></div></div> diff --git a/testing/web-platform/tests/layout-instability/content-visibility-hidden.html b/testing/web-platform/tests/layout-instability/content-visibility-hidden.html new file mode 100644 index 0000000000..f84cc2543e --- /dev/null +++ b/testing/web-platform/tests/layout-instability/content-visibility-hidden.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Layout Instability: content-visibility:hidden content</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +// These scripts need to be before the contents because we need to ensure no +// layout shifts during page load. +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + assert_equals(watcher.score, 0); +}, 'on-screen content-visibility:auto'); +</script> +<style> + #target { + content-visibility: hidden; + contain-intrinsic-size: 1px; + width: 100px; + } +</style> +<div id=target> + <div style="width: 100px; height: 100px; background: blue"></div> +</div> diff --git a/testing/web-platform/tests/layout-instability/display-change-with-transform.html b/testing/web-platform/tests/layout-instability/display-change-with-transform.html new file mode 100644 index 0000000000..75eb5c5870 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/display-change-with-transform.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Layout Instability: change display type with transform</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + div { + width: 100px; + height: 100px; + background: blue; + } + #target { + transform: translateX(0); + } +</style> +<div id=target> + <div id=shift>test</div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + await waitForAnimationFrames(2); + + target.style.display = 'flex'; + + await waitForAnimationFrames(1); + + assert_equals(watcher.score, 0); +}, 'Shift accompanied by body display change.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/expand-above-viewport.html b/testing/web-platform/tests/layout-instability/expand-above-viewport.html new file mode 100644 index 0000000000..f1e3f704b7 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/expand-above-viewport.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title>Layout Instability: layout shift when content expanded above the viewport</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<style> +body { + margin: 0; + /* To avoid browser automatic scroll offset adjustment for the expansion. */ + overflow-anchor: none; +} +</style> +<div id="expander" style="height: 50vh"></div> +<div id="shifted" style="width: 300px; height: 300vh; background: blue"></div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + const viewHeight = document.documentElement.clientHeight; + window.scrollTo(0, viewHeight); + + await waitForAnimationFrames(2); + + // Expander expands to push |shifted| down. + expander.style.height = '150vh'; + + const expectedScore1 = computeExpectedScore(300 * viewHeight, viewHeight); + + // Observer fires after the frame is painted. + cls_expect(watcher, {score: 0}); + await watcher.promise; + cls_expect(watcher, {score: expectedScore1}); + + // Expander expands to push |shifted| out of viewport. + expander.style.height = '200vh'; + + const expectedScore2 = expectedScore1 + + computeExpectedScore(0.5 * 300 * viewHeight, 0.5 * viewHeight); + await watcher.promise; + cls_expect(watcher, {score: expectedScore2}); +}, "Layout shift when content expanded above the viewport"); + +</script> diff --git a/testing/web-platform/tests/layout-instability/fixed-position-move.html b/testing/web-platform/tests/layout-instability/fixed-position-move.html new file mode 100644 index 0000000000..877c2ba4b3 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/fixed-position-move.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Layout Instability: movement of fixed position</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="target" style="position: fixed; top: 100px; width: 300px; height: 200px; background: yellow"> +</div> +<script> +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + target.style.top = '200px'; + + const expectedScore = computeExpectedScore(300 * (200 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Movement of fixed position'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/fully-clipped-visual-rect.html b/testing/web-platform/tests/layout-instability/fully-clipped-visual-rect.html new file mode 100644 index 0000000000..cf308f2634 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/fully-clipped-visual-rect.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Layout Instability: fully clipped visual rect</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#clip { width: 0px; height: 600px; overflow: hidden; } +#j { position: relative; width: 300px; height: 200px; background: blue; } + +</style> +<div id='clip'><div id='j'></div></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift an element that is fully clipped by its container. + document.querySelector("#j").style.top = "200px"; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Fully clipped visual rect."); + +</script> + + diff --git a/testing/web-platform/tests/layout-instability/idlharness.html b/testing/web-platform/tests/layout-instability/idlharness.html new file mode 100644 index 0000000000..ea6efb27f8 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/idlharness.html @@ -0,0 +1,50 @@ +<!doctype html> +<title>Layout Instability IDL tests</title> +<meta name="timeout" content="long"> +<link rel="help" href="https://wicg.github.io/layout-instability/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="/resources/idlharness.js"></script> +<script src="resources/util.js"></script> +<script> +'use strict'; + +idl_test( + ['layout-instability'], + ['performance-timeline', 'geometry', 'dom', 'hr-time'], + async (idl_array, t) => { + idl_array.add_objects({ + LayoutShift: ['layoutShift'], + LayoutShiftAttribution: ['layoutShiftAttribution'], + }); + + // If LayoutShift isn't supported, avoid the timeout below and just let the + // objects declared above be null. The tests will still fail, but we will + // consistently generate the same set of subtests on all platforms. + if (!PerformanceObserver || + !PerformanceObserver.supportedEntryTypes || + !PerformanceObserver.supportedEntryTypes.includes('layout-shift')) { + return; + } + + // Make sure that the image has initially been laid out, so that the movement + // later is counted as a layout shift. + await waitForAnimationFrames(2); + + self.layoutShift = await new Promise((resolve, reject) => { + const observer = new PerformanceObserver(entryList => { + resolve(entryList.getEntries()[0]); + }); + observer.observe({type: 'layout-shift', buffered: true}); + t.step_timeout(() => reject('Timed out waiting for LayoutShift entry'), 3000); + + // Move the image, to cause layout shift. + image.style.marginTop = '100px'; + }); + self.layoutShiftAttribution = layoutShift.sources[0]; + } +); +</script> + +<img id="image" src="/images/green-100x50.png"> diff --git a/testing/web-platform/tests/layout-instability/ignore-fixed-and-sticky.html b/testing/web-platform/tests/layout-instability/ignore-fixed-and-sticky.html new file mode 100644 index 0000000000..2023dafbe0 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/ignore-fixed-and-sticky.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>Layout Instability: ignore fixed and sticky positioning</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { height: 2000px; } +#f1, #f2 { + position: fixed; + width: 300px; + height: 100px; + left: 100px; + background: yellow; +} +#f1 { top: 0; } +#f2 { top: 150px; will-change: transform; } +#s1 { + position: sticky; + width: 200px; + height: 100px; + left: 450px; + top: 0; + background: blue; +} + +</style> +<div id='f1'>fixed</div> +<div id='f2'>fixed composited</div> +<div id='s1'>sticky</div> +normal + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Scroll down by 50px. + document.scrollingElement.scrollTop = 50; + + // Force a layout. + document.body.style.height = "2500px"; + + // No layout shift should occur as a result of the scroll-triggered + // repositioning of fixed and sticky elements. + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, 'Ignore fixed and sticky.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/inline-flow-shift-one-line.html b/testing/web-platform/tests/layout-instability/inline-flow-shift-one-line.html new file mode 100644 index 0000000000..0c477e0e98 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/inline-flow-shift-one-line.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>Layout Instability: inline/text movement is detected</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div style="width: 200px; font-size: 20px; line-height: 25px"> + 1AAAAAAA<br> + 2AAAAAAA<br> + 3AAAAAAA<br> + <div id="inline-block" style="display: inline-block">4AAAAAAA</div><br> + <div id="shift" style="display: inline-block"></div>5AAAAAAA<br> + 6AAAAAAA<br> + 7AAAAAAA<br> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the width of |shift|. + document.querySelector("#shift").style.width = '50px'; + + // Only the line after |shift| is shifted right by 50px. + // The implementation may measure the real width of the shifted text + // or use the available width (i.e. width of the containing block). + // Also tolerate extra 10% error. + const text_width = document.querySelector("#inline-block").clientWidth; + const expectedScoreMin = computeExpectedScore((text_width + 50) * 20, 50) * 0.9; + const expectedScoreMax = computeExpectedScore(200 * 25, 50) * 1.1; + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_between_exclusive(watcher.score, expectedScoreMin, expectedScoreMax); +}, 'Inline flow movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/inline-flow-shift-vertical-rl.html b/testing/web-platform/tests/layout-instability/inline-flow-shift-vertical-rl.html new file mode 100644 index 0000000000..848883755c --- /dev/null +++ b/testing/web-platform/tests/layout-instability/inline-flow-shift-vertical-rl.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>Layout Instability: vertical-rl inline/text movement is detected</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<body style="writing-mode: vertical-rl"> +<div style="height: 200px; font-size: 20px; line-height: 25px"> + 1AAAAAAA<br> + 2AAAAAAA<br> + 3AAAAAAA<br> + <div id="inline-block" style="display: inline-block; width: 50px">4AAAAAAA</div><br> + 5AAAAAAA<br> + 6AAAAAAA<br> + 7AAAAAAA<br> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const inline_block = document.querySelector("#inline-block"); + inline_block.style.width = '100px'; + + // The lines below the inline-block are shifted down by 50px. + // The implementation may measure the real width of the shifted text + // or use the available width (i.e. width of the containing block). + // Also tolerate extra 10% error. + const text_width = inline_block.offsetWidth; + const expectedScoreMin = computeExpectedScore(text_width * (20 * 3 + 50), 50) * 0.9; + const expectedScoreMax = computeExpectedScore(200 * (25 * 3 + 50), 50) * 1.1; + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_between_exclusive(watcher.score, expectedScoreMin, expectedScoreMax); +}, 'Vertical-rl inline flow movement.'); + +</script> +</body> diff --git a/testing/web-platform/tests/layout-instability/inline-flow-shift.html b/testing/web-platform/tests/layout-instability/inline-flow-shift.html new file mode 100644 index 0000000000..0385f29c2f --- /dev/null +++ b/testing/web-platform/tests/layout-instability/inline-flow-shift.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>Layout Instability: inline/text movement is detected</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div style="width: 200px; font-size: 20px; line-height: 25px"> + 1AAAAAAA<br> + 2AAAAAAA<br> + 3AAAAAAA<br> + <div id="inline-block" style="display: inline-block; height: 50px">4AAAAAAA</div><br> + 5AAAAAAA<br> + 6AAAAAAA<br> + 7AAAAAAA<br> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const inline_block = document.querySelector("#inline-block"); + inline_block.style.height = '100px'; + + // The lines below the inline-block are shifted down by 50px. + // The implementation may measure the real width of the shifted text + // or use the available width (i.e. width of the containing block). + // Also tolerate extra 10% error. + const text_width = inline_block.offsetWidth; + const expectedScoreMin = computeExpectedScore(text_width * (30 * 3 + 50), 50) * 0.9; + const expectedScoreMax = computeExpectedScore(200 * (30 * 3 + 50), 50) * 1.1; + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_between_exclusive(watcher.score, expectedScoreMin, expectedScoreMax); +}, 'Inline flow movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/input-timestamp.html b/testing/web-platform/tests/layout-instability/input-timestamp.html new file mode 100644 index 0000000000..ba31d47866 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/input-timestamp.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Layout Instability: observe timestamp after user input</title> + +<body> + <style> + #myDiv { + position: relative; + width: 300px; + height: 100px; + background: blue; + } + + /* Disable the button's focus ring, which otherwise expands its visual rect by + * 1px on all sides, triggering a layout shift event. + */ + #button { + outline: none; + } + </style> + <div id='myDiv'></div> + <button id='button'>Generate a 'click' event</button> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src=/resources/testdriver.js></script> + <script src=/resources/testdriver-vendor.js></script> + <script src=resources/util.js></script> + <script src=/event-timing/resources/event-timing-test-utils.js></script> + <script> + let timeAfterClick; + + promise_test(async t => { + assert_implements(window.LayoutShift, 'Layout Instability is not supported.'); + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + const startTime = performance.now(); + return new Promise(resolve => { + const observer = new PerformanceObserver( + t.step_func(entryList => { + const endTime = performance.now(); + assert_equals(entryList.getEntries().length, 1); + const entry = entryList.getEntries()[0]; + assert_equals(entry.entryType, "layout-shift"); + assert_equals(entry.name, ""); + assert_greater_than_equal(entry.startTime, startTime); + assert_less_than_equal(entry.startTime, endTime); + assert_equals(entry.duration, 0.0); + // The layout shift value should be: + // 300 * (100 + 60) * (60 / maxDimension) / viewport size. + assert_equals(entry.value, computeExpectedScore(300 * (100 + 60), 60)); + // We should see that there was a click input entry. + assert_equals(entry.hadRecentInput, false); + assert_greater_than_equal(timeAfterClick, entry.lastInputTime); + resolve(); + }) + ); + observer.observe({ entryTypes: ['layout-shift'] }); + // User input event + clickAndBlockMain('button').then(() => { + // 600ms delay + step_timeout(function() { + timeAfterClick = performance.now(); + // Modify the position of the div. + document.getElementById('myDiv').style = "top: 60px"; + }, 600); + }); + }); + }, 'Layout shift right after user input is observable via PerformanceObserver.'); + </script> + +</body> diff --git a/testing/web-platform/tests/layout-instability/local-shift-without-viewport-shift-2.html b/testing/web-platform/tests/layout-instability/local-shift-without-viewport-shift-2.html new file mode 100644 index 0000000000..7074bca7ba --- /dev/null +++ b/testing/web-platform/tests/layout-instability/local-shift-without-viewport-shift-2.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Layout Instability: local shift without viewport shift</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +#c { position: relative; width: 300px; height: 100px; scale: 0.1; } +#j { position: relative; width: 100px; height: 10px; background: blue; } + +</style> +<div id='c'> + <div id='j'></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + document.querySelector("#j").style.top = "4px"; + + // Make sure no shift score is reported, since the element didn't move in the + // viewport. + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Local shift without viewport shift."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/local-shift-without-viewport-shift.html b/testing/web-platform/tests/layout-instability/local-shift-without-viewport-shift.html new file mode 100644 index 0000000000..d635ed5056 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/local-shift-without-viewport-shift.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Layout Instability: local shift without viewport shift</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +#c { position: relative; width: 300px; height: 100px; transform: scale(0.1); } +#j { position: relative; width: 100px; height: 10px; background: blue; } + +</style> +<div id='c'> + <div id='j'></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + document.querySelector("#j").style.top = "4px"; + + // Make sure no shift score is reported, since the element didn't move in the + // viewport. + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Local shift without viewport shift."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/main-frame.html b/testing/web-platform/tests/layout-instability/main-frame.html new file mode 100644 index 0000000000..0d0bf84ddc --- /dev/null +++ b/testing/web-platform/tests/layout-instability/main-frame.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html> +<body> +<title>Layout Instability: subframe layout shift score</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #i { + border: 0; + position: absolute; + left: 0; + top: 0; + background-color: pink; + } +</style> +<iframe id="i" width="400" height="300" src="sub-frame.html"></iframe> + +<script> +const loadPromise = new Promise(resolve => { + window.addEventListener("load", () => { + resolve(true); + }); +}); + +let iframe = document.getElementById('i'); +const load_promise = new Promise(resolve => { + iframe.addEventListener('load', function() { + resolve(true); + }); +}); + +checkMainFrameLoad = async () => { + await loadPromise; + return true; +}; + +checkIFrameLoad = async () => { + // Wait for the iframe finishing loading + await load_promise; + return true; +}; + +promise_test(async t => { + checkMainFrameLoad(); + // Wait for the iframe finishing loading + checkIFrameLoad(); + + // Wait for the message sent from the iframe after it receives all the layout + // shifts. + await new Promise(resolve => { + window.addEventListener("message", (event) => { + if (event.data.type == "layout shift score") { + t.step(() => { + assert_equals(event.data.score, event.data.expectedScore); + }); + resolve(); + } + }, false); + }); +}, ""); +</script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/layout-instability/mousemove-becomes-drag.html b/testing/web-platform/tests/layout-instability/mousemove-becomes-drag.html new file mode 100644 index 0000000000..df4a416c81 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/mousemove-becomes-drag.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<title>Layout Instability: no shift in mouse moves with a button pressed</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; height: 2000px; } +#draggable { + top:50px; + left:50px; + width:50px; + height:50px; + background-color:blue; + position:absolute +} + +</style> +<div id="draggable" ></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/util.js"></script> +<script> + +const draggable = document.getElementById("draggable"); +draggable.addEventListener('mousemove', function(event) { + // Move the element when the mouse moves. + draggable.style.top = event.pageY - 25 + 'px'; + event.preventDefault(); +}, false); + +generateMouseMoveSequence = () => new test_driver.Actions() + .pointerMove(0, 0, {origin: draggable}) + .pointerDown() + .pointerMove(0, 15, {origin: draggable}) + .pause(100) + .pointerMove(0, 30, {origin: draggable}) + .pause(100) + .pointerMove(0, 45, {origin: draggable}) + .pause(100) + .pointerUp() + .pause(100); + +promise_test(async () => { + + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Send pointer events for a sequence of mouse actions, mouse down, mouse moves and mouse up. + await generateMouseMoveSequence().send(); + + // Mouse moves which drag the objects should be counted as the excluding inputs + // for the layout shift. + assert_greater_than(watcher.score, 0); + assert_equals(watcher.scoreWithInputExclusion, 0); + +}, "No layout shift when mouse moves with a button pressed."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/move-distance-clamped.html b/testing/web-platform/tests/layout-instability/move-distance-clamped.html new file mode 100644 index 0000000000..5854fe08d1 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/move-distance-clamped.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<title>Layout Instability: distance fraction not more than 1.0</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<style> +body { margin: 0; } +#shifter { + position: relative; + width: 100vw; + height: 100vh; + left: -2000vw; + top: -2000vh; + background: blue; +} +</style> +<div id="shifter"></div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + document.querySelector("#shifter").style = "left: 0; top: 0"; + + const docElement = document.documentElement; + const viewWidth = docElement.clientWidth; + const viewHeight = docElement.clientHeight; + + // An element the size of the viewport has shifted by a huge distance, but + // the move distance is effectively the viewport width or height (whichever + // is larger) as the distance fraction is limited to a maximum of 1.0. + const expectedScore = computeExpectedScore( + viewWidth * viewHeight, + Math.max(viewWidth, viewHeight)); + + // Observer fires after the frame is painted. + cls_expect(watcher, {score: 0}); + await watcher.promise; + cls_expect(watcher, {score: expectedScore}); +}, "Distance fraction not more than 1.0."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/move-transformed.html b/testing/web-platform/tests/layout-instability/move-transformed.html new file mode 100644 index 0000000000..2a7d048396 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/move-transformed.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>Layout Instability: shift of a transformed container</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +body { margin: 0; } +#transformed { position: relative; transform: translateX(20px); width: 100px; height: 100px; background: blue; } +</style> +<div id="transformed"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + document.querySelector("#transformed").style = "top: 50px"; + + const expectedScore = computeExpectedScore(100 * (100 + 50), 50); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Move transformed container'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/multi-clip-visual-rect.html b/testing/web-platform/tests/layout-instability/multi-clip-visual-rect.html new file mode 100644 index 0000000000..9237ad833e --- /dev/null +++ b/testing/web-platform/tests/layout-instability/multi-clip-visual-rect.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Layout Instability: multi clip visual rect</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#outer { width: 200px; height: 600px; overflow: hidden; } +#inner { width: 300px; height: 150px; overflow: hidden; } +#j { position: relative; width: 300px; height: 600px; background: blue; } + +</style> +<div id='outer'><div id='inner'><div id='j'></div></div></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + document.querySelector("#j").style.top = "-200px"; + + // Note that, while the element moves up 200px, its visibility is + // clipped at 0px,150px height, so the additional 200px of vertical + // move distance is not included in the score. + // (clip width 200) * (clip height 150) + const expectedScore = computeExpectedScore(200 * 150, 200); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, "Multi clip visual rect."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/multicol-000.html b/testing/web-platform/tests/layout-instability/multicol-000.html new file mode 100644 index 0000000000..c06cddd663 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/multicol-000.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="multicol" style="position:relative; columns:40; width:500px; column-gap:0;"> + <div style="height:4000px; background:black;"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + await waitForAnimationFrames(2); + + multicol.style.top = '100px'; + const expectedScore = computeExpectedScore(500 * (100 + 100), 100); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Move balanced multicol container'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/multicol-001.html b/testing/web-platform/tests/layout-instability/multicol-001.html new file mode 100644 index 0000000000..a47d5f0488 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/multicol-001.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="multicol" style="position:relative; columns:5; column-gap:0; column-gap:0; width:500px; height:1px;"> + <div style="contain:size; height:100px; background:hotpink;"></div> + <div style="contain:size; height:100px; background:hotpink;"></div> + <div style="contain:size; height:100px; background:hotpink;"></div> + <div style="contain:size; height:100px; background:hotpink;"></div> + <div style="contain:size; height:100px; background:hotpink;"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + await waitForAnimationFrames(2); + + multicol.style.top = '100px'; + const expectedScore = computeExpectedScore(500 * (100 + 100), 100); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Move multicol container with overflow'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/opacity-nonzero-to-zero.html b/testing/web-platform/tests/layout-instability/opacity-nonzero-to-zero.html new file mode 100644 index 0000000000..9ce0f2be9a --- /dev/null +++ b/testing/web-platform/tests/layout-instability/opacity-nonzero-to-zero.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Layout Instability: opacity:0</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="position: absolute; top: 0; width: 400px; height: 400px; background: blue;"> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift target, for which no shift should be reported because it's not visible. + target.style.top = '200px'; + target.style.opacity = 0; + + await waitForAnimationFrames(2); + assert_equals(watcher.score, 0); +}, 'opacity non-zero to zero'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/opacity-zero-layout-and-visible.html b/testing/web-platform/tests/layout-instability/opacity-zero-layout-and-visible.html new file mode 100644 index 0000000000..0172983cea --- /dev/null +++ b/testing/web-platform/tests/layout-instability/opacity-zero-layout-and-visible.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: opacity:0</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="position: absolute; top: 0; width: 200px; height: 200px; opacity: 0; background: blue"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift target, for which no shift should be reported because it's not visible. + target.style.top = '200px'; + target.style.opacity = 0.9; + + await waitForAnimationFrames(2); + assert_equals(watcher.score, 0); + + // Shift again, for which shift should be reported. + target.style.top = '300px'; + + await watcher.promise; + const expectedScore = computeExpectedScore(200 * (200 + 100), 100); + assert_equals(watcher.score, expectedScore); +}, 'opacity:0'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/opacity-zero.html b/testing/web-platform/tests/layout-instability/opacity-zero.html new file mode 100644 index 0000000000..edd90800a9 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/opacity-zero.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: opacity:0</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="position: absolute; top: 0; width: 400px; height: 400px; opacity: 0; background: blue"> + <div id="child" style="position: relative; top: 0; width: 200px; height: 200px; opacity: 0.5; background: yellow"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift target, for which no shift should be reported because it's not visible. + target.style.top = '200px'; + + await waitForAnimationFrames(2); + assert_equals(watcher.score, 0); + + // Shift child, for which no shift should be reported, either. + child.style.top = '100px'; + + await waitForAnimationFrames(2); + assert_equals(watcher.score, 0); +}, 'opacity:0'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/outline.html b/testing/web-platform/tests/layout-instability/outline.html new file mode 100644 index 0000000000..2b3704fd87 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/outline.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Layout Instability: outline doesn't contribute to layout shift</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div id="target" style="width: 300px; height: 300px; background: blue"></div> +<script> +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Add outline for target. This should not generate a shift. + target.style.outline = "10px solid blue"; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Outline."); +</script> diff --git a/testing/web-platform/tests/layout-instability/partially-clipped-visual-rect.html b/testing/web-platform/tests/layout-instability/partially-clipped-visual-rect.html new file mode 100644 index 0000000000..d8be37c8bf --- /dev/null +++ b/testing/web-platform/tests/layout-instability/partially-clipped-visual-rect.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Layout Instability: partially clipped visual rect</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#clip { width: 150px; height: 600px; overflow: hidden; } +#j { position: relative; width: 300px; height: 200px; background: blue; } + +</style> +<div id='clip'><div id='j'></div></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + document.querySelector("#j").style.top = "200px"; + + // (clipped width 150px) * (height 200 + movement 200) + const expectedScore = computeExpectedScore(150 * (200 + 200), 200); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, "Partially clipped visual rect."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/pointerdown-becomes-scroll.html b/testing/web-platform/tests/layout-instability/pointerdown-becomes-scroll.html new file mode 100644 index 0000000000..4fe5c6b7e7 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/pointerdown-becomes-scroll.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<title>Layout Instability: shift in pointerdown becoming scroll</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; height: 2000px; } +#box { + left: 0px; + top: 0px; + width: 400px; + height: 500px; + background: yellow; + position: relative; +} + +</style> +<div id="box"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/util.js"></script> +<script> + +const box = document.querySelector("#box"); +box.addEventListener("pointerdown", (e) => { + // Generate a layout shift before we know what type of input this pointer + // event sequence will become. + box.style.top = "100px"; + e.preventDefault(); +}); + +generateScrollSequence = () => new test_driver.Actions() + .addPointer("tp1", "touch") + .pointerMove(0, 100, {sourceName: "tp1"}) + .pointerDown({sourceName: "tp1"}) + .pointerMove(0, 0, {sourceName: "tp1"}) + .pause(100) + .pointerUp({sourceName: "tp1"}) + .pause(100); + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Send pointer events for a touch scroll. + await generateScrollSequence().send(); + + // The box is 400 x 500 and moves by 100px. + const expectedScore = computeExpectedScore(400 * (500 + 100), 100); + + // Both scores should increase (scroll doesn't count as input for the purpose + // of the LayoutShift.hadRecentInput bit). + assert_equals(watcher.score, expectedScore); + assert_equals(watcher.scoreWithInputExclusion, expectedScore); + +}, "Shift in pointerdown reported when it becomes a scroll."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/pointerdown-becomes-tap.html b/testing/web-platform/tests/layout-instability/pointerdown-becomes-tap.html new file mode 100644 index 0000000000..e2e7a911dc --- /dev/null +++ b/testing/web-platform/tests/layout-instability/pointerdown-becomes-tap.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Layout Instability: shift in pointerdown becoming tap</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; height: 2000px; } +#box { + left: 0px; + top: 0px; + width: 400px; + height: 500px; + background: yellow; + position: relative; +} + +</style> +<div id="box"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/util.js"></script> +<script> + +const box = document.querySelector("#box"); +box.addEventListener("pointerdown", (e) => { + // Generate a layout shift before we know what type of input this pointer + // event sequence will become. + box.style.top = "100px"; + e.preventDefault(); +}); + +generateTapSequence = () => new test_driver.Actions() + .addPointer("tp1", "touch") + .pointerMove(0, 0, {sourceName: "tp1"}) + .pointerDown({sourceName: "tp1"}) + .pause(100) + .pointerUp({sourceName: "tp1"}) + .pause(100); + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Send pointer events for a tap. + await generateTapSequence().send(); + + // The box is 400 x 500 and moves by 100px. + const expectedExcludedScore = computeExpectedScore(400 * (500 + 100), 100); + + // Only the score that ignores hadRecentInput should increase. + assert_equals(watcher.score, expectedExcludedScore); + assert_equals(watcher.scoreWithInputExclusion, 0); + +}, "Shift in pointerdown excluded when it becomes a tap."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/pointermove-becomes-drag.html b/testing/web-platform/tests/layout-instability/pointermove-becomes-drag.html new file mode 100644 index 0000000000..4bccf70423 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/pointermove-becomes-drag.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<title>Layout Instability: no shift in pointerdown becoming dragging</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#draggable { + top:50px; + left:50px; + width:50px; + height:50px; + background-color:blue; + position:absolute +} + +</style> +<div id="draggable" ></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/util.js"></script> +<script> + +const draggable = document.getElementById("draggable"); +draggable.addEventListener('touchmove', function(event) { + let touch = event.targetTouches[0]; + + // Move the element when the finger moves. + draggable.style.top = touch.pageY - 25 + 'px'; + event.preventDefault(); +}, false); + +generateTouchDragSequence = () => new test_driver.Actions() + .addPointer("touch1", "touch") + .pointerMove(0, 0, {origin: draggable}) + .pointerDown() + .pointerMove(0, 15, {origin: draggable}) + .pause(100) + .pointerMove(0, 30, {origin: draggable}) + .pause(100) + .pointerMove(0, 45, {origin: draggable}) + .pause(100) + .pointerUp() + .pause(100); + +promise_test(async(test) => { + const watcher = new ScoreWatcher; + let eventWatcher = new EventWatcher(test, draggable, ["pointerup"]); + let donePromise = eventWatcher.wait_for(["pointerup"], { record: 'all' }); + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Send pointer events for a touch drag. + await generateTouchDragSequence().send(); + + // wait for pointerUp before running the test + await donePromise; + + // Touch moves which drag the objects should be counted as the excluding inputs + // for the layout shift. + assert_greater_than(watcher.score, 0); + assert_equals(watcher.scoreWithInputExclusion, 0); + +}, "No Shift in pointerdown reported when it becomes a touch drag."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/recent-input.html b/testing/web-platform/tests/layout-instability/recent-input.html new file mode 100644 index 0000000000..2779d4ffe0 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/recent-input.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Layout Instability: observe after user input</title> +<body> +<style> +#myDiv { position: relative; width: 300px; height: 100px; background: blue; } + +/* Disable the button's focus ring, which otherwise expands its visual rect by + * 1px on all sides, triggering a layout shift event. + */ +#button { outline: none; } +</style> +<div id='myDiv'></div> +<button id='button'>Generate a 'click' event</button> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=resources/util.js></script> +<script src=/event-timing/resources/event-timing-test-utils.js></script> +<script> +let timeAfterClick; + +promise_test(async t => { + assert_implements(window.LayoutShift, 'Layout Instability is not supported.'); + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + const startTime = performance.now(); + return new Promise(resolve => { + const observer = new PerformanceObserver( + t.step_func(entryList => { + const endTime = performance.now(); + assert_equals(entryList.getEntries().length, 1); + const entry = entryList.getEntries()[0]; + assert_equals(entry.entryType, "layout-shift"); + assert_equals(entry.name, ""); + assert_greater_than_equal(entry.startTime, startTime); + assert_less_than_equal(entry.startTime, endTime); + assert_equals(entry.duration, 0.0); + // The layout shift value should be: + // 300 * (100 + 60) * (60 / maxDimension) / viewport size. + assert_equals(entry.value, computeExpectedScore(300 * (100 + 60), 60)); + // We should see that there was a click input entry. + assert_equals(entry.hadRecentInput, true); + assert_greater_than_equal(timeAfterClick, entry.lastInputTime); + resolve(); + }) + ); + observer.observe({entryTypes: ['layout-shift']}); + // User input event + clickAndBlockMain('button').then(() => { + timeAfterClick = performance.now(); + // Modify the position of the div. + document.getElementById('myDiv').style = "top: 60px"; + }); + }); +}, 'Layout shift right after user input is observable via PerformanceObserver.'); +</script> + +</body> diff --git a/testing/web-platform/tests/layout-instability/resources/slow-image.py b/testing/web-platform/tests/layout-instability/resources/slow-image.py new file mode 100644 index 0000000000..d9f09b8bca --- /dev/null +++ b/testing/web-platform/tests/layout-instability/resources/slow-image.py @@ -0,0 +1,6 @@ +import time + +def main(request, response): + # Sleep for 3s to delay onload. + time.sleep(3) + return [], b"" diff --git a/testing/web-platform/tests/layout-instability/resources/test-adapter.js b/testing/web-platform/tests/layout-instability/resources/test-adapter.js new file mode 100644 index 0000000000..3272790f7a --- /dev/null +++ b/testing/web-platform/tests/layout-instability/resources/test-adapter.js @@ -0,0 +1,5 @@ +// Abstracts expectations for reuse in different test frameworks. + +cls_expect = (watcher, expectation) => { + watcher.checkExpectation(expectation); +}; diff --git a/testing/web-platform/tests/layout-instability/resources/util.js b/testing/web-platform/tests/layout-instability/resources/util.js new file mode 100644 index 0000000000..597e2d7d84 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/resources/util.js @@ -0,0 +1,97 @@ +// Utilities for Layout Instability tests. + +// Returns a promise that is resolved when the specified number of animation +// frames has occurred. +waitForAnimationFrames = frameCount => { + return new Promise(resolve => { + const handleFrame = () => { + if (--frameCount <= 0) + resolve(); + else + requestAnimationFrame(handleFrame); + }; + requestAnimationFrame(handleFrame); + }); +}; + +// Returns a promise that is resolved when the next animation frame occurs. +waitForAnimationFrame = () => waitForAnimationFrames(1); + +// Helper to compute an expected layout shift score based on an expected impact +// region and max move distance for a particular animation frame. +computeExpectedScore = (impactRegionArea, moveDistance) => { + const docElement = document.documentElement; + + const viewWidth = docElement.clientWidth; + const viewHeight = docElement.clientHeight; + + const viewArea = viewWidth * viewHeight; + const viewMaxDim = Math.max(viewWidth, viewHeight); + + const impactFraction = impactRegionArea / viewArea; + const distanceFraction = moveDistance / viewMaxDim; + + return impactFraction * distanceFraction; +}; + +// An list to record all the entries with startTime and score. +let watcher_entry_record = []; + +// An object that tracks the document cumulative layout shift score. +// Usage: +// +// const watcher = new ScoreWatcher; +// ... +// assert_equals(watcher.score, expectedScore); +// +// The score reflects only layout shifts that occur after the ScoreWatcher is +// constructed. +ScoreWatcher = function() { + if (PerformanceObserver.supportedEntryTypes.indexOf("layout-shift") == -1) + throw new Error("Layout Instability API not supported"); + this.score = 0; + this.scoreWithInputExclusion = 0; + const resetPromise = () => { + this.promise = new Promise(resolve => { + this.resolve = () => { + resetPromise(); + resolve(); + } + }); + }; + resetPromise(); + const observer = new PerformanceObserver(list => { + list.getEntries().forEach(entry => { + this.lastEntry = entry; + this.score += entry.value; + watcher_entry_record.push({startTime: entry.startTime, score: entry.value, hadRecentInput : entry.hadRecentInput}); + if (!entry.hadRecentInput) + this.scoreWithInputExclusion += entry.value; + this.resolve(); + }); + }); + observer.observe({entryTypes: ['layout-shift']}); +}; + +ScoreWatcher.prototype.checkExpectation = function(expectation) { + if (expectation.score != undefined) + assert_equals(this.score, expectation.score); + if (expectation.sources) + check_sources(expectation.sources, this.lastEntry.sources); +}; + +ScoreWatcher.prototype.get_entry_record = function() { + return watcher_entry_record; +}; + +check_sources = (expect_sources, actual_sources) => { + assert_equals(expect_sources.length, actual_sources.length); + let rect_match = (e, a) => + e[0] == a.x && e[1] == a.y && e[2] == a.width && e[3] == a.height; + let match = e => a => + e.node === a.node && + rect_match(e.previousRect, a.previousRect) && + rect_match(e.currentRect, a.currentRect); + for (let e of expect_sources) + assert_true(actual_sources.some(match(e)), e.node + " not found"); +}; diff --git a/testing/web-platform/tests/layout-instability/rtl-distance.html b/testing/web-platform/tests/layout-instability/rtl-distance.html new file mode 100644 index 0000000000..6635438a5e --- /dev/null +++ b/testing/web-platform/tests/layout-instability/rtl-distance.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: movement distance uses starting corner</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +#shifter { position: relative; width: 100px; height: 100px; direction: rtl; background: blue; } + +</style> +<div id='shifter'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Move the left edge rightward by 10px and the right edge leftward by 20px. + document.querySelector("#shifter").style = "width: 70px; left: 10px"; + + // The movement distance should use the displacement of the right edge. + const expectedScore = computeExpectedScore(100 * 100, 20); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'RTL element.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-into-viewport-inline-direction-and-scroll.html b/testing/web-platform/tests/layout-instability/shift-into-viewport-inline-direction-and-scroll.html new file mode 100644 index 0000000000..241fdfc57f --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-into-viewport-inline-direction-and-scroll.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Layout Instability: shift into viewport in inline direction with scroll</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +#j { position: absolute; width: 200px; height: 600px; top: 300px; left: -200px; background: blue; } +</style> +<div id='j'></div> +<div style="width: 5000px; height: 5000px"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Move div into viewport horizontally. + document.querySelector("#j").style.left = '400px'; + window.scrollTo(300, 300); + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift into viewport in inline direction with scroll."); + +</script> + diff --git a/testing/web-platform/tests/layout-instability/shift-into-viewport-inline-direction.html b/testing/web-platform/tests/layout-instability/shift-into-viewport-inline-direction.html new file mode 100644 index 0000000000..8847e9058b --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-into-viewport-inline-direction.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Layout Instability: shift into viewport in inline direction</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +#j { position: absolute; width: 200px; height: 600px; left: -200px; background: blue; } +</style> +<div id='j'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Move div into viewport horizontally. + document.querySelector("#j").style.left = '100px'; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift into viewport in inline direction."); + +</script> + diff --git a/testing/web-platform/tests/layout-instability/shift-into-viewport.html b/testing/web-platform/tests/layout-instability/shift-into-viewport.html new file mode 100644 index 0000000000..455abd807f --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-into-viewport.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Layout Instability: shift into viewport</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#j { position: absolute; width: 600px; height: 200px; top: 100%; background: blue; } + +</style> +<div id='j'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Move div partially into viewport. + document.querySelector("#j").style.top = + document.documentElement.clientHeight - 200 + "px"; + + // The element moves from outside the viewport to within the viewport, which + // should generate a shift. + // (width 600) * (height 0 + move 200) + const expectedScore = computeExpectedScore(600 * 200, 200); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, "Shift into viewport."); + +</script> + diff --git a/testing/web-platform/tests/layout-instability/shift-invisible.html b/testing/web-platform/tests/layout-instability/shift-invisible.html new file mode 100644 index 0000000000..3c404a9438 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-invisible.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>Layout Instability: shift of invisible element not counted</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="width: 100px; height: 100px; position: relative"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + target.style.top = "200px"; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift of invisible element not counted."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-outside-viewport-inline-direction.html b/testing/web-platform/tests/layout-instability/shift-outside-viewport-inline-direction.html new file mode 100644 index 0000000000..57a19bceec --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-outside-viewport-inline-direction.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Layout Instability: shift out of viewport in inline direction</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +#j { position: absolute; width: 200px; height: 600px; background: blue; } +</style> +<div id='j'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Move div out of viewport horizontally. + document.querySelector("#j").style.left = '-300px'; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift out of viewport in inline direction."); + +</script> + diff --git a/testing/web-platform/tests/layout-instability/shift-outside-viewport.html b/testing/web-platform/tests/layout-instability/shift-outside-viewport.html new file mode 100644 index 0000000000..534d56be4b --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-outside-viewport.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>Layout Instability: shift outside viewport</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#j { position: absolute; width: 600px; height: 200px; top: 100%; background: blue; } + +</style> +<div id='j'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Move div even further out of viewport. + document.querySelector("#j").style.top = + document.documentElement.clientHeight + 200 + "px"; + + // Since the element moves entirely outside of the viewport, it shouldn't + // generate a score. + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift outside viewport."); + +</script> + + diff --git a/testing/web-platform/tests/layout-instability/shift-scroll-anchoring-natural-scroll.html b/testing/web-platform/tests/layout-instability/shift-scroll-anchoring-natural-scroll.html new file mode 100644 index 0000000000..1b146b05d7 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-scroll-anchoring-natural-scroll.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<title>Layout Instability: shift offscreen with scroll anchoring and natural scroll</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +#scroller { + overflow: scroll; + left: 20px; + top: 20px; + width: 200px; + height: 200px; +} +#spacer { + height: 3000px; +} +#ch { + position: relative; + background: yellow; + left: 10px; + top: 100px; + width: 150px; + height: 150px; +} +#offscreenElement { + width: 300px; + height: 300px; + background: lightblue; +} +#onscreenElement { + width: 300px; + height: 300px; + background: lightgreen; +} +</style> +<div id="scroller"> + <div id="offscreenElement"></div> + <div id="spacer"></div> + <div id="onscreenElement"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Scroll to show #onscreenElement. + scroller.scrollTop = 3250; + await waitForAnimationFrames(1); + + // Resize #offscreernElement and scroll a bit. + // Visually, #onscreenElement will move by 20px. + offscreenElement.style.height = '250px'; + scroller.scrollBy(0, 20); + + await waitForAnimationFrames(3); + // There should be no reported layout shift, because to the user it looks + // like a natural scroll by 20px. + assert_equals(watcher.score, 0); +}, "Offscreen shift with scroll annchoring and natural scroll not counted."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-while-scrolled.html b/testing/web-platform/tests/layout-instability/shift-while-scrolled.html new file mode 100644 index 0000000000..822aa94cff --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-while-scrolled.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Layout Instability: shift while scrolled</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { height: 2000px; margin: 0; } +#shift { position: relative; width: 300px; height: 200px; background: blue; } + +</style> +<div id="shift"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Scroll down by 100px. + document.scrollingElement.scrollTop = 100; + assert_equals(watcher.score, 0); + + // Generate a layout shift. + document.querySelector("#shift").style = "top: 60px"; + + // Impact region: width * (height - scrollTop + moveDistance) + const expectedScore = computeExpectedScore( + 300 * (200 - 100 + 60), 60); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Layout shift with non-zero scroll offset.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-with-counter-scroll-and-transform.html b/testing/web-platform/tests/layout-instability/shift-with-counter-scroll-and-transform.html new file mode 100644 index 0000000000..b4e4a99c1c --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-with-counter-scroll-and-transform.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<title>Layout Instability: shift with counter scroll and transform not counted</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +.scroller { + overflow: scroll; + position: absolute; + left: 20px; + top: 20px; + width: 200px; + height: 200px; +} +.content { + width: 600px; + height: 600px; +} +.changer { + position: relative; + background: yellow; + left: 10px; + top: 100px; + width: 150px; + height: 150px; +} + +</style> +<div id="scroller1" class="scroller"> + <div class="content"> + <div id="changer1" class="changer"></div> + </div> +</div> +<div id="scroller2" class="scroller"> + <div class="content"> + <div id="changer2" class="changer"></div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + changer1.style.top = "250px"; + changer1.style.transform = "translateY(-50px)"; + // 250 - 50 = 200; old position is 100; hence scrollTop to counter is 100. + scroller1.scrollTop = 100; + + changer2.style.left = "220px"; + changer2.style.transform = "translateX(80px)"; + // 220 + 80 = 300; old position is 10; hence scrollTop to counter is 290. + scroller2.scrollLeft = 290; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift with counter scroll and transform not counted."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-with-counter-scroll-and-translate.html b/testing/web-platform/tests/layout-instability/shift-with-counter-scroll-and-translate.html new file mode 100644 index 0000000000..a51920fc98 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-with-counter-scroll-and-translate.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<title>Layout Instability: shift with counter scroll and translate not counted</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +.scroller { + overflow: scroll; + position: absolute; + left: 20px; + top: 20px; + width: 200px; + height: 200px; +} +.content { + width: 600px; + height: 600px; +} +.changer { + position: relative; + background: yellow; + left: 10px; + top: 100px; + width: 150px; + height: 150px; +} + +</style> +<div id="scroller1" class="scroller"> + <div class="content"> + <div id="changer1" class="changer"></div> + </div> +</div> +<div id="scroller2" class="scroller"> + <div class="content"> + <div id="changer2" class="changer"></div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + changer1.style.top = "250px"; + changer1.style.translate = "0 -50px"; + // 250 - 50 = 200; old position is 100; hence scrollTop to counter is 100. + scroller1.scrollTop = 100; + + changer2.style.left = "220px"; + changer2.style.translate = "80px 0"; + // 220 + 80 = 300; old position is 10; hence scrollTop to counter is 290. + scroller2.scrollLeft = 290; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift with counter scroll and translate not counted."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-with-counterscroll-2.html b/testing/web-platform/tests/layout-instability/shift-with-counterscroll-2.html new file mode 100644 index 0000000000..d99723010e --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-with-counterscroll-2.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<title>Layout Instability: shift with counterscroll not counted, with 2 scrollers</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +.scroller { + overflow: scroll; + position: absolute; + left: 20px; + top: 20px; + width: 200px; + height: 200px; +} +.content { + width: 170px; + height: 600px; +} +.changer { + position: relative; + background: yellow; + left: 10px; + top: 100px; + width: 150px; + height: 150px; +} + +</style> +<div id="scroller1" class="scroller"> + <div class="content"> + <div id="changer1" class="changer"></div> + </div> +</div> +<div id="scroller2" class="scroller"> + <div class="content"> + <div id="changer2" class="changer"></div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Top goes from 100 to 200. scroll by 100 to counter it. + changer1.style.top = "200px"; + scroller1.scrollTop = 100; + // Top goes from 100 to 300. scroll by 200 to counter it. + changer2.style.top = "300px"; + scroller2.scrollTop = 200; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift with counterscroll not counted, with 2 scrollers."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-with-counterscroll.html b/testing/web-platform/tests/layout-instability/shift-with-counterscroll.html new file mode 100644 index 0000000000..85a8ed9336 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-with-counterscroll.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>Layout Instability: shift with counterscroll not counted</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +#s { + overflow: scroll; + position: absolute; + left: 20px; + top: 20px; + width: 200px; + height: 200px; +} +#sp { + width: 170px; + height: 600px; +} +#ch { + position: relative; + background: yellow; + left: 10px; + top: 100px; + width: 150px; + height: 150px; +} + +</style> +<div id="s"> + <div id="sp"> + <div id="ch"></div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + let scroller = document.querySelector("#s"); + let changer = document.querySelector("#ch"); + + changer.style.top = "200px"; + scroller.scrollTop = 100; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, "Shift with counterscroll not counted."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/shift-with-overflow-status-change.html b/testing/web-platform/tests/layout-instability/shift-with-overflow-status-change.html new file mode 100644 index 0000000000..33eea3080b --- /dev/null +++ b/testing/web-platform/tests/layout-instability/shift-with-overflow-status-change.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Layout Instability: change under overflow clipping container causing shift and overflow status change at the same time</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div style="height: 100px"></div> +<div style="overflow: auto; width: 400px; height: 400px"> + <div id="resized" style="width: 600px; height: 100px; background: gray"></div> + <div id="shifted" style="width: 300px; height: 100px; background: blue"></div> +</div> +<script> +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + resized.style.width = '200px'; + resized.style.height = '200px'; + + const expectedScore = computeExpectedScore(300 * (100 + 100), 100); + + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); + + resized.style.width = '600px'; + resized.style.height = '100px'; + await watcher.promise; + assert_equals(watcher.score, expectedScore * 2); +}, 'Change under overflow clipping container causing shift and overflow status change at the same time'); +</script> diff --git a/testing/web-platform/tests/layout-instability/simple-block-movement.html b/testing/web-platform/tests/layout-instability/simple-block-movement.html new file mode 100644 index 0000000000..10261f7d81 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/simple-block-movement.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Layout Instability: simple block movement is detected</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<style> +#shifter { position: relative; width: 300px; height: 200px; background: blue; } +</style> +<div id="shifter"></div> +<script> + +const watcher = new ScoreWatcher; +promise_test(async () => { + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + document.querySelector("#shifter").style = "top: 160px"; + + // An element of size (300 x 200) has shifted by 160px. + const expectedScore = computeExpectedScore(300 * (200 + 160), 160); + + // Observer fires after the frame is painted. + cls_expect(watcher, {score: 0}); + await watcher.promise; + cls_expect(watcher, {score: expectedScore}); +}, 'Simple block movement.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/sources-enclosure.html b/testing/web-platform/tests/layout-instability/sources-enclosure.html new file mode 100644 index 0000000000..8d1596ad49 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/sources-enclosure.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<title>Layout Instability: source attribution with redundant enclosure</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<style> +body { margin: 0; } +#shifter { + position: relative; background: #def; + width: 300px; height: 200px; +} +#inner { + position: relative; background: #f97; + width: 100px; height: 100px; +} +#absfollow { + position: absolute; background: #ffd; opacity: 50%; + width: 350px; height: 200px; left: 0; top: 160px; +} +.stateB { top: 160px; } +.stateB #inner { left: 100px; } +.stateC ~ #absfollow { top: 0; } +</style> +<div id="shifter" class="stateA"> + <div id="inner"></div> +</div> +<div id="absfollow"></div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + let shifter = document.querySelector("#shifter"); + let absfollow = document.querySelector("#absfollow"); + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + shifter.className = "stateB"; + await watcher.promise; + + // Shift of #inner ignored as redundant, fully enclosed by #shifter. + cls_expect(watcher, {sources: [{ + node: shifter, + previousRect: [0, 0, 300, 200], + currentRect: [0, 160, 300, 200] + }]}); + + shifter.className = "stateC"; + await watcher.promise; + + // Shift of #shifter ignored as redundant, fully enclosed by #absfollow. + cls_expect(watcher, {sources: [{ + node: absfollow, + previousRect: [0, 160, 350, 200], + currentRect: [0, 0, 350, 200] + }]}); + +}, "Sources with redundant enclosure."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/sources-maximpact.html b/testing/web-platform/tests/layout-instability/sources-maximpact.html new file mode 100644 index 0000000000..497932b065 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/sources-maximpact.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<title>Layout Instability: source attribution prioritization</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<style> +body { margin: 0; } +#a, #b, #c, #d, #e, #f { + display: inline-block; + background: gray; + min-width: 10px; + min-height: 10px; + vertical-align: top; +} +#a { width: 30px; height: 30px; } +#b { width: 20px; height: 20px; } +#c { height: 50px; } +#d { width: 50px; } +#e { width: 40px; height: 30px; } +#f { width: 30px; height: 40px; } +</style> +<div id="grow"></div> +<div id="a"></div +><div id="b"></div +><div id="c"></div +><div id="d"></div +><div id="e"></div +><div id="f"></div> +<script> + +let $ = id => document.querySelector(id); + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + $("#grow").style.height = "50px"; + await watcher.promise; + + cls_expect(watcher, {sources: [ + { + node: $("#a"), + previousRect: [0, 0, 30, 30], + currentRect: [0, 50, 30, 30] + }, + { + node: $("#f"), + previousRect: [150, 0, 30, 40], + currentRect: [150, 50, 30, 40] + }, + { + node: $("#c"), + previousRect: [50, 0, 10, 50], + currentRect: [50, 50, 10, 50] + }, + { + node: $("#d"), + previousRect: [60, 0, 50, 10], + currentRect: [60, 50, 50, 10] + }, + { + node: $("#e"), + previousRect: [110, 0, 40, 30], + currentRect: [110, 50, 40, 30] + } + ]}); +}, "Source attribution prioritizes by impact."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/sources.html b/testing/web-platform/tests/layout-instability/sources.html new file mode 100644 index 0000000000..5bf3abfcc8 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/sources.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Layout Instability: sources attribute</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 10px; } +#shifter { position: relative; width: 300px; height: 100px; background: blue; } + +</style> +<div id="shifter"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +strrect = r => `[${r.x},${r.y},${r.width},${r.height}]`; + +promise_test(async () => { + const watcher = new ScoreWatcher; + const shifter = document.querySelector("#shifter"); + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + shifter.style = "top: 60px; left: 10px"; + await watcher.promise; + + const sources = watcher.lastEntry.sources; + assert_equals(sources.length, 1); + + const source = sources[0]; + assert_equals(source.node, shifter); + assert_equals(strrect(source.previousRect), "[10,10,300,100]"); + assert_equals(strrect(source.currentRect), "[20,70,300,100]"); +}, "Sources attribute."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/sticky-descendant-move.html b/testing/web-platform/tests/layout-instability/sticky-descendant-move.html new file mode 100644 index 0000000000..afab89592d --- /dev/null +++ b/testing/web-platform/tests/layout-instability/sticky-descendant-move.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Layout Instability: movement of descendant of sticky positioned</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div style="position: sticky; width: 400px; height: 300px; top: 0"> + <div id="child" style="position: relative; width: 300px; height: 200px; background: yellow"></div> +</div> +<script> +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + const child = document.querySelector("#child"); + child.style.top = '100px'; + + const expectedScore = computeExpectedScore(300 * (200 + 100), 100); + + // Observer fires after the frame is painted. + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Movement of descendant of sticky positioned.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/sticky-layout-no-change.html b/testing/web-platform/tests/layout-instability/sticky-layout-no-change.html new file mode 100644 index 0000000000..8f3b2cf373 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/sticky-layout-no-change.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>Layout Instability: sticky positioned layout no change</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div style="height: 3000px"></div> +<div id="sticky" style="position: sticky; width: 200px; height: 300px; bottom: 0"> + <div style="will-change: transform; height: 3000px; background: yellow"></div> +</div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // This doesn't change layout because the sticky element sticks to the bottom. + sticky.style.marginTop = "-1000px"; + + await waitForAnimationFrames(3); + assert_equals(watcher.score, 0); +}, 'Sticky layout no change.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/sub-frame.html b/testing/web-platform/tests/layout-instability/sub-frame.html new file mode 100644 index 0000000000..d7cb40002e --- /dev/null +++ b/testing/web-platform/tests/layout-instability/sub-frame.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<style> + #j { + position: relative; + width: 300px; + height: 100px; + background-color: purple; + } +</style> +<div id="j"></div> +<script> +function shiftFrame() { + document.getElementById('j').style.top = '60px'; +} +function unshiftFrame() { + document.getElementById('j').style.top = ''; +} + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + shiftFrame(); + + const expectedScore = computeExpectedScore(300 * (100 + 60), 60); + + cls_expect(watcher, {score: 0}); + await watcher.promise; + cls_expect(watcher, {score: expectedScore}); + + unshiftFrame(); + await watcher.promise; + cls_expect(watcher, {score: expectedScore * 2}); + + window.parent.postMessage({ + type: 'layout shift score', + score: watcher.score, + expectedScore: expectedScore * 2, + }, '*'); +}, 'We will see two layout shift with the same score in the subframe.'); +</script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/layout-instability/supported-layout-type.html b/testing/web-platform/tests/layout-instability/supported-layout-type.html new file mode 100644 index 0000000000..3ba209f50a --- /dev/null +++ b/testing/web-platform/tests/layout-instability/supported-layout-type.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<head> +<title>PerformanceObserver.supportedEntryTypes contains "layout-shift"</title> +</head> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + assert_implements(window.LayoutShift, 'Layout Instability is not supported.'); + assert_implements(typeof PerformanceObserver.supportedEntryTypes !== "undefined", + 'supportedEntryTypes is not supported.'); + assert_greater_than(PerformanceObserver.supportedEntryTypes.indexOf("layout-shift"), -1, + "There should be an entry 'layout-shift' in PerformanceObserver.supportedEntryTypes"); +}, "supportedEntryTypes contains 'layoutShift'."); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/layout-instability/toJSON.html b/testing/web-platform/tests/layout-instability/toJSON.html new file mode 100644 index 0000000000..83ee9c968e --- /dev/null +++ b/testing/web-platform/tests/layout-instability/toJSON.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Layout Instability: toJSON</title> +<body> +<style> +#myDiv { position: relative; width: 300px; height: 100px; background: blue; } +</style> +<div id='myDiv'></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +promise_test(async t => { + assert_implements(window.LayoutShift, 'Layout Instability is not supported.'); + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + return new Promise(resolve => { + const observer = new PerformanceObserver( + t.step_func(entryList => { + const entry = entryList.getEntries()[0]; + assert_equals(typeof(entry.toJSON), 'function'); + const json = entry.toJSON(); + assert_equals(typeof(json), 'object'); + const keys = [ + // PerformanceEntry + 'name', + 'entryType', + 'startTime', + 'duration', + // LayoutShift + 'value', + 'hadRecentInput', + 'lastInputTime', + ]; + for (const key of keys) { + assert_equals(json[key], entry[key], + `LayoutShift ${key} entry does not match its toJSON value`); + } + resolve(); + }) + ); + observer.observe({type: 'layout-shift'}); + document.getElementById('myDiv').style = "top: 60px"; + }); +}, 'Test toJSON() in LayoutShift.'); +</script> +</body> diff --git a/testing/web-platform/tests/layout-instability/transform-above-filter-dynamic.html b/testing/web-platform/tests/layout-instability/transform-above-filter-dynamic.html new file mode 100644 index 0000000000..1d7ed51a91 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/transform-above-filter-dynamic.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>Layout Instability: addition of scale transform above filter</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id=target style="width: 581px"> + <div id=moved style="filter: saturate(1.1); width: 300px; height:300px; background: lightblue"> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +promise_test(async () => { + const watcher = new ScoreWatcher; + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + target.style.transform = "scale(1.1)"; + await waitForAnimationFrames(1); + + assert_equals(watcher.score, 0); +}, 'addition of scale transform above filter'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/transform-above-perspective-dynamic.html b/testing/web-platform/tests/layout-instability/transform-above-perspective-dynamic.html new file mode 100644 index 0000000000..fea65ca56a --- /dev/null +++ b/testing/web-platform/tests/layout-instability/transform-above-perspective-dynamic.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Layout Instability: addition of transform above perspective</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id=target2> + <div id=perspective style="perspective: 1000px;"> + <div id=target>Test</div> + </div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> +promise_test(async () => { + const watcher = new ScoreWatcher; + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + target2.style.transform = 'translateX(0px)'; + await waitForAnimationFrames(1); + + assert_equals(watcher.score, 0); +}, 'addition of transform above perspective'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/transform-change.html b/testing/web-platform/tests/layout-instability/transform-change.html new file mode 100644 index 0000000000..ea1f10ac8a --- /dev/null +++ b/testing/web-platform/tests/layout-instability/transform-change.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Layout Instability: no layout shift for transform change</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +body { margin: 0; } +#transformed { position: relative; transform: translateX(20px); width: 100px; height: 100px; background: blue; } +#child { width: 400px; height: 400px; } +</style> +<div id="transformed"> + <div id="child"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the transform, for which no shift should be reported. + document.querySelector("#transformed").style = "transform: translateY(100px)"; + // Change size of child, for which no shift should be reported, either. + document.querySelector("#child").style = "width: 300px"; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'no layout shift for transform change'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/transform-counter-layout-shift.html b/testing/web-platform/tests/layout-instability/transform-counter-layout-shift.html new file mode 100644 index 0000000000..476e25a532 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/transform-counter-layout-shift.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Layout Instability: no layout shift if transform change counters location change</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +body { margin: 0; } +#transformed { position: relative; transform: translateX(20px); width: 100px; height: 100px; background: blue; } +#child { width: 400px; height: 400px; } +</style> +<div id="transformed"> + <div id="child"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the transform and the location at the same time, and the values + // cancel each other visually, for which no shift should be reported. + transformed.style.transform = 'translateY(100px)'; + transformed.style.top = '-100px'; + transformed.style.left = '20px'; + // Change size of child, for which no shift should be reported, either. + child.style.width = '300px'; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'no layout shift if transform change counters location change'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/transform.html b/testing/web-platform/tests/layout-instability/transform.html new file mode 100644 index 0000000000..98f94f53cc --- /dev/null +++ b/testing/web-platform/tests/layout-instability/transform.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Layout Instability: shift inside a transformed container</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> + +body { margin: 0; } +#container { transform: translateX(-300px) translateY(-40px); } +#shifter { position: relative; width: 600px; height: 140px; background: blue; } + +</style> +<div id="container"> + <div id="shifter"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the position of the div. + document.querySelector("#shifter").style = "top: 60px"; + + // The shifter has size 600 x 140, but the container's transform + // reduces its viewport overlap. + const expectedScore = computeExpectedScore( + (600 - 300) * (140 - 40 + 60), 60); + + await watcher.promise; + assert_equals(watcher.score, expectedScore); +}, 'Transformed container.'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/translate-change.html b/testing/web-platform/tests/layout-instability/translate-change.html new file mode 100644 index 0000000000..ddfc041700 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/translate-change.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Layout Instability: no layout shift for change of individual transform property</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +body { margin: 0; } +#transformed { position: relative; translate: 20px 0; width: 100px; height: 100px; background: blue; } +#child { width: 400px; height: 400px; } +</style> +<div id="transformed"> + <div id="child"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the transform, for which no shift should be reported. + document.querySelector("#transformed").style = "translate: 0 100px"; + // Change size of child, for which no shift should be reported, either. + document.querySelector("#child").style = "width: 300px"; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'no layout shift for transform change'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/translate-counter-layout-shift.html b/testing/web-platform/tests/layout-instability/translate-counter-layout-shift.html new file mode 100644 index 0000000000..18e03ad7f2 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/translate-counter-layout-shift.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Layout Instability: no layout shift if translate change counters location change</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +body { margin: 0; } +#transformed { position: relative; translate: 20px 0; width: 100px; height: 100px; background: blue; } +#child { width: 400px; height: 400px; } +</style> +<div id="transformed"> + <div id="child"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the transform and the location at the same time, and the values + // cancel each other visually, for which no shift should be reported. + transformed.style.translate = '0 100px'; + transformed.style.top = '-100px'; + transformed.style.left = '20px'; + // Change size of child, for which no shift should be reported, either. + child.style.width = '300px'; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'no layout shift if translate change counters location change'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/video.html b/testing/web-platform/tests/layout-instability/video.html new file mode 100644 index 0000000000..d699ba0ae3 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/video.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Layout Instability: no shifts from advancing video track</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<video controls> + <source src="/media/white.webm" type="video/webm"> +</video> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + var video = document.querySelector("video"); + + await new Promise(resolve => { video.oncanplay = resolve; }); + await waitForAnimationFrames(2); + + // TODO(crbug.com/1088311): There are still some shifts from creating the + // <video>, so the score is already > 0 here. For now, just verify that + // advancing the track does not increase it further. + var currentScore = watcher.score; + + video.currentTime = 5; + + await waitForAnimationFrames(3); + cls_expect(watcher, {score: currentScore}); + +}, "No shifts from advancing video track."); + +</script> diff --git a/testing/web-platform/tests/layout-instability/visibility-hidden-layout-and-visible.html b/testing/web-platform/tests/layout-instability/visibility-hidden-layout-and-visible.html new file mode 100644 index 0000000000..35ee0d9ed8 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/visibility-hidden-layout-and-visible.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Layout Instability: visibility:hidden change with layout</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="position: absolute; top: 0; width: 200px; height: 200px; visibility: hidden; background: blue"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift target, for which no shift should be reported because it's hidden. + target.style.top = '200px'; + target.style.visibility = 'visible'; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); + + // Shift again, for which shift should be reported. + target.style.top = '300px'; + + await watcher.promise; + const expectedScore = computeExpectedScore(200 * (200 + 100), 100); + assert_equals(watcher.score, expectedScore); + +}, 'visibility:hidden change with layout'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/visibility-hidden.html b/testing/web-platform/tests/layout-instability/visibility-hidden.html new file mode 100644 index 0000000000..583be10cd2 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/visibility-hidden.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Layout Instability: visibility:hidden</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="position: absolute; top: 0; width: 400px; height: 400px; visibility: hidden; background: blue"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift target, for which no shift should be reported because it's hidden. + document.querySelector("#target").style.top = '200px'; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'visibility:hidden'); + +</script> diff --git a/testing/web-platform/tests/layout-instability/visible-to-hidden.html b/testing/web-platform/tests/layout-instability/visible-to-hidden.html new file mode 100644 index 0000000000..d6ac75a144 --- /dev/null +++ b/testing/web-platform/tests/layout-instability/visible-to-hidden.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Layout Instability: visibility:hidden</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<div id="target" style="position: absolute; top: 0; width: 400px; height: 400px; background: blue;"></div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Shift target and make hidden at the same time. Should not be reported! + document.querySelector("#target").style.top = '200px'; + document.querySelector("#target").style.visibility = 'hidden'; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'visible to hidden'); + +</script> |