Skip to content

Commit acd4d35

Browse files
theIDinsideKurtCattiSchmidt
authored andcommitted
Implement location.ancestorOrigins
This implements this attribute on Location and also adheres to the changes to the spec introduced in whatwg/html#11560 Tentative tests are also added in this patch. Differential Revision: https://phabricator.services.mozilla.com/D273393 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1085214 gecko-commit: 626bfdb90f3e1a0f78c6a6e832697520396e66bf gecko-reviewers: necko-reviewers, webidl, dom-core, smaug, jesup, zcorpan
1 parent b8cd5da commit acd4d35

File tree

4 files changed

+461
-0
lines changed

4 files changed

+461
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Location.ancestorOrigins lifetime behavior</title>
6+
<link rel="help" href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#dom-location-ancestororigins">
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
</head>
10+
<body>
11+
<script>
12+
function createIframeAndNavigate(test, src) {
13+
return new Promise(resolve => {
14+
const iframe = document.createElement("iframe");
15+
iframe.onload = () => {
16+
resolve(iframe);
17+
}
18+
iframe.src = src;
19+
document.body.appendChild(iframe);
20+
test.add_cleanup(() => {
21+
iframe.remove();
22+
});
23+
});
24+
}
25+
26+
27+
promise_test(async t => {
28+
assert_implements(location.ancestorOrigins, "location.ancestorOrigins not implemented");
29+
const iframe = await createIframeAndNavigate(t, "about:blank");
30+
assert_array_equals(
31+
iframe.contentWindow.location.ancestorOrigins,
32+
[location.origin],
33+
"Initial ancestorOrigins should match expected placeholder value"
34+
);
35+
36+
const loc = iframe.contentWindow.location;
37+
iframe.remove();
38+
39+
assert_array_equals(
40+
Array.from(loc.ancestorOrigins),
41+
[],
42+
"ancestorOrigins should be empty after iframe removal"
43+
);
44+
}, "location.ancestorOrigins returns empty list after iframe is removed and referenced Location's relevant document is null");
45+
46+
promise_test(async t => {
47+
assert_implements(location.ancestorOrigins, "location.ancestorOrigins not implemented");
48+
const iframe = await createIframeAndNavigate(t, "about:blank");
49+
assert_array_equals(
50+
iframe.contentWindow.location.ancestorOrigins,
51+
[location.origin],
52+
"Initial ancestorOrigins should match expected placeholder value"
53+
);
54+
55+
const loc = iframe.contentWindow.location;
56+
await new Promise(resolve => {
57+
iframe.onload = resolve;
58+
iframe.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/common/blank.html";
59+
});
60+
61+
assert_array_equals(
62+
Array.from(loc.ancestorOrigins),
63+
[],
64+
"ancestorOrigins should be empty after iframe navigation"
65+
);
66+
}, "location.ancestorOrigins returns empty list when iframe navigated away and referenced Location's relevant document is null");
67+
</script>
68+
</body>
69+
70+
</html>
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
<!DOCTYPE html>
2+
<title>
3+
Location#ancestorOrigins test
4+
</title>
5+
<script src="/resources/testharness.js"></script>
6+
<script src="/resources/testharnessreport.js"></script>
7+
<script src="/resources/testdriver.js"></script>
8+
<script src="/resources/testdriver-vendor.js"></script>
9+
<script src="./resources/location-ancestor-origins-create-iframe.js"></script>
10+
11+
<body>
12+
<script>
13+
function waitFor(action, frameName) {
14+
return new Promise((resolve) => {
15+
window.addEventListener("message", function listener(e) {
16+
if (e.data.event === action && e.data.name === frameName) {
17+
window.removeEventListener("message", listener);
18+
resolve(event.data);
19+
}
20+
});
21+
});
22+
}
23+
24+
const frameCreatorPath = "html/browsers/history/the-location-interface/resources/location-ancestor-origins-recursive-iframe.html"
25+
26+
const createOrigin = (src) => {
27+
const url = new URL(src);
28+
return {
29+
src: src,
30+
origin: url.origin,
31+
// change referrer policy value in header
32+
withHeaders: (policyValue) => {
33+
if (!policyValue)
34+
return src;
35+
return `${src}&pipe=header(Referrer-Policy,${policyValue})`;
36+
}
37+
}
38+
}
39+
40+
// iframeSources[0] origins shares origin with top level
41+
let iframeSources = [
42+
createOrigin(`http://{{hosts[][]}}:{{ports[http][0]}}/${frameCreatorPath}?a`),
43+
createOrigin(`http://{{hosts[alt][]}}:{{ports[http][0]}}/${frameCreatorPath}?b`),
44+
createOrigin(`http://{{hosts[][www]}}:{{ports[http][0]}}/${frameCreatorPath}?c`)
45+
];
46+
47+
const A = iframeSources[0]
48+
const B = iframeSources[1];
49+
const C = iframeSources[2];
50+
51+
const policy = {
52+
Default: "",
53+
NoRef: "no-referrer",
54+
SameOrigin: "same-origin"
55+
}
56+
57+
// options takes optional values for iframe attribute, or a pre-configure
58+
// step before creating the iframe that a call to `config` configures,
59+
// like changing iframe referrer policy of a frame in a specific target.
60+
// options is created by calling mutatePolicyOf, insertMetaForWithPolicyOf
61+
// or an object containing { sandbox: boolean }
62+
// The options argument can specify something that should happen before
63+
// the creation of the frame this configures.
64+
65+
// for example: config("B2", B.src, policy.Default, mutatePolicyOf("B1", "")) means
66+
// create a frame named B2 in the inner-most frame, with src=B.src, referrerPolicy = the default
67+
// but before the frame is bound to the tree,
68+
// perform an action in "B1" that mutates B1's referrer policy attribute (in this case to the default, i.e. the empty string)
69+
const config = (name, src, iframeRefPolicyAttr, options = null) => ({ name: name, src, referrerPolicy: iframeRefPolicyAttr, options })
70+
71+
const mutatePolicyOf = (target, value) => {
72+
return { target, action: Actions.changeReferrerPolicy, value }
73+
}
74+
75+
const insertMetaForWithPolicyOf = (target, value) => {
76+
return { target, action: Actions.insertMetaElement, value }
77+
}
78+
79+
// Top-level === A1
80+
// Only referrerpolicy on the element (e.g. iframe) should affect the ancestorOrigins list.
81+
// Some of these tests may seem counterintuitive/not useful, but they are to make sure that nothing but
82+
// that attribute, has effect.
83+
const testCases = [
84+
{
85+
description: "test A1 -> B1 (no-referrer in response header) -> C1 -> B2",
86+
frames: [
87+
config("B1", B.withHeaders(policy.NoRef), policy.Default),
88+
config("C1", C.src, policy.Default),
89+
config("B2", B.src, policy.Default),
90+
],
91+
expected: [
92+
[A.origin],
93+
[B.origin, A.origin],
94+
[C.origin, B.origin, A.origin],
95+
]
96+
},
97+
{
98+
description: "test A1 -> A2 (no-referrer in response header) -> B1 -> C1",
99+
frames: [
100+
config("A2", A.withHeaders(policy.NoRef), policy.Default),
101+
config("B1", B.src, policy.Default),
102+
config("C1", C.src, policy.Default),
103+
],
104+
expected: [
105+
[A.origin],
106+
[A.origin, A.origin],
107+
[B.origin, A.origin, A.origin],
108+
]
109+
},
110+
{
111+
description: "test no-referrer attribute on iframe for iframe containing B1",
112+
frames: [
113+
config("B1", B.src, policy.NoRef)
114+
],
115+
expected: [
116+
["null"]
117+
]
118+
},
119+
{
120+
description: "test default referrer policy, A1 -> B1 -> A2",
121+
frames: [
122+
config("B1", B.src, policy.Default),
123+
config("A2", A.src, policy.Default)
124+
],
125+
expected: [
126+
[A.origin],
127+
[B.origin, A.origin]
128+
]
129+
},
130+
{
131+
description: "test toggle of masked A1 -> B1 iframe for A2 sets no-referrer -> A2",
132+
frames: [
133+
config("B1", B.src, policy.Default),
134+
config("A2", A.src, policy.NoRef)
135+
],
136+
expected: [
137+
[A.origin],
138+
["null", A.origin]
139+
]
140+
},
141+
{
142+
description: "test of toggle of masked at first origin != parentDocOrigin, A1 -> B1 -> B2 -> A2",
143+
frames: [
144+
config("B1", B.src, policy.Default),
145+
config("B2", B.src, policy.Default),
146+
config("A2", A.src, policy.NoRef)
147+
],
148+
expected: [
149+
[A.origin],
150+
[B.origin, A.origin],
151+
["null", "null", A.origin]
152+
]
153+
},
154+
{
155+
description: "test sandboxed iframe in A1, A1 iframe attribute sandbox -> C1 -> B1 -> C2",
156+
frames: [
157+
config("C1", C.src, policy.Default, { sandbox: true }),
158+
config("B1", B.src, policy.Default),
159+
config("C2", C.src, policy.Default),
160+
],
161+
expected: [
162+
[A.origin],
163+
["null", A.origin],
164+
["null", "null", A.origin],
165+
]
166+
},
167+
{
168+
description: "Test same-origin referrer policy, A1 -> B1 -> A2 iframe attribute same-origin -> B2",
169+
frames: [
170+
config("B1", B.src, policy.Default),
171+
config("A2", A.src, policy.Default),
172+
config("B2", B.src, policy.SameOrigin)
173+
],
174+
expected: [
175+
[A.origin],
176+
[B.origin, A.origin],
177+
["null", B.origin, A.origin]
178+
]
179+
},
180+
{
181+
description: "Test that mutating the iframe referrerpolicy attribute of B1 before creation of B2 does not impact the hiding/non-hiding when creating a grandchild browsing context (keep null)",
182+
frames: [
183+
config("B1", B.src, policy.Default),
184+
config("C1", C.src, policy.NoRef),
185+
// change <iframe src=C1> in B1, referrer policy to default, before creating this context
186+
config("B2", B.src, policy.Default, mutatePolicyOf("B1", "")),
187+
],
188+
expected: [
189+
[A.origin],
190+
["null", A.origin],
191+
[C.origin, "null", A.origin]
192+
],
193+
},
194+
{
195+
description: "Test that mutating the iframe referrerpolicy attribute does not impact the hiding/non-hiding when creating a grandchild browsing context (keep origins)",
196+
frames: [
197+
config("B1", B.src, policy.Default),
198+
config("C1", C.src, policy.Default),
199+
// change <iframe src=C1> in B1, referrer policy to no-referrer, before creating this context
200+
config("B2", B.src, policy.Default, mutatePolicyOf("B1", "no-referrer")),
201+
],
202+
expected: [
203+
[A.origin],
204+
[B.origin, A.origin],
205+
[C.origin, B.origin, A.origin]
206+
],
207+
},
208+
{
209+
description: "Test that tightening policy using meta, does not affect list",
210+
frames: [
211+
config("B1", B.src, policy.Default),
212+
config("C1", C.src, policy.Default, insertMetaForWithPolicyOf("B1", "no-referrer")),
213+
],
214+
expected: [
215+
[A.origin],
216+
[B.origin, A.origin],
217+
],
218+
},
219+
{
220+
description: "Test that loosening policy in policy container has no effect.",
221+
frames: [
222+
config("B1", B.withHeaders("no-referrer"), policy.Default),
223+
config("C1", C.src, policy.Default, insertMetaForWithPolicyOf("B1", "strict-origin-when-cross-origin")),
224+
],
225+
expected: [
226+
[A.origin],
227+
[B.origin, A.origin],
228+
],
229+
},
230+
]
231+
232+
testCases.forEach((cfg, index) => {
233+
promise_test(async (t) => {
234+
assert_implements(location.ancestorOrigins, "location.ancestorOrigins not implemented");
235+
const iframeDetails = cfg.frames[0];
236+
237+
const topFrameAncestorOrigins = waitFor("sentOrigins", iframeDetails.name);
238+
const iframe = document.createElement("iframe");
239+
await configAndNavigateIFrame(iframe, iframeDetails);
240+
const res = await topFrameAncestorOrigins;
241+
242+
t.add_cleanup(() => {
243+
iframe.remove();
244+
});
245+
246+
let results = [res];
247+
248+
for (let i = 1; i < cfg.frames.length; ++i) {
249+
// name of frame, that shall create a new frame
250+
const parentName = cfg.frames[i - 1].name;
251+
const { target, action, value } = cfg.frames[i].options ?? {};
252+
if (target) {
253+
iframe.contentWindow.postMessage({ action: action, request: { value: value }, name: target }, "*");
254+
}
255+
iframe.contentWindow.postMessage({ action: Actions.addFrame, request: cfg.frames[i], name: parentName }, "*");
256+
const res = await waitFor("sentOrigins", cfg.frames[i].name);
257+
results.push(res);
258+
}
259+
260+
let i = 0;
261+
for (const { event, name, origins } of results) {
262+
assert_equals(origins.length, cfg.expected[i].length, `[test configuration ${index}, frame ${name}]`);
263+
assert_array_equals(origins, cfg.expected[i]);
264+
i += 1;
265+
}
266+
}, cfg.description);
267+
});
268+
269+
[{ policy: null, expected: [A.origin] }, { policy: "no-referrer", expected: ["null"] }].forEach(test => {
270+
promise_test(async t => {
271+
assert_implements(location.ancestorOrigins, "location.ancestorOrigins not implemented");
272+
const iframe = document.createElement("iframe");
273+
t.add_cleanup(() => {
274+
iframe.remove();
275+
})
276+
iframe.referrerPolicy = test.policy;
277+
document.body.appendChild(iframe);
278+
assert_array_equals(Array.from(iframe.contentWindow.location.ancestorOrigins), test.expected);
279+
}, `about:blank's location.ancestorOrigin policy=${test.policy}`)
280+
})
281+
282+
// Tests bottom-most frame's results only.
283+
const expectedAboutBlankResults = [
284+
{
285+
description: "A -> about:blank -> B1 -> B2",
286+
expected: [B.origin, A.origin, A.origin],
287+
setNoreferrer: false
288+
},
289+
{
290+
description: "A -> about:blank, that set iframe referrer policy=no-referrer -> B1 -> B2",
291+
expected: [B.origin, "null", "null"],
292+
setNoreferrer: true
293+
}
294+
].forEach(test => {
295+
promise_test(async (t) => {
296+
assert_implements(location.ancestorOrigins, "location.ancestorOrigins not implemented");
297+
298+
const iframe1 = document.createElement("iframe");
299+
// top level document's origin = A1.
300+
iframe1.name = "A2";
301+
t.add_cleanup(() => {
302+
iframe1.remove();
303+
});
304+
document.body.appendChild(iframe1);
305+
306+
let iframe2 = document.createElement("iframe");
307+
iframe2.name = "B1";
308+
309+
if (test.setNoreferrer) {
310+
iframe2.referrerPolicy = "no-referrer";
311+
}
312+
313+
let p = new Promise((resolve) => {
314+
iframe2.addEventListener("load", resolve, { once: true });
315+
iframe2.src = B.src;
316+
});
317+
iframe1.contentDocument.body.appendChild(iframe2);
318+
await p;
319+
320+
let resPromise = waitFor("sentOrigins", "B2");
321+
iframe2.contentWindow.postMessage({ action: Actions.addFrame, request: config("B2", B.src, policy.Default), name: "B1" }, "*")
322+
let res = await resPromise;
323+
324+
assert_array_equals(Array.from(res.origins), test.expected);
325+
}, test.description);
326+
})
327+
</script>
328+
</body>

0 commit comments

Comments
 (0)