Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 1 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /html/browsers/browsing-the-web/overlapping-navigations-and-traversals/nav-cancelation-2.sub.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<meta charset="utf-8">
<title>Grandparent main frame cancels a navigation in a cross-origin grandchild</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!--
This test asserts that an ancestor canceling a cross-origin descendant's
ongoing navigation does not result in load events firing in the ancestor
synchronously.
The reason this test uses a grandparent/grandchild pair to represent the
ancestor/descendant, instead of a parent/child pair, is because if a child
frame is blocking its parent window's load event, that means the child frame
navigation is being made from the initial about:blank Document to some
resource, and the initial about:blank child is synchronously scriptable from
the parent since they share the same window agent. This test is trying to
capture the scenario where the descendant document (that owns the ongoing
navigation) is hosted/scheduled on a different agent than the ancestor
document that cancels the descendant's ongoing navigation. The only way to do
this is to have a grandparent frame load a cross-origin child, whose document
itself loads a child frame that has a very slow ongoing navigation. That way
the grandparent can reach the grandchild via `window.frames[0].frames[0]`,
which is a proxy to the document living in a different agent.
-->
<body>
<iframe src="http://{{domains[www1]}}:{{ports[http][0]}}/html/browsers/browsing-the-web/overlapping-navigations-and-traversals/resources/nav-cancelation-2-helper.html"></iframe>
<script>
promise_test(async t => {
let window_load_fired = false;
let iframe_load_fired = false;
let grandchild_iframe_load_fired = false;
const iframe = document.querySelector('iframe');
const window_load_promise = new Promise(resolve => {
window.onload = () => {
window_load_fired = true;
resolve();
}
});
const iframe_onload_promise = new Promise(resolve => {
iframe.onload = () => {
iframe_load_fired = true;
resolve();
}
});
// Let the grandchild frame get registered in window.frames.
await new Promise((resolve, reject) => {
window.addEventListener('message', e => {
if (e.data != "grandchild frame created") {
reject(Error(`Expected 'grandchild frame created', but got ${e.data}`));
}
resolve();
}, {once: true});
});
// Set up a message handler to listen to the grandchild's iframe element's
// load event being fired. We should never get this message, and we assert
// this below. If we ever get this message, that means one of two things:
// 1.) The grandparent (this document)'s load event was blocked on the
// completion of its grandchild's subsequent navigation (after
// cancelation)
// 2.) After the grandchild's navigation was canceled, its <iframe>'s load
// event was fired before its subsequent navigation completed
// Both of these are wrong.
addEventListener('message', e => {
assert_equals(e.data, "grandchild frame loaded",
`Expected 'grandchild frame loaded', but got ${e.data}`);
grandchild_iframe_load_fired = true;
});
// While the grandchild navigation is in-flight, cancel it and record when the
// our `load` event fires. The second navigation is a slow resource so that
// the speed of the network doesn't cause the grandchild load event to fire
// early and confuse the grandparent when running the assertions below. We're
// trying to clearly separate out when the grandparent load event fires vs
// when the grandchild load event fires.
window.frames[0].frames[0].location.href = "resources/slow.py?different";
// Synchronously after cancelation, no load events should have been fired.
assert_false(window_load_fired,
"Grandparent's load event does not synchronously fire after grandchild " +
"navigation cancelation");
assert_false(iframe_load_fired,
"<iframe> load event does not synchronously fire after grandchild " +
"navigation cancelation");
assert_false(grandchild_iframe_load_fired,
"Grandchild <iframe>'s load event does not synchronously fire upon " +
"navigation cancelation");
// Load events did not fire in a microtask after cancelation.
await Promise.resolve();
assert_false(window_load_fired,
"Grandparent's load event does not fire in the microtask after " +
"navigation canceled");
assert_false(iframe_load_fired,
"<iframe> load event does not fire in the microtask after navigation " +
"canceled");
assert_false(grandchild_iframe_load_fired,
"Grandchild <iframe> load event does not fire in the microtask after " +
"navigation canceled");
// Canceling the navigation should however, asynchronously unblock, in this
// order:
// 1.) Our child window's load event, captured by our `iframe`'s load event
// 2.) Our window load event
// On the other hand, the grandchild navigation should still be ongoing, so
// inside our child's document, the nested <iframe> representing our
// grandchild should not have had its load event fired yet.
await iframe_onload_promise;
assert_true(iframe_load_fired);
assert_false(window_load_fired,
"Grandparent's load event does not fire before its child iframe's load " +
"event");
assert_false(grandchild_iframe_load_fired,
"Grandchild <iframe>'s load event does not fire before its parent's load " +
"event and grandparent's load event");
// We want to assert that the grandparent is not (incorrectly) blocked on its
// grandchild's second navigation from completing. One sign that it was
// incorrectly blocked on its grandchild's second navigation is if the
// grandparent receives a message (saying that the grandchild <iframe>
// element's load event fired) before the grandparent's load event fires.
//
// This indicates a weird state where the grandparent's immediate child fired
// its load event in response to navigation cancelation (see the assertions
// above), but the grandparent itself is still blocked on the grandchild
// loading. If this is the case, the the postMessage() (that sets
// `grandchild_iframe_load_fired = true`) is received by the grandparent just
// before the grandparent's load event is unblocked and fired. Therefore we
// can detect this situation by checking `grandchild_iframe_load_fired`.
await window_load_promise;
assert_true(iframe_load_fired);
assert_true(window_load_fired,
"Grandparent's load event fires asynchronously after grandchild " +
"navigation cancelation");
assert_false(grandchild_iframe_load_fired,
"Grandchild <iframe> load event doesn't fire before grandparent's " +
"load event");
// Verify that the grandchild <iframe>'s load event does not fire within one
// task of the grandchild's load event from being fired. This is to further
// verify that the grandparent's load event is not tied to its grandchild's
// second navigation.
//
// If for example, the grandparent's load event *is* blocked on the
// grandchild's second navigation from finishing, it is still possible for the
// grandparent's load event to fire. For example, Chromium has a bug where if
// both are true:
// 1.) The grandparent frame is in the same process as the grandchild frame
// 2.) The grandparent frame's load event is blocked on its grandchild's
// second navigation
//
// ...then the following will happen:
// 1.) The grandchild's load event will fire, triggering a postMessage() to
// the grandparent frame. This queues a task to run the grandparent's
// message handler.
// 2.) The grandparent's load event will *immediately* fire, and the
// postMessage() will fire a single task later since it is queued.
//
// Therefore, we assert that `grandchild_iframe_load_fired` is not true up to
// a single task after the grandparent's load event fires.
await new Promise(resolve => {
t.step_timeout(resolve, 0);
});
assert_false(grandchild_iframe_load_fired,
"Grandchild <iframe>'s load event does not fire at least one task " +
"after the grandparent's window load event fires. It should only fire " +
"when its subsequent navigation is complete");
}, "grandparent cancels a pending navigation in a cross-origin grandchild");
</script>