Skip to content

Commit

Permalink
Tests for history.pushState() URL rewriting
Browse files Browse the repository at this point in the history
  • Loading branch information
domenic committed Aug 25, 2021
1 parent 07066e1 commit b4ba9a0
Showing 1 changed file with 145 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>URL rewriting allowed/disallowed for history.pushState()</title>
<link rel="help" href="https://github.com/whatwg/html/issues/6836">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<body>
<script>
"use strict";

const baseWithUsernamePassword = new URL(location.href);
baseWithUsernamePassword.username = "username";
baseWithUsernamePassword.password = "password";

const blobURL = URL.createObjectURL(new Blob(["foo"], { type: "text/html" }));
const blobURL2 = URL.createObjectURL(new Blob(["bar"], { type: "text/html" }));

const basicCases = [
[new URL("/common/blank.html", location.href), new URL("/common/blank.html#newhash", location.href), true],
[new URL("/common/blank.html", location.href), new URL("/newpath", location.href), true],
[new URL("/common/blank.html", location.href), new URL("/common/blank.html", baseWithUsernamePassword), false],
[blobURL, blobURL, true],
[blobURL, blobURL + "?newsearch", true],
[blobURL, blobURL + "#newhash", true],
[blobURL, "blob:newpath", false],
[blobURL, "blob:" + self.origin + "/syntheticblob", true],
[blobURL, blobURL2, true],

// Note: these are cases where we create the iframe pointing at the initial URL,
// so its origin will actually be self.origin.
["about:blank", "about:blank", false],
["about:blank", "about:srcdoc", false],
["about:blank", "about:blank?newsearch", false],
["about:blank", "about:blank#newhash", false],
["about:blank", self.origin + "/blank", false],
["javascript:'foo'", "javascript:'foo'", false],
["javascript:'foo'", "javascript:'foo'?newsearch", false],
["javascript:'foo'", "javascript:'foo'#newhash", false],
].map(([from, to, expectedToWork]) => [from.toString(), to.toString(), expectedToWork]);

for (const [from, to, expectedToWork] of basicCases) {
// Otherwise the messages are not consistent between runs which breaks some systems.
const fromForTitle = from.replaceAll(blobURL, "blob:(a blob URL for this origin)")
.replaceAll(blobURL2, "blob:(another blob URL for this origin)");
const toForTitle = to.replaceAll(blobURL, "blob:(a blob URL for this origin)")
.replaceAll(blobURL2, "blob:(another blob URL for this origin)");

promise_test(async () => {
const iframe = document.createElement("iframe");
iframe.src = from;
const loadPromise = new Promise(r => iframe.onload = r);

document.body.append(iframe);
await loadPromise;

if (expectedToWork) {
iframe.contentWindow.history.pushState(null, "", to);
assert_equals(iframe.contentWindow.location.href, to);
} else {
assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, () => {
iframe.contentWindow.history.pushState(null, "", to);
});
}
}, `${fromForTitle} to ${toForTitle} should ${expectedToWork ? "" : "not"} work`);
}

const srcdocCases = [
["about:srcdoc", false],
["about:srcdoc?newsearch", false],
["about:srcdoc#newhash", false],
[self.origin + "/srcdoc", false]
];

for (const [to, expectedToWork] of srcdocCases) {
promise_test(async () => {
const iframe = document.createElement("iframe");
iframe.srcdoc = "foo";
const loadPromise = new Promise(r => iframe.onload = r);

document.body.append(iframe);
await loadPromise;

if (expectedToWork) {
iframe.contentWindow.history.pushState(null, "", to);
assert_equals(iframe.contentWindow.location.href, to);
} else {
assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, () => {
iframe.contentWindow.history.pushState(null, "", to);
});
}
}, `about:srcdoc to ${to} should ${expectedToWork ? "" : "not"} work`);
}

// We need to test these separately since they're cross-origin.

const htmlInside = `
<script>
window.onmessage = ({ data }) => {
try {
history.pushState(null, "", data);
} catch (e) {
parent.postMessage({ result: "exception", exceptionName: e.name }, "*");
return;
}
parent.postMessage({ result: "no exception", locationHref: location.href }, "*");
};
</${'script'}>
`;

const dataURLStart = "data:text/html;base64," + btoa(htmlInside);

const dataURLCases = [
[dataURLStart, dataURLStart, false],
[dataURLStart, dataURLStart + "?newsearch", false],
[dataURLStart, dataURLStart + "#newhash", false],
[dataURLStart, "data:newpath", false]
];

for (const [from, to, expectedToWork] of dataURLCases) {
// Otherwise the messages are unreadably long.
const fromForTitle = from.replaceAll(dataURLStart, "data:(script to run this test)");
const toForTitle = to.replaceAll(dataURLStart, "data:(script to run this test)");

promise_test(async () => {
const iframe = document.createElement("iframe");
iframe.src = from;
const loadPromise = new Promise(r => iframe.onload = r);

document.body.append(iframe);
await loadPromise;

const messagePromise = new Promise(r => window.addEventListener("message", r, { once: true }));
iframe.contentWindow.postMessage("go", "*");
const { data } = await messagePromise;
if (expectedToWork) {
assert_equals(data.result, "no exception");
assert_equals(data.locationHref, to);
} else {
assert_equals(data.result, "exception");
assert_equals(data.exceptionName, "SecurityError");
}
}, `${fromForTitle} to ${toForTitle} should ${expectedToWork ? "" : "not"} work`);
}
</script>

0 comments on commit b4ba9a0

Please sign in to comment.