Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTML: navigate cancels iframe lazy-load #45650

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

zcorpan
Copy link
Member

@zcorpan zcorpan commented Apr 10, 2024

@noamr
Copy link
Contributor

noamr commented Apr 10, 2024

would be interesting to test also what happens when the iframe receives lazy steps after a navigation is started but before it's committed. e.g. you have an about:blank with lazy and without a src, you navigate it with a link, then set a src, and the new document is ready to commit before the lazy steps are run
I think what chromium does here would differ from your spec PR (not saying here what the "right" solution is)

@zcorpan
Copy link
Member Author

zcorpan commented Apr 11, 2024

@noamr per spec, when the iframe is first inserted, process the iframe attributes is run but returns at 2.2. Then something navigates. Then setting src will again call process the iframe attributes, which sets the lazy load resumption steps in 2.6.1.

<script src="/resources/testharnessreport.js"></script>
<script>
setup({single_test: true});
onload = () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify? To say onload overall "doesn't work" indicates that the lazyloaded support/blank.html iframe ends up loading ASAP (before this onload handler is set), causing the outer document's load event to fire before our handler is assigned to catch the load event.

But I can't imagine that's the case, right? Certainly the main document's load event does not fire before this script runs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the load event might fire before the iframe is loaded, since a lazy-loading iframe doesn't block the load event. Therefore it's racy. But the step_timeout should address this sufficiently.

@zcorpan
Copy link
Member Author

zcorpan commented Apr 11, 2024

These are unstable in Chrome:

  • /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-location-replace-set-src.html
  • /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-meta-refresh.optional.html

The first one I think should be stable per spec, since the lazy navigation happens after location.replace and it sets the ongoing navigation which should abort the previous navigation.

The meta refresh could arguably be unstable per spec if the UA decides to delay the navigation by the same amount of time as the test's step_timeout. But changing the timeout to 5s doesn't help, so it doesn't explain the instability in Chrome.

setup({single_test: true});
onload = () => {
step_timeout(() => {
assert_equals(iframe.contentWindow.location.href, new URL("support/blank.htm?src", location.href).href);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we assert above this, that location.href equals support/blank.htm?foo (at least I think it will at this time?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right after calling pushState yes. When this script runs, it's racy whether the lazy load has happened.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pushState and replaceState tests were incorrect since the iframe's doc's URL is about:blank, and so the URL components (except fragment) need to match about:blank. Fixed now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this script runs, it's racy whether the lazy load has happened.

What do you mean by "happened"? Do you mean https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element:current-navigation-was-lazy-loaded-4 just being set to true? Or do you mean that plus the resumption steps executed, and the support/blank.htm?src navigation has completed?

In any case, I'm not sure I follow. By this point in the script, we've done nothing asynchronous, right? I think what has happened up to this point is:

  1. https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element:current-navigation-was-lazy-loaded-4 gets set to true, for the lazyload navigation to support/blank.html?src. But the iframe's document is still about:blank, since the lazy load resumption steps were not run immediately
  2. Synchronously after, pushState() comes along and changes the about:blank Document's URL to about:blank#foo, which is allowed per https://html.spec.whatwg.org/C#can-have-its-url-rewritten

So is there any room in there for the lazy loaded navigation to have already completed by then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The navigation might have completed. The event loop is allowed to spin between each script, and the pushState() happens in the first script element.

// Should have no effect on lazy-loading
iframe.contentWindow.history.pushState(null, "", iframe.dataset.src);
</script>
<!-- Loading testharness.js here is intentional. -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebKit passes the tests if testharness.js is loaded in head, but fails these tests (or a simple demo without external scripts).

@domfarolino
Copy link
Member

Sorry for the delay, I took some time off last week and early this week. Getting through my backlog so I will take a look at this as soon as I can

let pushStateSuccess = true;
try {
// Should have no effect on lazy-loading.
// Per https://html.spec.whatwg.org/#can-have-its-url-rewritten
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Per https://html.spec.whatwg.org/#can-have-its-url-rewritten
// Per https://html.spec.whatwg.org/C#can-have-its-url-rewritten

nit: Just so the average reader has an easier time with this link.

<script src="/resources/testharnessreport.js"></script>
<script>
setup({single_test: true});
assert_true(pushStateSuccess);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can just avoid this check altogether since pushState() will throw an exception in the error case and we don't have to handle that explicitly? That is, I assume the error thrown to the harness in the error case is enough to fail the test reasonable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The harness isn't loaded yet when that exception is thrown, hence this assertion.

<script src="/resources/testharnessreport.js"></script>
<script>
setup({single_test: true});
onload = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify? To say onload overall "doesn't work" indicates that the lazyloaded support/blank.html iframe ends up loading ASAP (before this onload handler is set), causing the outer document's load event to fire before our handler is assigned to catch the load event.

But I can't imagine that's the case, right? Certainly the main document's load event does not fire before this script runs?

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<title>History state change for iframe loading='lazy' before it is loaded: history.pushState</title>
<iframe data-src="about:blank#foo" src="support/blank.htm?src" loading="lazy"></iframe>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<iframe data-src="about:blank#foo" src="support/blank.htm?src" loading="lazy"></iframe>
<iframe data-src="about:blank#push" src="support/blank.htm?src" loading="lazy"></iframe>

nit: more descriptive

setup({single_test: true});
onload = () => {
step_timeout(() => {
assert_equals(iframe.contentWindow.location.href, new URL("support/blank.htm?src", location.href).href);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this script runs, it's racy whether the lazy load has happened.

What do you mean by "happened"? Do you mean https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element:current-navigation-was-lazy-loaded-4 just being set to true? Or do you mean that plus the resumption steps executed, and the support/blank.htm?src navigation has completed?

In any case, I'm not sure I follow. By this point in the script, we've done nothing asynchronous, right? I think what has happened up to this point is:

  1. https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element:current-navigation-was-lazy-loaded-4 gets set to true, for the lazyload navigation to support/blank.html?src. But the iframe's document is still about:blank, since the lazy load resumption steps were not run immediately
  2. Synchronously after, pushState() comes along and changes the about:blank Document's URL to about:blank#foo, which is allowed per https://html.spec.whatwg.org/C#can-have-its-url-rewritten

So is there any room in there for the lazy loaded navigation to have already completed by then?

step_timeout(() => {
assert_equals(iframe.contentWindow.location.href, new URL("support/blank.htm?src", location.href).href);
done();
}, 1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be done on a timeout? Or can it be done on the lazyloaded iframe's load event, since it will be fired asynchronously (with respect to this code currently running) once support/blank.html loads?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since some tests do potentially two navigations, a timeout is still needed. But maybe I can use the iframe load event instead of the window load event...

// only the fragment can be changed for about: URLs.
iframe.contentWindow.history.pushState(null, "", iframe.dataset.src);
} catch(ex) {
replaceStateSuccess = false;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be pushStateSuccess, oops

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants