Skip to content

Commit 8047304

Browse files
annevkmoz-wptsync-bot
authored andcommitted
Bug 2079563 [wpt PR 63353] - WebKit export of https://bugs.webkit.org/show_bug.cgi?id=326665, a=testonly
Automatic update from web-platform-tests Separate same-origin documents with a different Document-Isolation-Policy into different agent clusters Same-origin documents can now be in different agent clusters, so an origin check no longer tells whether one document can reach another. Building on 322736@main, the UI process assigns each document an AgentClusterIdentifier, minted per browsing context group from the agent cluster key: site or origin, plus the cross-origin isolation key where one applies. The identifier, origin keying, and policy travel as an AgentClusterAssignment. A preliminary one is made at navigation action time and replaced at response time, when the policy headers are parsed. Loads that continue in another process get theirs through LoadParameters and GoToBackForwardItemParameters, and the injected bundle can no longer skip the response decision for documents that need it. Loads without a response decision now also record their historical agent cluster keying. The WebContent process no longer needs the BrowsingContextGroupIdentifier. isPlatformObjectSameOrigin() adds the agent cluster check to isSameOriginDomain() wherever one document can reach another's objects or run script in it, including javascript: URLs and the navigate event's sourceElement. Universal access bypasses it. The initial about:blank Window is not reused across agent clusters, and javascript: URL and XSLT results keep the identifier of the document they replace. See WICG/document-isolation-policy#11 WebKit-Bug: https://bugs.webkit.org/show_bug.cgi?id=326665 -- wpt-commits: d6713be63025929a1e8d8af45baaff0744b5e50d wpt-pr: 63353
1 parent e665f57 commit 8047304

5 files changed

Lines changed: 412 additions & 2 deletions

File tree

‎testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
<div id="log"></div>
88

99
<script type="module">
10-
import runTest from "./resources/data-url-test.mjs";
10+
import runTest from "./resources/javascript-url-test.mjs";
1111
runTest({ expected: false });
1212
</script>

‎testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
<div id="log"></div>
88

99
<script type="module">
10-
import runTest from "./resources/data-url-test.mjs";
10+
import runTest from "./resources/javascript-url-test.mjs";
1111
runTest({ expected: true });
1212
</script>
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
<!doctype html>
2+
<meta charset="utf-8">
3+
<meta name="timeout" content="long">
4+
<title>Document-Isolation-Policy: same-origin documents with a different policy are in different agent clusters</title>
5+
<script src="/resources/testharness.js"></script>
6+
<script src="/resources/testharnessreport.js"></script>
7+
<script src="/common/get-host-info.sub.js"></script>
8+
<script src="/common/utils.js"></script>
9+
<script src="/common/dispatcher/dispatcher.js"></script>
10+
<script src="./resources/common.js"></script>
11+
<body>
12+
<script>
13+
"use strict";
14+
15+
const origin = get_host_info().HTTPS_ORIGIN;
16+
const noCorpURL = get_host_info().HTTPS_REMOTE_ORIGIN + "/html/document-isolation-policy/resources/nothing-no-corp.js";
17+
// The smallest valid WebAssembly module.
18+
const emptyModuleBytes = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
19+
20+
function executorURL(policy, uuid) {
21+
return origin + executor_path + policy + `&uuid=${uuid}`;
22+
}
23+
24+
function createFrame(t, url) {
25+
const frame = document.createElement("iframe");
26+
t.add_cleanup(() => frame.remove());
27+
const loaded = new Promise(resolve => frame.onload = resolve);
28+
frame.src = url;
29+
document.body.append(frame);
30+
return loaded.then(() => frame);
31+
}
32+
33+
// Runs the body of an async function in the context identified by uuid and returns its result.
34+
async function runInContext(uuid, body) {
35+
const reply = token();
36+
send(uuid, `
37+
(async () => {
38+
let result;
39+
try {
40+
result = await (async () => { ${body} })();
41+
} catch (e) {
42+
result = e.name;
43+
}
44+
send("${reply}", JSON.stringify(result));
45+
})();
46+
`);
47+
return JSON.parse(await receive(reply));
48+
}
49+
50+
promise_test(async t => {
51+
const uuid = token();
52+
const frame = await createFrame(t, executorURL(dip_require_corp, uuid));
53+
assert_equals(frame.contentDocument, null, "contentDocument");
54+
assert_throws_dom("SecurityError", () => frame.contentWindow.document);
55+
assert_equals(await runInContext(uuid, "return parent.document.URL;"), "SecurityError");
56+
}, "A document without a policy and a same-origin frame with one cannot access each other");
57+
58+
promise_test(async t => {
59+
const uuid = token();
60+
await createFrame(t, executorURL(dip_require_corp, uuid));
61+
const result = await runInContext(uuid, `
62+
const child = document.createElement("iframe");
63+
const loaded = new Promise(resolve => child.onload = resolve);
64+
child.src = ${JSON.stringify(executorURL(dip_none, token()))};
65+
document.body.append(child);
66+
await loaded;
67+
let access;
68+
try {
69+
access = typeof child.contentWindow.document;
70+
} catch (e) {
71+
access = e.name;
72+
}
73+
return { contentDocument: child.contentDocument === null ? "null" : "document", access };
74+
`);
75+
assert_object_equals(result, { contentDocument: "null", access: "SecurityError" });
76+
}, "A document with a policy cannot access a same-origin frame without one");
77+
78+
promise_test(async t => {
79+
const uuid = token();
80+
await createFrame(t, executorURL(dip_require_corp, uuid));
81+
const result = await runInContext(uuid, `
82+
const child = document.createElement("iframe");
83+
const loaded = new Promise(resolve => child.onload = resolve);
84+
child.src = ${JSON.stringify(executorURL(dip_require_corp, token()))};
85+
document.body.append(child);
86+
await loaded;
87+
return { contentDocument: child.contentDocument === null ? "null" : "document", access: typeof child.contentWindow.document };
88+
`);
89+
assert_object_equals(result, { contentDocument: "document", access: "object" });
90+
}, "Same-origin documents with the same policy can access each other");
91+
92+
promise_test(async t => {
93+
const uuid = token();
94+
await createFrame(t, executorURL(dip_require_corp, uuid));
95+
const result = await runInContext(uuid, `
96+
const child = document.createElement("iframe");
97+
document.body.append(child);
98+
const accessible = child.contentDocument !== null;
99+
let fetchResult;
100+
try {
101+
await child.contentWindow.fetch(${JSON.stringify(noCorpURL)}, { mode: "no-cors" });
102+
fetchResult = "allowed";
103+
} catch (e) {
104+
fetchResult = "blocked";
105+
}
106+
return { accessible, fetchResult };
107+
`);
108+
assert_object_equals(result, { accessible: true, fetchResult: "blocked" });
109+
}, "An about:blank frame created by a document with a policy inherits it and can be accessed");
110+
111+
[
112+
{ policy: dip_none, expected: "number", description: "without a policy reuses" },
113+
{ policy: dip_require_corp, expected: "undefined", description: "with a policy does not reuse" },
114+
].forEach(({ policy, expected, description }) => {
115+
promise_test(async t => {
116+
const uuid = token();
117+
const popup = window.open(executorURL(policy, uuid));
118+
t.add_cleanup(() => popup.close());
119+
popup.expando = 1;
120+
assert_equals(await runInContext(uuid, "return typeof window.expando;"), expected);
121+
}, `Navigating the initial about:blank document of a popup to a same-origin document ${description} its Window`);
122+
});
123+
124+
promise_test(async t => {
125+
const uuid = token();
126+
const popup = window.open(executorURL(dip_require_corp, uuid));
127+
t.add_cleanup(() => popup.close());
128+
assert_equals(await runInContext(uuid, "return opener.document.URL;"), "SecurityError");
129+
assert_throws_dom("SecurityError", () => popup.document);
130+
}, "A document without a policy and a same-origin popup with one cannot access each other");
131+
132+
promise_test(async t => {
133+
const uuid = token();
134+
await createFrame(t, executorURL(dip_require_corp, uuid));
135+
assert_equals(await runInContext(uuid, "return originAgentCluster;"), true);
136+
}, "A document with a policy is in an origin-keyed agent cluster");
137+
138+
promise_test(async t => {
139+
const uuid = token();
140+
const frame = await createFrame(t, executorURL(dip_require_corp, uuid));
141+
frame.contentWindow.location = "javascript:void(window.ranViaLocation = true)";
142+
frame.src = "javascript:void(window.ranViaSrc = true)";
143+
await new Promise(resolve => t.step_timeout(resolve, 500));
144+
assert_object_equals(await runInContext(uuid, "return { location: window.ranViaLocation === true, src: window.ranViaSrc === true };"), { location: false, src: false });
145+
}, "A document without a policy cannot run a javascript: URL in a same-origin frame with one");
146+
147+
promise_test(async t => {
148+
const uuid = token();
149+
await createFrame(t, executorURL(dip_require_corp, uuid));
150+
await runInContext(uuid, `
151+
${appendFrameScript(executorURL(dip_require_corp, token()))}
152+
window.child = child;
153+
child.contentWindow.location = "javascript:void(window.ranViaLocation = true)";
154+
return "navigated";
155+
`);
156+
await new Promise(resolve => t.step_timeout(resolve, 500));
157+
assert_true(await runInContext(uuid, "return child.contentWindow.ranViaLocation === true;"));
158+
}, "A document with a policy can run a javascript: URL in a same-origin frame with one");
159+
160+
// Clicks a link in the context identified by uuid that navigates the frame named targetName to targetURL.
161+
function clickLinkScript(targetName, targetURL) {
162+
return `
163+
const link = document.createElement("a");
164+
link.target = ${JSON.stringify(targetName)};
165+
link.href = ${JSON.stringify(targetURL)};
166+
document.body.append(link);
167+
link.click();
168+
return "clicked";
169+
`;
170+
}
171+
172+
async function createNamedFrameWithNavigateListener(t, name) {
173+
const uuid = token();
174+
const frame = document.createElement("iframe");
175+
frame.name = name;
176+
t.add_cleanup(() => frame.remove());
177+
const loaded = new Promise(resolve => frame.onload = resolve);
178+
frame.src = executorURL(dip_none, uuid);
179+
document.body.append(frame);
180+
await loaded;
181+
const reply = token();
182+
await runInContext(uuid, `
183+
navigation.onnavigate = event => send("${reply}", event.sourceElement ? "element" : "null");
184+
return "ready";
185+
`);
186+
return { url: frame.src, reply };
187+
}
188+
189+
promise_test(async t => {
190+
const name = token();
191+
const { url, reply } = await createNamedFrameWithNavigateListener(t, name);
192+
const uuid = token();
193+
await createFrame(t, executorURL(dip_require_corp, uuid));
194+
await runInContext(uuid, clickLinkScript(name, url + "#fragment"));
195+
assert_equals(await receive(reply), "null");
196+
}, "A navigate event in a same-origin frame without a policy does not expose a link of a document with one as its sourceElement");
197+
198+
promise_test(async t => {
199+
const name = token();
200+
const { url, reply } = await createNamedFrameWithNavigateListener(t, name);
201+
const link = document.createElement("a");
202+
link.target = name;
203+
link.href = url + "#fragment";
204+
document.body.append(link);
205+
t.add_cleanup(() => link.remove());
206+
link.click();
207+
assert_equals(await receive(reply), "element");
208+
}, "A navigate event in a same-origin frame without a policy exposes a link of a document without one as its sourceElement");
209+
210+
promise_test(async t => {
211+
const uuid = token();
212+
// The body arrives two seconds after the headers, so the fragment navigation happens while the reload is waiting for it.
213+
const frame = await createFrame(t, origin + executor_path + dip_require_corp + "|trickle(d2)" + `&uuid=${uuid}`);
214+
const reloaded = new Promise(resolve => frame.onload = resolve);
215+
await runInContext(uuid, `
216+
location.reload();
217+
// The body of this response arrives after a second, while the reload is still waiting for its own.
218+
fetch("/common/blank.html?pipe=trickle(d1)").then(response => response.text()).then(() => navigation.navigate("#fragment"));
219+
return "reloading";
220+
`);
221+
await reloaded;
222+
assert_equals(frame.contentDocument, null, "contentDocument");
223+
assert_throws_dom("SecurityError", () => frame.contentWindow.document);
224+
}, "A fragment navigation during a reload of a same-origin frame with a policy does not move the reloaded document into another agent cluster");
225+
226+
[
227+
{ policy: dip_none, expected: "message", description: "without a policy succeeds" },
228+
{ policy: dip_require_corp, expected: "messageerror", description: "with a policy fails" },
229+
].forEach(({ policy, expected, description }) => {
230+
promise_test(async t => {
231+
const uuid = token();
232+
const frame = await createFrame(t, executorURL(policy, uuid));
233+
await runInContext(uuid, `
234+
window.received = new Promise(resolve => {
235+
onmessage = () => resolve("message");
236+
onmessageerror = () => resolve("messageerror");
237+
});
238+
return "ready";
239+
`);
240+
frame.contentWindow.postMessage(new WebAssembly.Module(emptyModuleBytes), "*");
241+
assert_equals(await runInContext(uuid, "return window.received;"), expected);
242+
}, `Posting a WebAssembly.Module to a same-origin frame ${description}`);
243+
});
244+
245+
const remoteOrigin = get_host_info().HTTPS_REMOTE_ORIGIN;
246+
const coopSameOrigin = "|header(Cross-Origin-Opener-Policy,same-origin)";
247+
const coepRequireCorp = "|header(Cross-Origin-Embedder-Policy,require-corp)";
248+
249+
// Opens a cross-origin isolated popup and returns its uuid.
250+
function openCrossOriginIsolatedPopup(t) {
251+
const uuid = token();
252+
window.open(origin + executor_path + coopSameOrigin + coepRequireCorp + `&uuid=${uuid}`);
253+
t.add_cleanup(() => send(uuid, "window.close();"));
254+
return uuid;
255+
}
256+
257+
function appendFrameScript(url) {
258+
return `
259+
const child = document.createElement("iframe");
260+
const loaded = new Promise(resolve => child.onload = resolve);
261+
child.src = ${JSON.stringify(url)};
262+
document.body.append(child);
263+
await loaded;
264+
`;
265+
}
266+
267+
promise_test(async t => {
268+
const topUUID = openCrossOriginIsolatedPopup(t);
269+
const childUUID = token();
270+
const result = await runInContext(topUUID, `
271+
${appendFrameScript(origin + executor_path + coepRequireCorp + dip_require_corp + `&uuid=${childUUID}`)}
272+
window.child = child;
273+
let access;
274+
try {
275+
access = typeof child.contentWindow.document;
276+
} catch (e) {
277+
access = e.name;
278+
}
279+
return { crossOriginIsolated, access };
280+
`);
281+
assert_object_equals(result, { crossOriginIsolated: true, access: "object" });
282+
283+
await runInContext(childUUID, `
284+
window.received = new Promise(resolve => {
285+
onmessage = () => resolve("message");
286+
onmessageerror = () => resolve("messageerror");
287+
});
288+
return "ready";
289+
`);
290+
await runInContext(topUUID, `
291+
child.contentWindow.postMessage(new WebAssembly.Module(new Uint8Array([${emptyModuleBytes}])), "*");
292+
return "posted";
293+
`);
294+
assert_equals(await runInContext(childUUID, "return window.received;"), "message");
295+
}, "A frame with a policy that is same origin with a cross-origin isolated top-level document is in its agent cluster");
296+
297+
promise_test(async t => {
298+
const topUUID = openCrossOriginIsolatedPopup(t);
299+
const frameUUID = token();
300+
await runInContext(topUUID, `
301+
${appendFrameScript(remoteOrigin + executor_path + coepRequireCorp + corp_cross_origin + dip_require_corp + `&uuid=${frameUUID}`)}
302+
return "loaded";
303+
`);
304+
const accessChild = policy => `
305+
${appendFrameScript(remoteOrigin + executor_path + coepRequireCorp + policy + `&uuid=${token()}`)}
306+
try {
307+
return typeof child.contentWindow.document;
308+
} catch (e) {
309+
return e.name;
310+
}
311+
`;
312+
assert_equals(await runInContext(frameUUID, accessChild(dip_none)), "SecurityError", "same-origin child without a policy");
313+
assert_equals(await runInContext(frameUUID, accessChild(dip_require_corp)), "object", "same-origin child with a policy");
314+
}, "A frame with a policy that is cross origin with a cross-origin isolated top-level document is not in the agent cluster of same-origin documents without one");
315+
</script>

0 commit comments

Comments
 (0)