commit fd2f344cfcfffe56fe2cc7ad7cbfb683360d837f
parent b8ab0b6ce9c9d312b57414c281df16cd57d9af99
Author: Perry <perryuwang@gmail.com>
Date: Wed, 15 Oct 2025 08:22:10 +0000
Bug 1992859 [wpt PR 55261] - Save and restore iframe scroll offset when going to/from display:none, a=testonly
Automatic update from web-platform-tests
Save and restore iframe scroll offset when going to/from display:none
When iframe is set to display:none, its scroll offset is clamped to
zero, so it cannot preserve previous scroll offset after returning from
display:none. This CL saves the current scroll offset when iframe was
set to display:none, and restore the scroll offset when it returns from
display:none.
Bug: 41368291
Change-Id: I7440f4198741d7f7bd9bc472d16438e9781260fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6999533
Commit-Queue: Perry <perryuwang@gmail.com>
Reviewed-by: Stefan Zager <szager@chromium.org>
Reviewed-by: David Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1527333}
--
wpt-commits: 9561610dc1096d35ed38080c675accecc97cf13e
wpt-pr: 55261
Diffstat:
2 files changed, 119 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/dom/events/scrolling/save-iframe-scroll-offset-when-display-none.html b/testing/web-platform/tests/dom/events/scrolling/save-iframe-scroll-offset-when-display-none.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Ensure that the scroll position isn't lost when the iframe is set to display:none and shown again</title>
+<link rel="author" href="mailto:perryuwang@gmail.com">
+<link rel="help" href="https://issues.chromium.org/issues/41368291">
+<script src="scroll_support.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div>
+ <iframe id="frame"></iframe>
+</div>
+
+<script>
+const IFRAME_PATH = '/dom/events/scrolling/save-iframe-scroll-offset-when-display-none.sub.html';
+
+function waitForFrameLoadAsync(frame) {
+ return new Promise(async (resolve) => {
+ frame.addEventListener('load', resolve, { once: true });
+ });
+}
+
+function waitForMessageAsync(expected_frame_id, expected_command) {
+ return new Promise((resolve) => {
+ window.addEventListener('message', (event) => {
+ assert_equals(event.data.command, expected_command);
+ assert_equals(event.data.frame_id, expected_frame_id);
+ resolve({scrollX: event.data.scrollX, scrollY: event.data.scrollY});
+ }, { once: true });
+ });
+}
+
+function iframeScrollTo(frame, x, y) {
+ return new Promise(async (resolve) => {
+ const scroll_ack_waiter = waitForMessageAsync(frame.id, 'scrollTo');
+ await frame.contentWindow.postMessage({
+ command: 'scrollTo',
+ frame_id: frame.id,
+ scrollX: x,
+ scrollY: y,
+ }, '*');
+ const ret = await scroll_ack_waiter;
+ resolve(ret);
+ });
+}
+
+function iframeGetScroll(frame) {
+ return new Promise(async (resolve) => {
+ const scroll_ack_waiter = waitForMessageAsync(frame.id, 'getScroll');
+ await frame.contentWindow.postMessage({
+ command: 'getScroll',
+ frame_id: frame.id,
+ }, '*');
+ const ret = await scroll_ack_waiter;
+ resolve(ret);
+ });
+}
+
+async function testIFrame(src) {
+ const frame = document.getElementById('frame');
+ frame.src = src;
+ await waitForFrameLoadAsync(frame);
+ let ret = await iframeScrollTo(frame, 1000, 2000);
+ assert_equals(ret.scrollX, 1000);
+ assert_equals(ret.scrollY, 2000);
+ frame.style.display = 'none';
+ await waitForCompositorCommit();
+ frame.style.display = '';
+ await waitForCompositorCommit();
+ ret = await iframeGetScroll(frame);
+ assert_equals(ret.scrollX, 1000);
+ assert_equals(ret.scrollY, 2000);
+}
+
+window.onload = async () => {
+ promise_test(async () => {
+ await testIFrame(IFRAME_PATH);
+ }, 'Ensure that the scroll position is not lost when the local iframe is set to display:none and shown again.');
+
+ promise_test(async () => {
+ await testIFrame(get_host_info().HTTP_NOTSAMESITE_ORIGIN + IFRAME_PATH);
+ }, 'Ensure that the scroll position is not lost when the remote iframe is set to display:none and shown again.');
+}
+</script>
diff --git a/testing/web-platform/tests/dom/events/scrolling/save-iframe-scroll-offset-when-display-none.sub.html b/testing/web-platform/tests/dom/events/scrolling/save-iframe-scroll-offset-when-display-none.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<div style="width: 10000px; height: 10000px">
+ <!-- Adding <input> is to reproduce scroll offset reset when display:none -->
+ <input type="text">
+</div>
+<script>
+function postReplyMessage(target, frame_id, command) {
+ target.postMessage({
+ command: command,
+ frame_id: frame_id,
+ scrollX: window.scrollX,
+ scrollY: window.scrollY
+ }, "*");
+}
+
+function handleMessage(event) {
+ switch (event.data.command) {
+ case 'scrollTo':
+ window.scrollTo(event.data.scrollX, event.data.scrollY);
+ break;
+ case 'getScroll':
+ // No-op, just reply with current scroll position.
+ break;
+ default:
+ throw Error(`Unknown command: ${event.data.command}`);
+ break;
+ }
+ requestAnimationFrame(() => {
+ postReplyMessage(event.source, event.data.frame_id, event.data.command);
+ });
+}
+
+window.addEventListener('message', handleMessage);
+</script>