Skip to content

Commit

Permalink
App history: fix cross origin-domain cross-document navigate events
Browse files Browse the repository at this point in the history
Per the spec, do not fire navigate events for cross-document navigations
where the initiating window is cross origin-domain from the target
window. This was working with origin isolation enabled (the default
for WPTs), but was not working with only site isolation (the default for
real users).

Also introduce a more comprehensive test matrix for these cross-window
navigations and what navigate events they fire.

Co-authored-by: Nate Chapin <japhet@chromium.org>
Fixed: 1266580
Change-Id: Ib5e96287a5306e17e41b407db47774b6beef8119
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3256148
Reviewed-by: Mason Freed <masonf@chromium.org>
Reviewed-by: Nate Chapin <japhet@chromium.org>
Commit-Queue: Domenic Denicola <domenic@chromium.org>
Cr-Commit-Position: refs/heads/main@{#939463}
  • Loading branch information
2 people authored and pull[bot] committed Nov 12, 2023
1 parent 205183d commit 1192701
Show file tree
Hide file tree
Showing 28 changed files with 796 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>

<script>
document.domain = "{{host}}";
async_test(t => {
const url = new URL("resources/document-domain-setter.sub.html", location.href);
url.hostname = "{{domains[www1]}}";
const iframe = document.createElement("iframe");
iframe.name = "windowname";
iframe.src = url;
document.body.append(iframe);

url.search = "?foo";
const link = document.createElement("a");
link.href = url;
link.target = iframe.name;
document.body.append(link);

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => {
iframe.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_false(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, link.href, "destination.url");
assert_false(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

link.click();
});
}, "clicking on an <a> element that navigates cross-document targeting a same-origin-domain (but cross-origin) window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body>

<script>
async_test(t => {
const url = new URL("resources/cross-origin-iframe-helper.html", location.href);
url.hostname = get_host_info().REMOTE_HOST;
const iframe = document.createElement("iframe");
iframe.src = url;
iframe.name = "windowname";
document.body.append(iframe);

url.search = "?postMessage-top-when-done";
const link = document.createElement("a");
link.href = url;
link.target = iframe.name;
document.body.append(link);

window.onmessage = t.step_func_done(e => {
// If we hit onnavigate in the target window, we'll get a different message, and fail.
assert_equals(e.data, "DONE");
});

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => link.click());
}, "clicking on an <a> element that navigates cross-document targeting a cross-origin window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<a id="link" href="/common/blank.html?foo" target="windowname">Click me</a>
<iframe id="i" name="windowname" src="/common/blank.html"></iframe>

<script>
async_test(t => {
appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => {
i.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_false(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, link.href, "destination.url");
assert_false(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

link.click();
});
}, "clicking on an <a> element that navigates cross-document targeting a same-origin window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>

<script>
document.domain = "{{host}}";
async_test(t => {
const url = new URL("resources/document-domain-setter.sub.html", location.href);
url.hostname = "{{domains[www1]}}";
const iframe = document.createElement("iframe");
iframe.name = "windowname";
iframe.src = url;
document.body.append(iframe);

url.hash = "#foo";
const link = document.createElement("a");
link.href = url;
link.target = iframe.name;
document.body.append(link);

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => {
iframe.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_true(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, link.href, "destination.url");
assert_true(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

link.click();
});
}, "clicking on an <a> element that navigates same-document targeting a same-origin-domain (but cross-origin) window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body>

<script>
async_test(t => {
const url = new URL("resources/cross-origin-iframe-helper.html", location.href);
url.hostname = get_host_info().REMOTE_HOST;
const iframe = document.createElement("iframe")
iframe.src = url;
iframe.name = "windowname";
document.body.append(iframe);

url.hash = "#foo";
const link = document.createElement("a");
link.href = url;
link.target = iframe.name;
document.body.append(link);

window.onmessage = t.step_func_done(e => {
assert_equals(e.data.navigationType, "push", "navigationType");
assert_true(e.data.cancelable, "cancelable");
assert_true(e.data.canTransition, "canTransition");
assert_false(e.data.userInitiated, "userInitiated");
assert_true(e.data.hashChange, "hashChange");
assert_equals(e.data.formData, null, "formData");
assert_equals(e.data.destination.url, link.href, "destination.url");
assert_true(e.data.destination.sameDocument, "destination.sameDocument");
assert_equals(e.data.destination.key, null, "destination.key");
assert_equals(e.data.destination.id, null, "destination.id");
assert_equals(e.data.destination.index, -1, "destination.index");
});

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => link.click());
}, "clicking on an <a> element that navigates same-document targeting a cross-origin window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<a id="link" href="/common/blank.html#foo" target="windowname">Click me</a>
<iframe id="i" name="windowname" src="/common/blank.html"></iframe>

<script>
async_test(t => {
i.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_true(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, link.href, "destination.url");
assert_true(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => link.click());
}, "clicking on an <a> element that navigates same-document targeting a same-origin window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>

<script>
document.domain = "{{host}}";
async_test(t => {
const url = new URL("resources/document-domain-setter.sub.html", location.href);
url.hostname = "{{domains[www1]}}";
const iframe = document.createElement("iframe");
iframe.src = url;
document.body.append(iframe);

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => {
iframe.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_false(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, iframe.src + "?foo", "destination.url");
assert_false(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

iframe.contentWindow.location.href = url + "?foo";
});
}, "using location.href to navigate cross-document targeting a same-origin-domain (but cross-origin) window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body>

<script>
async_test(t => {
const iframeURL = new URL("resources/cross-origin-iframe-helper.html", location.href);
iframeURL.hostname = get_host_info().REMOTE_HOST;
const iframe = document.createElement("iframe");
iframe.src = iframeURL;
document.body.append(iframe);

window.onmessage = t.step_func_done(e => {
// If we hit onnavigate in the target window, we'll get a different message, and fail.
assert_equals(e.data, "DONE");
});

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => iframe.contentWindow.location.href = iframeURL + "?postMessage-top-when-done");
}, "using location.href to navigate cross-document targeting a cross-origin window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<iframe id="i" src="/common/blank.html"></iframe>

<script>
async_test(t => {
i.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_false(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, i.src + "?foo", "destination.url");
assert_false(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => i.contentWindow.location.href = "/common/blank.html?foo");
}, "using location.href to navigate cross-document targeting a same-origin window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>

<script>
document.domain = "{{host}}";
async_test(t => {
const url = new URL("resources/document-domain-setter.sub.html", location.href);
url.hostname = "{{domains[www1]}}";
const iframe = document.createElement("iframe");
iframe.src = url;
document.body.append(iframe);

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => {
iframe.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_true(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, iframe.src + "#foo", "destination.url");
assert_true(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

iframe.contentWindow.location.href = url + "#foo";
});
}, "using location.href to navigate same-document targeting a same-origin-domain (but cross-origin) window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body>

<script>
async_test(t => {
const iframeURL = new URL("resources/cross-origin-iframe-helper.html", location.href);
iframeURL.hostname = get_host_info().REMOTE_HOST;
const iframe = document.createElement("iframe")
iframe.src = iframeURL;
document.body.append(iframe);

window.onmessage = t.step_func_done(e => {
assert_equals(e.data.navigationType, "push", "navigationType");
assert_true(e.data.cancelable, "cancelable");
assert_true(e.data.canTransition, "canTransition");
assert_false(e.data.userInitiated, "userInitiated");
assert_true(e.data.hashChange, "hashChange");
assert_equals(e.data.formData, null, "formData");
assert_equals(e.data.destination.url, iframe.src + "#foo", "destination.url");
assert_true(e.data.destination.sameDocument, "destination.sameDocument");
assert_equals(e.data.destination.key, null, "destination.key");
assert_equals(e.data.destination.id, null, "destination.id");
assert_equals(e.data.destination.index, -1, "destination.index");
});

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => iframe.contentWindow.location.href = iframeURL + "#foo");
}, "using location.href to navigate same-document targeting a cross-origin window");
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<iframe id="i" src="/common/blank.html"></iframe>

<script>
async_test(t => {
i.contentWindow.appHistory.onnavigate = t.step_func_done(e => {
assert_equals(e.navigationType, "push", "navigationType");
assert_true(e.cancelable, "cancelable");
assert_true(e.canTransition, "canTransition");
assert_false(e.userInitiated, "userInitiated");
assert_true(e.hashChange, "hashChange");
assert_equals(e.formData, null, "formData");
assert_equals(e.destination.url, i.src + "#foo", "destination.url");
assert_true(e.destination.sameDocument, "destination.sameDocument");
assert_equals(e.destination.key, null, "destination.key");
assert_equals(e.destination.id, null, "destination.id");
assert_equals(e.destination.index, -1, "destination.index");
});

appHistory.onnavigate = t.unreached_func("onnavigate must not fire in the source window");
window.onload = t.step_func(() => i.contentWindow.location.href = "/common/blank.html#foo");
}, "using location.href to navigate same-document targeting a same-origin window");
</script>
Loading

0 comments on commit 1192701

Please sign in to comment.