summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/browsers/origin
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/html/browsers/origin
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/browsers/origin')
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html33
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html49
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-common.js34
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-length.html45
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html45
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-on-new-window.html25
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html718
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame-with-then.html19
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame.html51
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/location-properties-smoke-test.window.js51
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/resources/cross-origin-due-to-document-domain-only-helper.html9
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html63
-rw-r--r--testing/web-platform/tests/html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html106
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-iframe.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-window.html25
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/about-srcdoc.html29
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js10
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/javascript-url.html33
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html40
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html7
-rw-r--r--testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-bad-subdomain.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-port.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-same.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain-with-redirect.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html26
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html26
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html36
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html37
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html37
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html37
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html36
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html39
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html38
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/META.yml5
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/README.md30
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html63
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html52
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html15
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html15
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-no.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-no.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-to-javascript-test.mjs33
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-url-test.mjs13
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/helpers.mjs28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/javascript-url-test.mjs14
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-iframe-test.sub.mjs20
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-same-origin-iframe-test.sub.mjs20
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-no.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html62
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html41
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html41
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html40
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html41
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html43
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html41
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html41
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html41
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html26
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups-crash.https.html9
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html27
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html28
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/regression-1399759.https.sub.html100
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html46
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/README.md6
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html5
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html.headers2
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html6
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html12
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs390
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs63
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py43
-rw-r--r--testing/web-platform/tests/html/browsers/origin/origin-of-data-document.html30
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain.html35
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html305
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html76
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_srcdoc.html84
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html21
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.headers1
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html52
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.sub.js65
-rw-r--r--testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html12
156 files changed, 4865 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html
new file mode 100644
index 0000000000..425374faec
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Cross-origin due to document.domain</title>
+<meta charset=utf-8>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<iframe src=resources/cross-origin-due-to-document-domain-only-helper.html></iframe>
+<script>
+async_test(t => {
+ onload = t.step_func_done(() => {
+ const frame = document.querySelector("iframe");
+ const innerSelf = self[0];
+ const innerLocation = innerSelf.location;
+ const innerDocument = innerSelf.document;
+ assert_equals(innerLocation.host, location.host);
+ assert_true(innerSelf.expandosForever);
+ assert_true(innerLocation.expandosForever);
+ assert_equals(frame.contentWindow, innerSelf);
+ assert_equals(frame.contentDocument, innerDocument);
+ innerSelf.setDocumentDomain();
+ assert_throws_dom("SecurityError", () => innerSelf.expandosForever);
+ assert_throws_dom("SecurityError", () => innerLocation.expandosForever);
+ assert_throws_dom("SecurityError", () => innerLocation.host);
+ assert_equals(innerSelf.parent, self);
+ assert_throws_dom("SecurityError", () => innerSelf.frameElement);
+ assert_throws_dom("SecurityError", () => innerLocation.reload());
+ assert_equals(frame.contentWindow, innerSelf);
+ assert_equals(frame.contentDocument, null);
+ // Cross-origin Document object obtained before it became cross-origin has no protections
+ assert_equals(innerDocument.URL, frame.src);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html
new file mode 100644
index 0000000000..a8af18d106
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Cross-origin methods and accessors are cached per Realm via[[CrossOriginPropertyDescriptorMap]]</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="cross-origin-objects-function-common.js"></script>
+<div id=log></div>
+<script>
+"use strict";
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ for (const {key} of crossOriginWindowMethods) {
+ assert_equals(w[key], w[key], `w.${key} via [[Get]]`);
+ const desc1 = Object.getOwnPropertyDescriptor(w, key);
+ const desc2 = Object.getOwnPropertyDescriptor(w, key);
+ assert_equals(desc1.value, desc2.value, `w.${key} via [[GetOwnProperty]]`);
+ }
+}, "Cross-origin Window methods are cached");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ for (const {key} of crossOriginWindowAccessors) {
+ const desc1 = Object.getOwnPropertyDescriptor(w, key);
+ const desc2 = Object.getOwnPropertyDescriptor(w, key);
+ assert_equals(desc1.get, desc2.get, `w.${key} getter`);
+ if (key === "location") {
+ assert_equals(desc1.set, desc2.set, `w.${key} setter`);
+ }
+ }
+}, "Cross-origin Window accessors are cached");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ assert_equals(w.location.replace, w.location.replace, "via [[Get]]");
+ const desc1 = Object.getOwnPropertyDescriptor(w.location, "replace");
+ const desc2 = Object.getOwnPropertyDescriptor(w.location, "replace");
+ assert_equals(desc1.value, desc2.value, "via [[GetOwnProperty]]");
+}, "Cross-origin Location `replace` method is cached");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ const desc1 = Object.getOwnPropertyDescriptor(w.location, "href");
+ const desc2 = Object.getOwnPropertyDescriptor(w.location, "href");
+ assert_equals(desc1.set, desc2.set);
+}, "Cross-origin Location `href` setter is cached");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-common.js b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-common.js
new file mode 100644
index 0000000000..3b93b498a2
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-common.js
@@ -0,0 +1,34 @@
+"use strict";
+
+const crossOriginWindowMethods = [
+ {key: "close", length: 0},
+ {key: "focus", length: 0},
+ {key: "blur", length: 0},
+ {key: "postMessage", length: 1},
+];
+
+const crossOriginWindowAccessors = [
+ "window",
+ "self",
+ "location",
+ "closed",
+ "frames",
+ "length",
+ "top",
+ "opener",
+ "parent",
+].map(key => ({key}));
+
+const makeCrossOriginWindow = t => {
+ const iframe = document.createElement("iframe");
+ const path = location.pathname.slice(0, location.pathname.lastIndexOf("/")) + "/frame.html";
+ iframe.src = get_host_info().HTTP_REMOTE_ORIGIN + path;
+
+ return new Promise((resolve, reject) => {
+ iframe.onload = () => { resolve(iframe.contentWindow); };
+ iframe.onerror = reject;
+
+ document.body.append(iframe);
+ t.add_cleanup(() => { iframe.remove(); });
+ });
+};
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-length.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-length.html
new file mode 100644
index 0000000000..466915a461
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-length.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Cross-origin methods and accessors are created with correct 'length' property</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="cross-origin-objects-function-common.js"></script>
+<div id=log></div>
+<script>
+"use strict";
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ for (const {key, length} of crossOriginWindowMethods) {
+ assert_equals(w[key].length, length, `w.${key} via [[Get]]`);
+ const desc = Object.getOwnPropertyDescriptor(w, key);
+ assert_equals(desc.value.length, length, `w.${key} via [[GetOwnProperty]]`);
+ }
+}, "Cross-origin Window methods have correct 'length'");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ for (const {key} of crossOriginWindowAccessors) {
+ const desc = Object.getOwnPropertyDescriptor(w, key);
+ assert_equals(desc.get.length, 0, `w.${key}`);
+ if (key === "location") {
+ assert_equals(desc.set.length, 1, `w.${key}`);
+ }
+ }
+}, "Cross-origin Window accessors have correct 'length'");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ assert_equals(w.location.replace.length, 1);
+ const desc = Object.getOwnPropertyDescriptor(w.location, "replace");
+ assert_equals(desc.value.length, 1);
+}, "Cross-origin Location `replace` method has correct 'length'");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ const desc = Object.getOwnPropertyDescriptor(w.location, "href");
+ assert_equals(desc.set.length, 1);
+}, "Cross-origin Location `href` setter has correct 'length'");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html
new file mode 100644
index 0000000000..167c30e8f3
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-function-name.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Cross-origin methods and accessors are created with correct 'name' property</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#crossorigingetownpropertyhelper-(-o,-p-)">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="cross-origin-objects-function-common.js"></script>
+<div id=log></div>
+<script>
+"use strict";
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ for (const {key} of crossOriginWindowMethods) {
+ assert_equals(w[key].name, key, `w.${key} via [[Get]]`);
+ const desc = Object.getOwnPropertyDescriptor(w, key);
+ assert_equals(desc.value.name, key, `w.${key} via [[GetOwnProperty]]`);
+ }
+}, "Cross-origin Window methods have correct 'name'");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ for (const {key} of crossOriginWindowAccessors) {
+ const desc = Object.getOwnPropertyDescriptor(w, key);
+ assert_equals(desc.get.name, `get ${key}`);
+ if (key === "location") {
+ assert_equals(desc.set.name, `set ${key}`);
+ }
+ }
+}, "Cross-origin Window accessors have correct 'name'");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ assert_equals(w.location.replace.name, "replace");
+ const desc = Object.getOwnPropertyDescriptor(w.location, "replace");
+ assert_equals(desc.value.name, "replace");
+}, "Cross-origin Location `replace` method has correct 'name'");
+
+promise_test(async t => {
+ const w = await makeCrossOriginWindow(t);
+ const desc = Object.getOwnPropertyDescriptor(w.location, "href");
+ assert_equals(desc.set.name, "set href");
+}, "Cross-origin Location `href` setter has correct 'name'");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-on-new-window.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-on-new-window.html
new file mode 100644
index 0000000000..3ad0de6a3a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects-on-new-window.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>Cross-origin behavior of Window and Location on new Window</title>
+<link rel="author" title="Bobby Holley (:bholley)" href="bobbyholley@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-window">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-location">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+setup({explicit_done: true});
+
+window.addEventListener('message', function onmessage(evt) {
+ window.removeEventListener('message', onmessage);
+ test(function() {
+ var results = evt.data;
+ assert_true(results.length > 0, 'Need results');
+ results.forEach(function(r) { assert_true(r.pass, r.message); });
+ }, "Cross-origin object identity preserved across document.domain");
+ win.close();
+ done();
+});
+var win = window.open('win-documentdomain.sub.html');
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
new file mode 100644
index 0000000000..d1b6cabc0f
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html
@@ -0,0 +1,718 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>Cross-origin behavior of Window and Location</title>
+<link rel="author" title="Bobby Holley (:bholley)" href="bobbyholley@gmail.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-window">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#security-location">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log></div>
+<iframe id="B"></iframe>
+<iframe id="C"></iframe>
+<iframe id="D"></iframe>
+<iframe id="E"></iframe>
+<iframe id="F"></iframe>
+<iframe id="G"></iframe>
+<iframe id="H"></iframe>
+<script>
+
+/*
+ * Setup boilerplate. This gives us a same-origin window "B", cross-origin
+ * windows "C" and "D", initially same-origin but then changing document.domain
+ * windows "E" and "F", and not-same-site (also cross-origin, of course) windows
+ * "G" and "H".
+ */
+var host_info = get_host_info();
+
+setup({explicit_done: true});
+path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html';
+pathWithThen = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame-with-then.html';
+var B = document.getElementById('B').contentWindow;
+var C = document.getElementById('C').contentWindow;
+var D = document.getElementById('D').contentWindow;
+var E = document.getElementById('E').contentWindow;
+var F = document.getElementById('F').contentWindow;
+var G = document.getElementById('G').contentWindow;
+var H = document.getElementById('H').contentWindow;
+B.frameElement.uriToLoad = path;
+C.frameElement.uriToLoad = get_host_info().HTTP_REMOTE_ORIGIN + path;
+D.frameElement.uriToLoad = get_host_info().HTTP_REMOTE_ORIGIN + pathWithThen;
+E.frameElement.uriToLoad = path + "?setdomain";
+F.frameElement.uriToLoad = pathWithThen + "?setdomain";
+G.frameElement.uriToLoad = get_host_info().HTTP_NOTSAMESITE_ORIGIN + path;
+H.frameElement.uriToLoad = get_host_info().HTTP_NOTSAMESITE_ORIGIN + pathWithThen;
+
+function winName(win) {
+ var iframes = document.getElementsByTagName('iframe');
+ iframes.find = Array.prototype.find;
+ var found = iframes.find(function (ifr) {
+ return ifr.contentWindow == win;
+ });
+ if (found) {
+ return found.id;
+ }
+ return "UNKNOWN";
+}
+
+function reloadSubframes(cb) {
+ var iframes = document.getElementsByTagName('iframe');
+ iframes.forEach = Array.prototype.forEach;
+ var count = 0;
+ function frameLoaded() {
+ this.onload = null;
+ if (++count == iframes.length)
+ cb();
+ }
+ iframes.forEach(function(ifr) { ifr.onload = frameLoaded; ifr.setAttribute('src', ifr.uriToLoad); });
+}
+function isObject(x) { return Object(x) === x; }
+
+/*
+ * Note: we eschew assert_equals in a lot of these tests, since the harness ends
+ * up throwing when it tries to format a message involving a cross-origin object.
+ */
+
+/*
+ * List of tests. Each test is actually a pair: an array of tests to run and a
+ * boolean for whether these are promise tests. We reload all the subframes in
+ * between running each toplevel test. This is done to avoid having to reload
+ * all the subframes for every single test, which is overkill: some of these
+ * tests are known to touch only one subframe. And doing it makes the test
+ * really slow.
+ */
+var testList = [];
+function addTest(func, desc) {
+ testList.push(
+ {tests: [
+ {func: func.bind(null, C),
+ desc: desc + " (cross-origin)"},
+ {func: func.bind(null, E),
+ desc: desc + " (same-origin + document.domain)"},
+ {func: func.bind(null, G),
+ desc: desc + " (cross-site)"}],
+ promiseTest: false});
+}
+
+function addPromiseTest(func, desc) {
+ testList.push(
+ {tests: [
+ {func: func.bind(null, C),
+ desc: desc + " (cross-origin)"},
+ {func: func.bind(null, E),
+ desc: desc + " (same-origin + document.domain)"},
+ {func: func.bind(null, G),
+ desc: desc + " (cross-site)"}],
+ promiseTest: true});
+}
+
+/**
+ * Similar helpers, but for the subframes that load frame-with-then.html
+ */
+function addThenTest(func, desc) {
+ testList.push(
+ {tests: [
+ {func: func.bind(null, D),
+ desc: desc + " (cross-origin)"},
+ {func: func.bind(null, F),
+ desc: desc + " (same-origin + document.domain)"},
+ {func: func.bind(null, H),
+ desc: desc + " (cross-site)"}],
+ promiseTest: false});
+}
+
+function addPromiseThenTest(func, desc) {
+ testList.push(
+ {tests: [
+ {func: func.bind(null, D),
+ desc: desc + " (cross-origin)"},
+ {func: func.bind(null, F),
+ desc: desc + " (same-origin + document.domain)"},
+ {func: func.bind(null, H),
+ desc: desc + " (cross-site)"}],
+ promiseTest: true});
+}
+
+/*
+ * Basic smoke tests for same-origin and cross-origin behaviors.
+ */
+
+addTest(function(win) {
+ // Note: we do not check location.host as its default port semantics are hard to reflect statically
+ assert_equals(location.hostname, host_info.ORIGINAL_HOST, 'Need to run the top-level test from domain ' + host_info.ORIGINAL_HOST);
+ assert_equals(get_port(location), host_info.HTTP_PORT, 'Need to run the top-level test from port ' + host_info.HTTP_PORT);
+ assert_equals(B.parent, window, "window.parent works same-origin");
+ assert_equals(win.parent, window, "window.parent works cross-origin");
+ assert_equals(B.location.pathname, path, "location.href works same-origin");
+ assert_throws_dom("SecurityError", function() { win.location.pathname; }, "location.pathname throws cross-origin");
+ assert_equals(B.frames, 'override', "Overrides visible in the same-origin case");
+ assert_equals(win.frames, win, "Overrides invisible in the cross-origin case");
+ assert_equals(B.focus, 'override', "Overrides visible in the same-origin case");
+ checkFunction(win.focus, Function.prototype);
+ assert_not_equals(win.focus, focus, "Overrides invisible in the cross-origin case");
+}, "Basic sanity-checking");
+
+/*
+ * Tests regarding which properties are allowed cross-origin.
+ *
+ * Also tests for [[GetOwnProperty]] and [[HasOwnProperty]] behavior.
+ */
+
+var allowedSymbols = [Symbol.toStringTag, Symbol.hasInstance,
+ Symbol.isConcatSpreadable];
+var windowAllowlists = {
+ namedFrames: ['donotleakme'],
+ indices: ['0', '1'],
+ getters: ['location', 'window', 'frames', 'self', 'top', 'parent',
+ 'opener', 'closed', 'length'],
+ setters: ['location'],
+ methods: ['postMessage', 'close', 'blur', 'focus'],
+ // These are methods which return promises and, therefore, when called with a
+ // cross-origin `this` object, do not throw immediately, but instead return a
+ // Promise which rejects with the same SecurityError that they would
+ // otherwise throw. They are not, however, cross-origin accessible.
+ promiseMethods: ['createImageBitmap', 'fetch'],
+}
+windowAllowlists.propNames = Array.from(new Set([...windowAllowlists.indices,
+ ...windowAllowlists.getters,
+ ...windowAllowlists.setters,
+ ...windowAllowlists.methods,
+ 'then'])).sort();
+windowAllowlists.props = windowAllowlists.propNames.concat(allowedSymbols);
+
+var locationAllowlists = {
+ getters: [],
+ setters: ['href'],
+ methods: ['replace'],
+ promiseMethods: [],
+}
+locationAllowlists.propNames = Array.from(new Set([...locationAllowlists.getters,
+ ...locationAllowlists.setters,
+ ...locationAllowlists.methods,
+ 'then'])).sort();
+
+// Define various sets of arguments to call cross-origin methods with. Arguments
+// for any cross-origin-callable method must be valid, and should aim to have no
+// side-effects. Any method without an entry in this list will be called with
+// an empty arguments list.
+var methodArgs = new Map(Object.entries({
+ // As a basic smoke test, we call one cross-origin-inaccessible method with
+ // both valid and invalid arguments to make sure that it rejects with the
+ // same SecurityError regardless.
+ assign: [
+ [],
+ ["javascript:undefined"],
+ ],
+ // Note: If we post a message to frame.html with a matching origin, its
+ // "onmessage" handler will change its `document.domain`, and potentially
+ // invalidate subsequent tests, so be sure to only pass non-matching origins.
+ postMessage: [
+ ["foo", "http://does-not.exist/"],
+ ["foo", {}],
+ ],
+ replace: [["javascript:undefined"]],
+}));
+
+addTest(function(win) {
+ for (var prop in window) {
+ if (windowAllowlists.props.indexOf(prop) != -1) {
+ win[prop]; // Shouldn't throw.
+ Object.getOwnPropertyDescriptor(win, prop); // Shouldn't throw.
+ assert_true(Object.prototype.hasOwnProperty.call(win, prop), "hasOwnProperty for " + String(prop));
+ } else {
+ assert_throws_dom("SecurityError", function() { win[prop]; }, "Should throw when accessing " + String(prop) + " on Window");
+ assert_throws_dom("SecurityError", function() { Object.getOwnPropertyDescriptor(win, prop); },
+ "Should throw when accessing property descriptor for " + prop + " on Window");
+ assert_throws_dom("SecurityError", function() { Object.prototype.hasOwnProperty.call(win, prop); },
+ "Should throw when invoking hasOwnProperty for " + prop + " on Window");
+ }
+ if (prop != 'location')
+ assert_throws_dom("SecurityError", function() { win[prop] = undefined; }, "Should throw when writing to " + prop + " on Window");
+ }
+ for (var prop of windowAllowlists.namedFrames) {
+ win[prop]; // Shouldn't throw.
+ var desc = Object.getOwnPropertyDescriptor(win, prop);
+ assert_false(desc.writable, "[[Writable]] for named frame " + String(prop));
+ assert_false(desc.enumerable, "[[Enumerable]] for named frame " + String(prop));
+ assert_true(desc.configurable, "[[Configurable]] for named frame " + String(prop));
+ assert_true(Object.prototype.hasOwnProperty.call(win, prop), "hasOwnProperty for " + String(prop));
+ }
+ for (var prop in location) {
+ if (prop == 'replace') {
+ win.location[prop]; // Shouldn't throw.
+ Object.getOwnPropertyDescriptor(win.location, prop); // Shouldn't throw.
+ assert_true(Object.prototype.hasOwnProperty.call(win.location, prop), "hasOwnProperty for " + prop);
+ assert_throws_dom("SecurityError", function() { win.location[prop] = undefined; }, "Should throw when writing to " + prop + " on Location");
+ }
+ else if (prop == 'href') {
+ Object.getOwnPropertyDescriptor(win.location, prop); // Shouldn't throw.
+ assert_true(Object.prototype.hasOwnProperty.call(win.location, prop), "hasOwnProperty for " + prop);
+ assert_throws_dom("SecurityError", function() { win.location[prop] },
+ "Should throw reading href on Location");
+ }
+ else {
+ assert_throws_dom("SecurityError", function() { win.location[prop]; }, "Should throw when accessing " + prop + " on Location");
+ assert_throws_dom("SecurityError", function() { Object.getOwnPropertyDescriptor(win.location, prop); },
+ "Should throw when accessing property descriptor for " + prop + " on Location");
+ assert_throws_dom("SecurityError", function() { Object.prototype.hasOwnProperty.call(win.location, prop); },
+ "Should throw when invoking hasOwnProperty for " + prop + " on Location");
+ assert_throws_dom("SecurityError", function() { win.location[prop] = undefined; }, "Should throw when writing to " + prop + " on Location");
+ }
+ }
+}, "Only certain properties are accessible cross-origin");
+
+addPromiseTest(async function(win, test_obj) {
+ async function checkProperties(objName, allowedlists) {
+ var localObj = window[objName];
+ var otherObj = win[objName];
+
+ for (var prop in localObj) {
+ let desc;
+ for (let obj = localObj; !desc; obj = Object.getPrototypeOf(obj)) {
+ desc = Object.getOwnPropertyDescriptor(obj, prop);
+
+ }
+
+ if ("value" in desc) {
+ if (typeof desc.value === "function" && String(desc.value).includes("[native code]")) {
+ if (allowedlists.promiseMethods.includes(prop)) {
+ await promise_rejects_dom(test_obj, "SecurityError", desc.value.call(otherObj),
+ `Should throw when calling ${objName}.${prop} with cross-origin this object`);
+ } else if (!allowedlists.methods.includes(prop)) {
+ for (let args of methodArgs.get(prop) || [[]]) {
+ assert_throws_dom("SecurityError", desc.value.bind(otherObj, ...args),
+ `Should throw when calling ${objName}.${prop} with cross-origin this object`);
+ }
+
+ } else {
+ for (let args of methodArgs.get(prop) || [[]]) {
+ desc.value.apply(otherObj, args); // Shouldn't throw.
+ }
+ }
+ }
+ } else {
+ if (desc.get) {
+ if (allowedlists.getters.includes(prop)) {
+ desc.get.call(otherObj); // Shouldn't throw.
+ } else {
+ assert_throws_dom("SecurityError", desc.get.bind(otherObj),
+ `Should throw when calling ${objName}.${prop} getter with cross-origin this object`);
+ }
+ }
+ if (desc.set) {
+ if (allowedlists.setters.includes(prop)) {
+ desc.set.call(otherObj, "javascript:undefined"); // Shouldn't throw.
+ } else {
+ assert_throws_dom("SecurityError", desc.set.bind(otherObj, "foo"),
+ `Should throw when calling ${objName}.${prop} setter with cross-origin this object`);
+ }
+ }
+ }
+ }
+ }
+
+ await checkProperties("location", locationAllowlists);
+ await checkProperties("window", windowAllowlists);
+}, "Only certain properties are usable as cross-origin this objects");
+
+/*
+ * ES Internal Methods.
+ */
+
+/*
+ * [[GetPrototypeOf]]
+ */
+addTest(function(win) {
+ assert_equals(Object.getPrototypeOf(win), null, "cross-origin Window proto is null");
+ assert_equals(Object.getPrototypeOf(win.location), null, "cross-origin Location proto is null (__proto__)");
+ var protoGetter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').get;
+ assert_equals(protoGetter.call(win), null, "cross-origin Window proto is null");
+ assert_equals(protoGetter.call(win.location), null, "cross-origin Location proto is null (__proto__)");
+ assert_throws_dom("SecurityError", function() { win.__proto__; }, "__proto__ property not available cross-origin");
+ assert_throws_dom("SecurityError", function() { win.location.__proto__; }, "__proto__ property not available cross-origin");
+
+}, "[[GetPrototypeOf]] should return null");
+
+/*
+ * [[SetPrototypeOf]]
+ */
+addTest(function(win) {
+ assert_throws_dom("SecurityError", function() { win.__proto__ = new Object(); }, "proto set on cross-origin Window");
+ assert_throws_dom("SecurityError", function() { win.location.__proto__ = new Object(); }, "proto set on cross-origin Location");
+ var setters = [Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set];
+ if (Object.setPrototypeOf)
+ setters.push(function(p) { Object.setPrototypeOf(this, p); });
+ setters.forEach(function(protoSetter) {
+ assert_throws_js(TypeError, function() { protoSetter.call(win, new Object()); }, "proto setter |call| on cross-origin Window");
+ assert_throws_js(TypeError, function() { protoSetter.call(win.location, new Object()); }, "proto setter |call| on cross-origin Location");
+ });
+ // Hack to avoid "duplicate test name" harness issues.
+ setters.forEach(function(protoSetter) {
+ test(function() { protoSetter.call(win, null); },
+ "proto setter |call| on cross-origin Window with null (" + protoSetter + ", " + winName(win) + ")");
+ test(function() { protoSetter.call(win.location, null); },
+ "proto setter |call| on cross-origin Location with null (" + protoSetter + ", " + winName(win) + ")");
+ });
+ if (Reflect.setPrototypeOf) {
+ assert_false(Reflect.setPrototypeOf(win, new Object()),
+ "Reflect.setPrototypeOf on cross-origin Window");
+ assert_true(Reflect.setPrototypeOf(win, null),
+ "Reflect.setPrototypeOf on cross-origin Window with null");
+ assert_false(Reflect.setPrototypeOf(win.location, new Object()),
+ "Reflect.setPrototypeOf on cross-origin Location");
+ assert_true(Reflect.setPrototypeOf(win.location, null),
+ "Reflect.setPrototypeOf on cross-origin Location with null");
+ }
+}, "[[SetPrototypeOf]] should return false");
+
+/*
+ * [[IsExtensible]]
+ */
+addTest(function(win) {
+ assert_true(Object.isExtensible(win), "cross-origin Window should be extensible");
+ assert_true(Object.isExtensible(win.location), "cross-origin Location should be extensible");
+}, "[[IsExtensible]] should return true for cross-origin objects");
+
+/*
+ * [[PreventExtensions]]
+ */
+addTest(function(win) {
+ assert_throws_js(TypeError, function() { Object.preventExtensions(win) },
+ "preventExtensions on cross-origin Window should throw");
+ assert_throws_js(TypeError, function() { Object.preventExtensions(win.location) },
+ "preventExtensions on cross-origin Location should throw");
+ assert_false(Reflect.preventExtensions(win),
+ "Reflect.preventExtensions on cross-origin Window");
+ assert_false(Reflect.preventExtensions(win.location),
+ "Reflect.preventExtensions on cross-origin Location");
+}, "[[PreventExtensions]] should return false cross-origin objects");
+
+/*
+ * [[GetOwnProperty]]
+ */
+
+addTest(function(win) {
+ assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'close')), "win.close is |own|");
+ assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'top')), "win.top is |own|");
+ assert_true(isObject(Object.getOwnPropertyDescriptor(win.location, 'href')), "win.location.href is |own|");
+ assert_true(isObject(Object.getOwnPropertyDescriptor(win.location, 'replace')), "win.location.replace is |own|");
+}, "[[GetOwnProperty]] - Properties on cross-origin objects should be reported |own|");
+
+function checkPropertyDescriptor(desc, propName, expectWritable) {
+ const isSymbol = typeof(propName) === "symbol";
+ const isArrayIndexPropertyName = !isSymbol && !isNaN(parseInt(propName, 10));
+ propName = String(propName);
+ assert_true(isObject(desc), "property descriptor for " + propName + " should exist");
+ assert_equals(desc.configurable, true, "property descriptor for " + propName + " should be configurable");
+ if (!isArrayIndexPropertyName) {
+ assert_equals(desc.enumerable, false, "property descriptor for " + propName + " should not be enumerable");
+ if (isSymbol || propName == "then") {
+ assert_true("value" in desc,
+ "property descriptor for " + propName + " should be a value descriptor");
+ assert_equals(desc.value, undefined,
+ "symbol-named cross-origin visible prop " + propName +
+ " should come back as undefined");
+ }
+ } else {
+ assert_equals(desc.enumerable, true, "property descriptor for " + propName + " should be enumerable");
+ }
+ if ('value' in desc)
+ assert_equals(desc.writable, expectWritable, "property descriptor for " + propName + " should have writable: " + expectWritable);
+ else
+ assert_equals(typeof desc.set != 'undefined', expectWritable,
+ "property descriptor for " + propName + " should " + (expectWritable ? "" : "not ") + "have setter");
+}
+
+addTest(function(win) {
+ windowAllowlists.props.forEach(function(prop) {
+ var desc = Object.getOwnPropertyDescriptor(win, prop);
+ checkPropertyDescriptor(desc, prop, prop == 'location');
+ });
+ checkPropertyDescriptor(Object.getOwnPropertyDescriptor(win.location, 'replace'), 'replace', false);
+ checkPropertyDescriptor(Object.getOwnPropertyDescriptor(win.location, 'href'), 'href', true);
+ assert_equals(typeof Object.getOwnPropertyDescriptor(win.location, 'href').get, 'undefined', "Cross-origin location should have no href getter");
+ allowedSymbols.forEach(function(prop) {
+ var desc = Object.getOwnPropertyDescriptor(win.location, prop);
+ checkPropertyDescriptor(desc, prop, false);
+ });
+}, "[[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly");
+
+addThenTest(function(win) {
+ assert_equals(typeof win.then, "object");
+}, "[[GetOwnProperty]] - Subframe named 'then' should shadow the default 'then' value");
+
+addThenTest(function(win) {
+ assert_equals(typeof win.close, "function");
+ assert_equals(typeof win.open, "object");
+}, "[[GetOwnProperty]] - Subframes should be visible cross-origin only if their names don't match the names of cross-origin-exposed IDL properties");
+
+addTest(function(win) {
+ assert_equals(typeof Object.getOwnPropertyDescriptor(win, '0').value, "object");
+ assert_equals(typeof Object.getOwnPropertyDescriptor(win, '1').value, "object");
+ assert_throws_dom("SecurityError", function() {
+ Object.getOwnPropertyDescriptor(win, '2');
+ });
+}, "[[GetOwnProperty]] - Should be able to get a property descriptor for an indexed property only if it corresponds to a child window.");
+
+/*
+ * [[Delete]]
+ */
+addTest(function(win) {
+ assert_throws_dom("SecurityError", function() { delete win[0]; }, "Can't delete cross-origin indexed property");
+ assert_throws_dom("SecurityError", function() { delete win[100]; }, "Can't delete cross-origin indexed property");
+ assert_throws_dom("SecurityError", function() { delete win.location; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.parent; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.length; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.document; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.foopy; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.location.href; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.location.replace; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.location.port; }, "Can't delete cross-origin property");
+ assert_throws_dom("SecurityError", function() { delete win.location.foopy; }, "Can't delete cross-origin property");
+}, "[[Delete]] Should throw on cross-origin objects");
+
+/*
+ * [[DefineOwnProperty]]
+ */
+function checkDefine(obj, prop) {
+ var valueDesc = { configurable: true, enumerable: false, writable: false, value: 2 };
+ var accessorDesc = { configurable: true, enumerable: false, get: function() {} };
+ assert_throws_dom("SecurityError", function() { Object.defineProperty(obj, prop, valueDesc); }, "Can't define cross-origin value property " + prop);
+ assert_throws_dom("SecurityError", function() { Object.defineProperty(obj, prop, accessorDesc); }, "Can't define cross-origin accessor property " + prop);
+}
+addTest(function(win) {
+ checkDefine(win, 'length');
+ checkDefine(win, 'parent');
+ checkDefine(win, 'location');
+ checkDefine(win, 'document');
+ checkDefine(win, 'foopy');
+ checkDefine(win.location, 'href');
+ checkDefine(win.location, 'replace');
+ checkDefine(win.location, 'port');
+ checkDefine(win.location, 'foopy');
+}, "[[DefineOwnProperty]] Should throw for cross-origin objects");
+
+/*
+ * EnumerateObjectProperties (backed by [[OwnPropertyKeys]])
+ */
+
+addTest(function(win) {
+ let i = 0;
+ for (var prop in win) {
+ i++;
+ assert_true(windowAllowlists.indices.includes(prop), prop + " is not safelisted for a cross-origin Window");
+ }
+ assert_equals(i, windowAllowlists.indices.length, "Enumerate all enumerable safelisted cross-origin Window properties");
+ i = 0;
+ for (var prop in win.location) {
+ i++;
+ }
+ assert_equals(i, 0, "There's nothing to enumerate for cross-origin Location properties");
+}, "Can only enumerate safelisted enumerable properties");
+
+/*
+ * [[OwnPropertyKeys]]
+ */
+
+addTest(function(win) {
+ assert_array_equals(Object.getOwnPropertyNames(win).sort(),
+ windowAllowlists.propNames,
+ "Object.getOwnPropertyNames() gives the right answer for cross-origin Window");
+ assert_array_equals(Object.keys(win).sort(),
+ windowAllowlists.indices,
+ "Object.keys() gives the right answer for cross-origin Window");
+ assert_array_equals(Object.getOwnPropertyNames(win.location).sort(),
+ locationAllowlists.propNames,
+ "Object.getOwnPropertyNames() gives the right answer for cross-origin Location");
+ assert_equals(Object.keys(win.location).length, 0,
+ "Object.keys() gives the right answer for cross-origin Location");
+}, "[[OwnPropertyKeys]] should return all properties from cross-origin objects");
+
+addTest(function(win) {
+ assert_array_equals(Object.getOwnPropertySymbols(win), allowedSymbols,
+ "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Window");
+ assert_array_equals(Object.getOwnPropertySymbols(win.location),
+ allowedSymbols,
+ "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Location");
+}, "[[OwnPropertyKeys]] should return the right symbol-named properties for cross-origin objects");
+
+addTest(function(win) {
+ var allWindowProps = Reflect.ownKeys(win);
+ indexedWindowProps = allWindowProps.slice(0, windowAllowlists.indices.length);
+ stringWindowProps = allWindowProps.slice(0, -1 * allowedSymbols.length);
+ symbolWindowProps = allWindowProps.slice(-1 * allowedSymbols.length);
+ // stringWindowProps should have "then" last in this case. Do this
+ // check before we call stringWindowProps.sort() below.
+ assert_equals(stringWindowProps[stringWindowProps.length - 1], "then",
+ "'then' property should be added to the end of the string list if not there");
+ assert_array_equals(indexedWindowProps, windowAllowlists.indices,
+ "Reflect.ownKeys should start with the indices exposed on the cross-origin window.");
+ assert_array_equals(stringWindowProps.sort(), windowAllowlists.propNames,
+ "Reflect.ownKeys should continue with the cross-origin window properties for a cross-origin Window.");
+ assert_array_equals(symbolWindowProps, allowedSymbols,
+ "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Window.");
+
+ var allLocationProps = Reflect.ownKeys(win.location);
+ stringLocationProps = allLocationProps.slice(0, -1 * allowedSymbols.length);
+ symbolLocationProps = allLocationProps.slice(-1 * allowedSymbols.length);
+ assert_array_equals(stringLocationProps.sort(), locationAllowlists.propNames,
+ "Reflect.ownKeys should start with the cross-origin window properties for a cross-origin Location.")
+ assert_array_equals(symbolLocationProps, allowedSymbols,
+ "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Location.")
+}, "[[OwnPropertyKeys]] should place the symbols after the property names after the subframe indices");
+
+addThenTest(function(win) {
+ var stringProps = Object.getOwnPropertyNames(win);
+ // Named frames are not exposed via [[OwnPropertyKeys]].
+ assert_equals(stringProps.indexOf("a"), -1);
+ assert_equals(stringProps.indexOf("b"), -1);
+ assert_equals(typeof win.a, "object");
+ assert_equals(typeof win.b, "object");
+ assert_equals(stringProps[stringProps.length - 1], "then");
+ assert_equals(stringProps.indexOf("then"), stringProps.lastIndexOf("then"));
+}, "[[OwnPropertyKeys]] should not reorder where 'then' appears if it's a named subframe, nor add another copy of 'then'");
+
+addTest(function(win) {
+ assert_equals(B.eval('parent.' + winName(win)), win, "A and B observe the same identity for C's Window");
+ assert_equals(B.eval('parent.' + winName(win) + '.location'), win.location, "A and B observe the same identity for C's Location");
+}, "A and B jointly observe the same identity for cross-origin Window and Location");
+
+function checkFunction(f, proto) {
+ var name = f.name || '<missing name>';
+ assert_equals(typeof f, 'function', name + " is a function");
+ assert_equals(Object.getPrototypeOf(f), proto, f.name + " has the right prototype");
+}
+
+addTest(function(win) {
+ checkFunction(win.close, Function.prototype);
+ checkFunction(win.location.replace, Function.prototype);
+}, "Cross-origin functions get local Function.prototype");
+
+addTest(function(win) {
+ assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'parent')),
+ "Need to be able to use Object.getOwnPropertyDescriptor do this test");
+ checkFunction(Object.getOwnPropertyDescriptor(win, 'parent').get, Function.prototype);
+ checkFunction(Object.getOwnPropertyDescriptor(win.location, 'href').set, Function.prototype);
+}, "Cross-origin Window accessors get local Function.prototype");
+
+addTest(function(win) {
+ checkFunction(close, Function.prototype);
+ assert_not_equals(close, B.close, 'same-origin Window functions get their own object');
+ assert_not_equals(close, win.close, 'cross-origin Window functions get their own object');
+ var close_B = B.eval('parent.' + winName(win) + '.close');
+ assert_not_equals(close, close_B, 'close_B is unique when viewed by the parent');
+ assert_not_equals(close_B, win.close, 'different Window functions per-incumbent script settings object');
+ checkFunction(close_B, B.Function.prototype);
+
+ checkFunction(location.replace, Function.prototype);
+ assert_not_equals(location.replace, win.location.replace, "cross-origin Location functions get their own object");
+ var replace_B = B.eval('parent.' + winName(win) + '.location.replace');
+ assert_not_equals(replace_B, win.location.replace, 'different Location functions per-incumbent script settings object');
+ checkFunction(replace_B, B.Function.prototype);
+}, "Same-origin observers get different functions for cross-origin objects");
+
+addTest(function(win) {
+ assert_true(isObject(Object.getOwnPropertyDescriptor(win, 'parent')),
+ "Need to be able to use Object.getOwnPropertyDescriptor do this test");
+ var get_self_parent = Object.getOwnPropertyDescriptor(window, 'parent').get;
+ var get_parent_A = Object.getOwnPropertyDescriptor(win, 'parent').get;
+ var get_parent_B = B.eval('Object.getOwnPropertyDescriptor(parent.' + winName(win) + ', "parent").get');
+ assert_not_equals(get_self_parent, get_parent_A, 'different Window accessors per-incumbent script settings object');
+ assert_not_equals(get_parent_A, get_parent_B, 'different Window accessors per-incumbent script settings object');
+ checkFunction(get_self_parent, Function.prototype);
+ checkFunction(get_parent_A, Function.prototype);
+ checkFunction(get_parent_B, B.Function.prototype);
+}, "Same-origin observers get different accessors for cross-origin Window");
+
+addTest(function(win) {
+ var set_self_href = Object.getOwnPropertyDescriptor(window.location, 'href').set;
+ var set_href_A = Object.getOwnPropertyDescriptor(win.location, 'href').set;
+ var set_href_B = B.eval('Object.getOwnPropertyDescriptor(parent.' + winName(win) + '.location, "href").set');
+ assert_not_equals(set_self_href, set_href_A, 'different Location accessors per-incumbent script settings object');
+ assert_not_equals(set_href_A, set_href_B, 'different Location accessors per-incumbent script settings object');
+ checkFunction(set_self_href, Function.prototype);
+ checkFunction(set_href_A, Function.prototype);
+ checkFunction(set_href_B, B.Function.prototype);
+}, "Same-origin observers get different accessors for cross-origin Location");
+
+addTest(function(win) {
+ assert_equals({}.toString.call(win), "[object Object]");
+ assert_equals({}.toString.call(win.location), "[object Object]");
+}, "{}.toString.call() does the right thing on cross-origin objects");
+
+addPromiseTest(function(win) {
+ return Promise.resolve(win).then((arg) => {
+ assert_equals(arg, win);
+ });
+}, "Resolving a promise with a cross-origin window without a 'then' subframe should work");
+
+addPromiseThenTest(function(win) {
+ return Promise.resolve(win).then((arg) => {
+ assert_equals(arg, win);
+ });
+}, "Resolving a promise with a cross-origin window with a 'then' subframe should work");
+
+addPromiseThenTest(function(win) {
+ return Promise.resolve(win.location).then((arg) => {
+ assert_equals(arg, win.location);
+ });
+}, "Resolving a promise with a cross-origin location should work");
+
+addTest(function(win) {
+ var desc = Object.getOwnPropertyDescriptor(window, "onmouseenter");
+ var f = () => {};
+
+ // Check that it has [LegacyLenientThis] behavior
+ assert_equals(desc.get.call({}), undefined, "getter should return undefined");
+ desc.set.call({}, f); // Should not throw.
+
+ // Check that we can apply it to a same-origin window.
+ assert_equals(desc.get.call(B), null, "Should be able to read the value");
+ desc.set.call(B, f);
+ assert_equals(desc.get.call(B), f, "Value should have updated");
+ // And reset it for our next test
+ desc.set.call(B, null);
+ assert_equals(desc.get.call(B), null, "Should have been reset");
+
+ // Check that applying it to a cross-origin window throws instead of doing
+ // the [LegacyLenientThis] behavior.
+ assert_throws_dom("SecurityError", () => {
+ desc.get.call(win);
+ }, "Should throw when getting cross-origin");
+ assert_throws_dom("SecurityError", () => {
+ desc.set.call(win, f);
+ }, "Should throw when setting cross-origin");
+}, "LegacyLenientThis behavior");
+
+// We do a fresh load of the subframes for each test to minimize side-effects.
+// It would be nice to reload ourselves as well, but we can't do that without
+// disrupting the test harness.
+function testDone() {
+ if (testList.length != 0) {
+ reloadSubframes(runNextTest);
+ } else {
+ done();
+ }
+}
+
+async function runNextTest() {
+ var entry = testList.shift();
+ if (entry.promiseTest) {
+ for (let t of entry.tests) {
+ await new Promise(resolve => {
+ promise_test(test_obj => {
+ return new Promise(res => res(t.func(test_obj))).finally(resolve);
+ }, t.desc);
+ });
+ }
+ } else {
+ for (let t of entry.tests) {
+ test(t.func, t.desc);
+ }
+ }
+ testDone();
+}
+reloadSubframes(runNextTest);
+
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame-with-then.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame-with-then.html
new file mode 100644
index 0000000000..3eedeca38a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame-with-then.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <script>
+ if (location.search == "?setdomain") {
+ document.domain = document.domain;
+ }
+ </script>
+ <body>
+ <!--- Some frames to test ordering -->
+ <iframe name="a"></iframe>
+ <!-- A subframe to test "then" behavior -->
+ <iframe name="then"></iframe>
+ <iframe name="b"></iframe>
+ <!-- Two subframes with names corresponding to IDL-defined properties; one
+ a cross-origin-exposed property and one not exposed cross-origin -->
+ <iframe name="close"></iframe>
+ <iframe name="open"></iframe>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame.html
new file mode 100644
index 0000000000..ca2dd8ebf8
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/frame.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+<script>
+ if (location.search == "?setdomain") {
+ document.domain = document.domain;
+ }
+
+ // Override the |frames| and |focus| property to test that such overrides are
+ // properly ignored cross-origin.
+ window.frames = "override";
+ window.focus = "override";
+
+ // Also add a |then| property to test that it doesn't get exposed.
+ window.then = "something";
+ window.location.then = "something-else";
+
+ // If we get a postMessage, we grab references to everything and set
+ // document.domain to trim off our topmost subdomain.
+ window.onmessage = function(evt) {
+ window.windowReferences = [];
+ window.locationReferences = [];
+ for (var i = 0; i < parent.length; ++i) {
+ windowReferences.push(parent[i]);
+ locationReferences.push(parent[i].location);
+ }
+ try {
+ document.domain = document.domain.substring(document.domain.indexOf('.') + 1);
+ evt.source.postMessage('PASS', '*');
+ } catch (e) {
+ evt.source.postMessage('FAIL: cannot trim off document.domain: ' + e, '*');
+ }
+ }
+
+ function checkWindowReferences() {
+ for (var i = 0; i < parent.length; ++i) {
+ if (windowReferences[i] != parent[i])
+ throw new Error("Window references don't match for " + i + " after document.domain");
+ if (locationReferences[i] != parent[i].location)
+ throw new Error("Location references don't match for " + i + " after document.domain");
+ }
+ return true;
+ }
+</script>
+</head>
+<body>
+ <!-- Two subframes to give us some indexed properties -->
+ <iframe></iframe>
+ <iframe name=donotleakme></iframe><!-- "donotleakme" is excluded as cross-origin named property due to [[HideFromKeys]] -->
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/location-properties-smoke-test.window.js b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/location-properties-smoke-test.window.js
new file mode 100644
index 0000000000..45b61d07f5
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/location-properties-smoke-test.window.js
@@ -0,0 +1,51 @@
+// META: variant=?assign
+// META: variant=?customproperty
+// META: variant=?hash
+// META: variant=?host
+// META: variant=?hostname
+// META: variant=?pathname
+// META: variant=?port
+// META: variant=?protocol
+// META: variant=?reload
+// META: variant=?search
+// META: variant=?toString
+// META: variant=?valueOf
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+
+const property = window.location.search.substr(1);
+
+promise_test(async t => {
+ const iframeContext = new RemoteContext(token());
+ const iframe = document.createElement("iframe");
+ iframe.src = get_host_info().REMOTE_ORIGIN +
+ "/common/dispatcher/remote-executor.html?uuid=" + iframeContext.context_id;
+ document.body.appendChild(iframe);
+
+ // Wait for the cross-origin document to be loaded inside the iframe.
+ assert_equals(
+ await iframeContext.execute_script(() => "Document loaded") ,
+ "Document loaded"
+ );
+
+ assert_throws_dom("SecurityError", () => {
+ const unused = iframe.contentWindow.location[property];
+ }, "Cross origin get of a location property should throw a security error");
+
+ assert_throws_dom("SecurityError", () => {
+ iframe.contentWindow.location[property] = "Random string";
+ }, "Cross origin set of a location property should throw a security error");
+
+ // Verify that the property was indeed not modified.
+ assert_not_equals(
+ await iframeContext.execute_script(property => location[property],
+ [property]),
+ "Random string",
+ );
+
+ assert_throws_dom("SecurityError", () => {
+ const unused = Object.getOwnPropertyDescriptor(
+ iframe.contentWindow.location, property);
+ }, "Cross origin get of descriptors should throw a security error");
+}, `Verifying that cross-origin access of '${property}' is restricted`);
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/resources/cross-origin-due-to-document-domain-only-helper.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/resources/cross-origin-due-to-document-domain-only-helper.html
new file mode 100644
index 0000000000..10ac8ece0e
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/resources/cross-origin-due-to-document-domain-only-helper.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<script>
+self.expandosForever = true
+self.location.expandosForever = true
+function setDocumentDomain() {
+ document.domain = document.domain
+}
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html
new file mode 100644
index 0000000000..a315e21208
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/win-documentdomain.sub.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script>
+ function loadFrames() {
+ window.A = document.getElementById('A').contentWindow;
+ window.B = document.getElementById('B').contentWindow;
+ window.C = document.getElementById('C').contentWindow;
+ window.D = document.getElementById('D').contentWindow;
+
+ var path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html';
+ A.location = 'frame.html';
+ B.location = '//{{domains[www2]}}:' + get_port(location) + path;
+ C.location = '//{{domains[www2]}}:' + get_port(location) + path;
+ D.location = '//{{domains[www1]}}:' + get_port(location) + path;
+
+ var loadCount = 0;
+ function frameLoaded() {
+ if (++loadCount == 4)
+ go();
+ }
+ var iframes = document.getElementsByTagName('iframe');
+ for (var i = 0; i < iframes.length; i++) {
+ iframes[i].onload = frameLoaded;
+ }
+ }
+
+ var results = [];
+ function assert(cond, msg) {
+ results.push({pass: !!cond, message: msg});
+ }
+
+ function go() {
+ window.onmessage = function(evt) {
+ try {
+ assert(evt.data == "PASS", "frame.html processing should be PASS but got " + evt.data);
+ assert(B.checkWindowReferences(), "B's Window references are still self-consistent after document.domain");
+ for (var i = 0; i < window.length; ++i) {
+ assert(window[i] === B.windowReferences[i],
+ "Window reference " + i + " consistent between globals after document.domain");
+ assert(window[i].location === B.locationReferences[i],
+ "Location reference " + i + " consistent between globals after document.domain");
+ }
+ } catch(e) {
+ assert(false, "Should not receive exception: " + e);
+ }
+ opener.postMessage(results, '*');
+ };
+ A.document.domain = A.document.domain;
+ document.domain = document.domain;
+ B.postMessage('', '*');
+ }
+
+ </script>
+</head>
+<body onload="loadFrames()">
+ <iframe id="A"></iframe>
+ <iframe id="B"></iframe>
+ <iframe id="C"></iframe>
+ <iframe id="D"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html
new file mode 100644
index 0000000000..f03550a141
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Cross-realm [[Set]] to window.location and location.href throws an error of correct realm</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#window">
+<link rel="help" href="https://webidl.spec.whatwg.org/#Unforgeable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+<script>
+const URL_SAME_ORIGIN = get_host_info().ORIGINAL_HOST;
+const URL_CROSS_ORIGIN = get_host_info().HTTP_REMOTE_ORIGIN;
+const URL_VALID = "#foo";
+const URL_INVALID = "http://#";
+
+const { get: locationGet, set: locationSet } = Object.getOwnPropertyDescriptor(window, "location");
+const { get: hrefGet, set: hrefSet } = Object.getOwnPropertyDescriptor(location, "href");
+
+
+promise_test(async t => {
+ const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN);
+ assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow).location; });
+ assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.get(sameOriginWindow, "location", {}); });
+ assert_throws_js(TypeError, () => { locationGet.call({}); });
+}, "Same-origin window.location getter throws TypeError in holder's realm on invalid |this| value");
+
+promise_test(async t => {
+ const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN);
+ assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow.location).href; });
+ assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.get(sameOriginWindow.location, "href", {}); });
+ assert_throws_js(TypeError, () => { hrefGet(); });
+}, "Same-origin location.href getter throws TypeError in holder's realm on invalid |this| value");
+
+promise_test(async t => {
+ const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN);
+ assert_throws_dom("SECURITY_ERR", () => { crossOriginWindow.location.href; });
+ assert_throws_dom("SECURITY_ERR", () => { hrefGet.call(crossOriginWindow.location); });
+ assert_equals(Object.getOwnPropertyDescriptor(crossOriginWindow.location, "href").get, undefined);
+}, "Cross-origin location.href getter throws SecurityError in lexical realm");
+
+
+promise_test(async t => {
+ const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN);
+ assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow).location = URL_VALID; });
+ assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.set(sameOriginWindow, "location", URL_VALID, {}); });
+ assert_throws_js(TypeError, () => { locationSet.call(() => {}, URL_VALID); });
+}, "Same-origin window.location setter throws TypeError in holder's realm on invalid |this| value");
+
+promise_test(async t => {
+ const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN);
+ assert_throws_js(sameOriginWindow.TypeError, () => { Object.create(sameOriginWindow.location).href = URL_VALID; });
+ assert_throws_js(sameOriginWindow.TypeError, () => { Reflect.set(sameOriginWindow.location, "href", URL_VALID, {}); });
+ assert_throws_js(TypeError, () => { hrefSet.call(undefined, URL_VALID); });
+}, "Same-origin location.href setter throws TypeError in holder's realm on invalid |this| value");
+
+promise_test(async t => {
+ const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN);
+ assert_throws_js(TypeError, () => { Object.create(crossOriginWindow).location = URL_VALID; });
+ assert_throws_js(TypeError, () => { Reflect.set(crossOriginWindow, "location", URL_VALID, {}); });
+ assert_throws_js(TypeError, () => { locationSet.call([], URL_VALID); });
+}, "Cross-origin window.location setter throws TypeError in lexical realm on invalid |this| value");
+
+promise_test(async t => {
+ const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN);
+ assert_throws_js(TypeError, () => { Object.create(crossOriginWindow.location).href = URL_VALID; });
+ assert_throws_js(TypeError, () => { Reflect.set(crossOriginWindow.location, "href", URL_VALID, {}); });
+ assert_throws_js(TypeError, () => { hrefSet.call(null, URL_VALID); });
+}, "Cross-origin location.href setter throws TypeError in lexical realm on invalid |this| value");
+
+
+promise_test(async t => {
+ const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN);
+ assert_throws_js(sameOriginWindow.TypeError, () => { sameOriginWindow.location = Symbol(); });
+
+ // The error originates in sameOriginWindow.location.href setter, hence it's not in realm of locationSet.
+ assert_throws_js(sameOriginWindow.TypeError, () => { locationSet.call(sameOriginWindow, Symbol()); });
+}, "Same-origin window.location` setter throws TypeError in holder's realm on non-coercible URL argument");
+
+promise_test(async t => {
+ const sameOriginWindow = await makeWindow(t, URL_SAME_ORIGIN);
+ assert_throws_js(sameOriginWindow.TypeError, () => { sameOriginWindow.location.href = Symbol(); });
+ assert_throws_js(TypeError, () => { hrefSet.call(sameOriginWindow.location, Symbol()); });
+}, "Same-origin location.href setter throws TypeError in holder's realm on non-coercible URL argument");
+
+promise_test(async t => {
+ const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN);
+ assert_throws_js(TypeError, () => { crossOriginWindow.location = Symbol(); });
+ assert_throws_js(TypeError, () => { locationSet.call(crossOriginWindow, Symbol()); });
+}, "Cross-origin window.location setter throws TypeError in lexical realm on non-coercible URL argument");
+
+promise_test(async t => {
+ const crossOriginWindow = await makeWindow(t, URL_CROSS_ORIGIN);
+ assert_throws_js(TypeError, () => { crossOriginWindow.location.href = Symbol(); });
+ assert_throws_js(TypeError, () => { hrefSet.call(crossOriginWindow.location, Symbol()); });
+}, "Cross-origin location.href setter throws TypeError in lexical realm on non-coercible URL argument");
+
+function makeWindow(t, src) {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ t.add_cleanup(() => { iframe.remove(); });
+ iframe.onload = () => { resolve(iframe.contentWindow); };
+ iframe.src = src;
+ document.body.append(iframe);
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-iframe.html b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-iframe.html
new file mode 100644
index 0000000000..fabde327a1
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-iframe.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html>
+ <head>
+ <title>about:blank in child browsing context aliases security origin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ test(() => {
+ let iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ // Should not throw: srcdoc should always be same-origin.
+ iframe.contentWindow.document.body.innerHTML = '<p>Hello world!</p>';
+
+ // Explicitly set `domain` component of origin: any other same-origin
+ // browsing contexts are now cross-origin unless they also explicitly
+ // set document.domain to the same value.
+ document.domain = document.domain;
+ // Should not throw: the origin should be aliased, so setting
+ // document.domain in one Document should affect both Documents.
+ assert_equals(
+ iframe.contentWindow.document.body.textContent,
+ 'Hello world!');
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-window.html b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-window.html
new file mode 100644
index 0000000000..cc3177f943
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/about-blank-window.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+ <head>
+ <title>about:blank in auxiliary browsing context aliases security origin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ test(() => {
+ let newWindow = window.open();
+ // Should not throw: the newly-opened window should be same-origin.
+ newWindow.document.body.innerHTML = '<p>Hello world!</p>';
+
+ // Explicitly set `domain` component of origin: any other same-origin
+ // browsing contexts are now cross-origin unless they also explicitly
+ // set document.domain to the same value.
+ document.domain = document.domain;
+ // Should not throw: the origin should be aliased, so setting
+ // document.domain in one Document should affect both Documents.
+ assert_equals(newWindow.document.body.textContent, 'Hello world!');
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/about-srcdoc.html b/testing/web-platform/tests/html/browsers/origin/inheritance/about-srcdoc.html
new file mode 100644
index 0000000000..971811ee66
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/about-srcdoc.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+ <head>
+ <title>about:srcdoc aliases security origin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ test(() => {
+ let iframe = document.createElement('iframe');
+ iframe.srcdoc = '<body></body>';
+ document.body.appendChild(iframe);
+ // Should not throw: srcdoc should always be same-origin.
+ iframe.contentWindow.document.body.innerHTML = '<p>Hello world!</p>';
+
+ // Explicitly set `domain` component of origin: any other same-origin
+ // browsing contexts are now cross-origin unless they also explicitly
+ // set document.domain to the same value.
+ document.domain = document.domain;
+ // Should not throw: the origin should be aliased, so setting
+ // document.domain in one Document should affect both Documents.
+ assert_equals(
+ iframe.contentWindow.document.body.textContent,
+ 'Hello world!');
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js b/testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js
new file mode 100644
index 0000000000..39dc3b1cc2
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js
@@ -0,0 +1,10 @@
+// META: script=/common/get-host-info.sub.js
+
+// To use document.domain, we need to start from a subdomain.
+//
+// For document.domain setter to work, some web browser require the
+// |Origin-Agent-Cluster: ?0| header to be set uniformly on the origin.
+const origin = get_host_info().OTHER_ORIGIN;
+const openee = window.open(
+ origin + '/html/browsers/origin/inheritance/resources/document-write.html')
+fetch_tests_from_window(openee);
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js.headers b/testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js.headers
new file mode 100644
index 0000000000..e007de4d7b
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/document-write.https.window.js.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?0
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/javascript-url.html b/testing/web-platform/tests/html/browsers/origin/inheritance/javascript-url.html
new file mode 100644
index 0000000000..7dfb1130ce
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/javascript-url.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+ <head>
+ <title>javascript: aliases security origin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ promise_test(t => {
+ let iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ // Should not throw: srcdoc should always be same-origin.
+ iframe.contentDocument;
+
+ iframe.contentWindow.location = 'javascript:"Hello world!"';
+ return new Promise(resolve => {
+ iframe.addEventListener('load', resolve);
+ }).then(() => {
+ // Explicitly set `domain` component of origin: any other same-origin
+ // browsing contexts are now cross-origin unless they also explicitly
+ // set document.domain to the same value.
+ document.domain = document.domain;
+ // Should not throw: the origin should be aliased, so setting
+ // document.domain in one Document should affect both Documents.
+ assert_equals(
+ iframe.contentWindow.document.body.textContent,
+ 'Hello world!');
+ });
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html
new file mode 100644
index 0000000000..7a6ff3118e
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body></body>
+ <script>
+ const domain_start = document.domain;
+ const domain_new = domain_start.replace(/^[^.]+\./,'');
+
+ async_test(test => {
+ const iframe = document.createElement('iframe');
+ iframe.src = './iframe-with-about-blank-iframe.html';
+ iframe.onload = test.step_func_done(() => {
+ const doc0 = frames[0].frames[0].document;
+ const doc1 = frames[0].frames[1].document;
+
+ assert_equals(doc0.domain, domain_start);
+ assert_equals(doc1.domain, domain_start);
+
+ doc0.open();
+ doc1.open();
+ assert_equals(doc0.domain, domain_start);
+ assert_equals(doc1.domain, domain_start);
+
+ document.domain = domain_new;
+ assert_equals(doc0.domain, domain_start);
+ assert_equals(doc1.domain, domain_start);
+
+ doc0.close();
+ doc1.close();
+ assert_equals(doc0.domain, domain_start);
+ assert_equals(doc1.domain, domain_start);
+ });
+ document.body.appendChild(iframe);
+ }, "document.open() do not make the callee's origin to alias the caller's"
+ + " one");
+ </script>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html.headers b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html.headers
new file mode 100644
index 0000000000..e007de4d7b
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/document-write.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?0
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html
new file mode 100644
index 0000000000..b3f5125233
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <iframe src="about:blank"></iframe>
+ <iframe src=""></iframe>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html.headers b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html.headers
new file mode 100644
index 0000000000..e007de4d7b
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/inheritance/resources/iframe-with-about-blank-iframe.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?0
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-bad-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-bad-subdomain.sub.https.html
new file mode 100644
index 0000000000..3a45ee6d6a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-bad-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, child attempts to origin-key but uses a bad header value, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frameIndex = 0;
+for (const badValue of ["", "?0", "true", "\"?1\"", "1", "?2", "(?1)"]) {
+ promise_test(async () => {
+ await insertIframe("{{hosts[][www]}}", badValue);
+ }, `"${badValue}": frame insertion`);
+
+ // Since the header values are bad they should be site-keyed.
+ testSameAgentCluster([self, frameIndex], `"${badValue}"`);
+ testGetter(frameIndex, false, `"${badValue}"`);
+ ++frameIndex;
+}
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-port.sub.https.html
new file mode 100644
index 0000000000..85fb1f64e0
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-port.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, child is origin-keyed, child is different-origin to the parent because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1");
+});
+
+// Since they're different-origin, the child's request is respected, so the
+// parent ends up in the site-keyed agent cluster and the child in the
+// origin-keyed one.
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, false, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-same.sub.https.html
new file mode 100644
index 0000000000..7ece02c81a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-same.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, child is origin-keyed, child is same-origin to the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][]}}", "?1");
+});
+
+// Since they're same-origin, and the parent loaded in the site-keyed agent
+// cluster, the child's request for origin-keying gets ignored, and both end up
+// site-keyed.
+testSameAgentCluster([self, 0]);
+
+testGetter(self, false, "parent");
+testGetter(0, false, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain-with-redirect.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain-with-redirect.sub.https.html
new file mode 100644
index 0000000000..994f80876d
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain-with-redirect.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, child is reached via a redirect response with no header, child final response does have the header</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][www]}}", "?1", { redirectFirst: true });
+});
+
+// Since they're different-origin, the child's request is respected, so the
+// parent ends up in the site-keyed agent cluster and the child in the
+// origin-keyed one.
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, false, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..5fc2fa29f3
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yes-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, child is origin-keyed, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Since they're different-origin, the child's request is respected, so the
+// parent ends up in the site-keyed agent cluster and the child in the
+// origin-keyed one.
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, false, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html
new file mode 100644
index 0000000000..3e7c8419b3
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-no-child-yeswithparams-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, child is origin-keyed using parameters on its structured header, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][www]}}", "?1;param1;param2=value2");
+});
+
+// Since they're different-origin, the child's request is respected, so the
+// parent ends up in the site-keyed agent cluster and the child in the
+// origin-keyed one.
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, false, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html
new file mode 100644
index 0000000000..f00814cfbf
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, child is site-keyed, child is is different-origin to the parent because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
+});
+
+// Since they're different-origin, the parent's request is respected, as is the
+// child's non-request. So the parent ends up in the origin-keyed agent cluster
+// and the child ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, true, "parent");
+testGetter(0, false, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-port.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html
new file mode 100644
index 0000000000..307f8c48d7
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, child is site-keyed, child is same-origin to the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][]}}");
+});
+
+// Since they're same-origin, and the parent loaded with origin-keying, the
+// child's non-request gets ignored, and both end up origin-keyed.
+testSameAgentCluster([self, 0]);
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-same.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html
new file mode 100644
index 0000000000..8c823fa36f
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, child is site-keyed, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][www]}}");
+});
+
+// Since they're different-origin, the parent's request is respected, as is the
+// child's non-request. So the parent ends up in the origin-keyed agent cluster
+// and the child ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, true, "parent");
+testGetter(0, false, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-no-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html
new file mode 100644
index 0000000000..5e431e6e41
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, child is site-keyed, child is different-origin to the parent because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][]}}:{{ports[https][1]}}", "?1");
+});
+
+// Both request origin-keying, so the parent ends up in one origin-keyed agent
+// cluster (the default port's origin), and the child ends up in a different
+// origin-keyed agent cluster (the other port's origin).
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-port.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html
new file mode 100644
index 0000000000..3b8c214a61
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, child is site-keyed, child is same-origin to the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][]}}", "?1");
+});
+
+// Both request origin-keying, and they're same-origin, so they both end up in
+// the same origin-keyed agent cluster.
+testSameAgentCluster([self, 0]);
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-same.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..136a3a0bba
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, child is site-keyed, child is a subdomain of the parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Both request origin-keying, so the parent ends up in one origin-keyed agent
+// cluster (the base domain's origin), and the child ends up in a different
+// origin-keyed agent cluster (the www subdomain's origin).
+testDifferentAgentClusters([self, 0]);
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/1-iframe/parent-yes-child-yes-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..1bb252f0ab
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, subdomain child 1 is site-keyed, same-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Must be sequential, not parallel: the site-keyed frame must load first.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+
+// Since they're different-origin, the parent's non-request is respected, as is
+// child 1's non-request. child 2 requests origin-keying but is ignored, since
+// child 1 is in the same browsing context group.
+//
+// So, everyone ends up in the site-keyed agent cluster.
+testSameAgentCluster([self, 0], "Parent to child1");
+testSameAgentCluster([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testGetter(self, false, "parent");
+testGetter(0, false, "child1");
+testGetter(1, false, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
new file mode 100644
index 0000000000..5b80c528f0
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, subdomain child 1 is site-keyed, different-port-same-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
+});
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent and child 1 end up in the site-keyed agent cluster, and child
+// 2 ends up in its own origin-keyed agent cluster.
+testSameAgentCluster([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, false, "parent");
+testGetter(0, false, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000000..bba13b82a4
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, subdomain child 1 is site-keyed, different-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www1]}}", "?1");
+});
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent and child 1 end up in the site-keyed agent cluster, and child
+// 2 ends up in its own origin-keyed agent cluster.
+testSameAgentCluster([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, false, "parent");
+testGetter(0, false, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html
new file mode 100644
index 0000000000..d01d180213
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-port.sub.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, subdomain child 1 is origin-keyed, non-subdomain but different-port child 2 is site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
+});
+
+// Everyone is different-origin, so everyone's request/non-request is
+// respected.
+//
+// So, the parent and child2 end up in the site-keyed agent cluster, and child1
+// ends up in an origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testSameAgentCluster([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, false, "parent");
+testGetter(0, true, "child1");
+testGetter(1, false, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html
new file mode 100644
index 0000000000..9a245b3ace
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, subdomain child 1 is origin-keyed, same-subdomain child 2 is site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Must be sequential, not parallel: the origin-keyed frame must load first.
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][www]}}");
+});
+
+
+// Since they're different-origin, the parent's non-request is respected, as is
+// child 1's request. child 2's non-request is ignored, since child 1 is in the
+// same browsing context group.
+//
+// So, the parent ends up in the site-keyed agent cluster, and both children end
+// up in an origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testGetter(self, false, "parent");
+testGetter(0, true, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html
new file mode 100644
index 0000000000..c308b9a17a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is site-keyed, same-subdomain child 2 is site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www]}}");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, and child 1 and
+// child 2 both end up in the site-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, false, "child1");
+testGetter(1, false, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html
new file mode 100644
index 0000000000..767d908c21
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is site-keyed, different-subdomain child 2 is site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www1]}}");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, and child 1 and
+// child 2 both end up in the site-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, false, "child1");
+testGetter(1, false, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..45047f3ae1
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is site-keyed, same-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Must be sequential, not parallel: the non-isolaed frame must load first.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+
+// Since they're different-origin, the parent's request is respected, as is
+// child 1's non-request. child 2 requests origin-keying but is ignored, since
+// child 1 is in the same browsing context group.
+//
+// So, the parent ends up in the origin-keyed agent cluster, and both children
+// ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, false, "child1");
+testGetter(1, false, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000000..202b916767
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is site-keyed, different-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed
+// agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, false, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
new file mode 100644
index 0000000000..a1316731ac
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is site-keyed, different-port-same-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}");
+ await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests/non-requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// the site-keyed agent cluster, and child 2 ends up in a different origin-keyed
+// agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, false, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html
new file mode 100644
index 0000000000..46bef4b9a9
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, non-subdomain but different-port child 2 is site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][]}}:{{ports[https][1]}}");
+});
+
+// Everyone is different-origin, so everyone's request/non-request is
+// respected.
+//
+// So, child2 ends up in the site-keyed agent cluster, and the parent and child1
+// end up in two separate origin-keyed agent clusters.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child1");
+testGetter(1, false, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html
new file mode 100644
index 0000000000..39dcfc04b0
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, same-subdomain child 2 is site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Must be sequential, not parallel: the origin-keyed frame must load first.
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][www]}}");
+});
+
+
+// Since they're different-origin, the parent's request is respected, as is
+// child 1's request. child 2's non-request is ignored, since child 1 is in the
+// same browsing context group.
+//
+// So, the parent ends up in the origin-keyed agent cluster, and both children
+// ends up in a different origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..b6daf91b54
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, same-subdomain child 2 is site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+
+// Since they're different-origin, the parent's request is respected, as is
+// child 1's request. child 2's request is redundant, since child 1 is in the
+// same browsing context group.
+//
+// So, the parent ends up in the origin-keyed agent cluster, and both children
+// ends up in a different origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000000..b94f9392d4
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, different-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][www1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// a second origin-keyed agent cluster, and child 2 ends up in a third
+// origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html
new file mode 100644
index 0000000000..fb3fda1bf2
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, subdomain child 1 is origin-keyed, different-port-same-subdomain child 2 is origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+promise_setup(async () => {
+ // Order of loading should not matter, but we make it sequential to ensure the
+ // tests are deterministic.
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][www]}}:{{ports[https][1]}}", "?1");
+});
+
+
+// Since everybody is different-origin, everyone's requests get
+// respected.
+//
+// So, the parent ends up in its origin-keyed agent cluster, child 1 ends up in
+// a second origin-keyed agent cluster, and child 2 ends up in a third
+// origin-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child1");
+testDifferentAgentClusters([self, 1], "Parent to child2");
+testDifferentAgentClusters([0, 1], "child1 to child2");
+testDifferentAgentClusters([1, 0], "child2 to child1");
+
+testGetter(self, true, "parent");
+testGetter(0, true, "child1");
+testGetter(1, true, "child2");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/META.yml b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/META.yml
new file mode 100644
index 0000000000..f21ce69f6a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/META.yml
@@ -0,0 +1,5 @@
+spec: https://html.spec.whatwg.org/multipage/#origin-keyed-agent-clusters
+suggested_reviewers:
+ - domenic
+ - annevk
+ - wjmaclean
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/README.md b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/README.md
new file mode 100644
index 0000000000..85ba3bce7f
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/README.md
@@ -0,0 +1,30 @@
+# Origin-keyed agent clusters tests
+
+These are tests for the [origin-keyed agent clusters](https://html.spec.whatwg.org/multipage/origin.html#origin-keyed-agent-clusters)
+feature.
+
+## Test filenames
+
+The tests in `2-iframes` follow the file naming pattern
+
+```
+parent-[yes|no]-child1-[yes|no]-[designator]-child2-[yes|no]-[designator]
+```
+
+Here:
+
+* `yes` or `no` refers to whether the `Origin-Agent-Cluster` header is set or
+ unset.
+* `designator` explains how the child differs from the parent: e.g. by being a
+ subdomain, or having a different port, or both. There's also `same` if it's
+ same-origin.
+
+Other directories have variations on this, e.g. `1-iframe/` does the same thing
+but for a single `child` instead of `child1` and `child2`, and `navigation/`
+uses `1` and `2` to represent the two different locations the single iframe will
+be navigated to.
+
+## Coverage
+
+Header parsing is covered by a few tests in the `1-iframe/` subdirectory, and
+not duplicated to all other scenarios.
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html
new file mode 100644
index 0000000000..556d528aa0
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>The initial about:blank respects origin isolation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "./resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertAboutBlankIframe();
+ await insertIframe("{{hosts[][www]}}");
+});
+
+// Since the initial about:blank inherits its origin from its parent, it is
+// same-origin with the parent, and thus cross-origin with child2.
+testSameAgentCluster([self, 0], "parent to about:blank");
+testDifferentAgentClusters([0, 1], "about:blank to child2");
+testDifferentAgentClusters([1, 0], "child2 to about:blank");
+
+testGetter(self, true, "parent");
+testGetter(0, true, "about:blank");
+testGetter(1, false, "child2");
+
+async function insertAboutBlankIframe() {
+ const iframe = await createBlankIframe();
+
+ // Now create and add the script, but don't navigate anywhere (since we want
+ // to stay on the initial about:blank).
+ // We need to absolutize the URL to since about:blank doesn't have a base URL.
+ const scriptURL = (new URL("./resources/send-header-page-script.mjs", import.meta.url)).href;
+ const script = iframe.contentDocument.createElement("script");
+ script.type = "module";
+ script.src = scriptURL;
+
+ await new Promise((resolve, reject) => {
+ script.onload = resolve;
+ script.onerror = () => reject(
+ new Error("Could not load the child frame script into the about:blank page")
+ );
+ iframe.contentDocument.body.append(script);
+ });
+
+ await setBothDocumentDomains(iframe.contentWindow);
+}
+
+function createBlankIframe() {
+ const iframe = document.createElement("iframe");
+ const promise = new Promise(resolve => {
+ iframe.addEventListener("load", resolve);
+ });
+ document.body.append(iframe);
+ return promise;
+}
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/about-blank.https.sub.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html
new file mode 100644
index 0000000000..b4535d9e54
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Setting document.domain does not change same-originness when origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!--
+ Other tests check that using document.domain doesn't allow cross-origin
+ access. This test ensures a different, more subtle property: that
+ origin-keying makes document.domain into a no-op in other ways.
+-->
+
+<iframe src="resources/frame.html"></iframe>
+<iframe src="//{{domains[www1]}}:{{location[port]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html"></iframe>
+
+<script type="module">
+setup({ explicit_done: true });
+
+window.onload = () => {
+ test(() => {
+ // Normally, setting document.domain to itself would change the domain
+ // component of the origin. Since the iframe does *not* set document.domain,
+ // the two would then be considered cross-origin.
+ document.domain = document.domain;
+
+ // However, because we're origin-keyed, this shouldn't have any impact. The
+ // test fails if this throws, and passes if it succeeds.
+ frames[0].document;
+ }, "Setting document.domain must not change same-originness");
+
+ test(() => {
+ assert_throws_dom("SecurityError", () => {
+ document.domain = "{{hosts[][nonexistent]}}";
+ });
+ }, "The registrable domain suffix check must happen before the bail-out");
+
+ async_test(t => {
+ frames[1].postMessage({
+ type: "set document.domain",
+ newValue: "{{host}}"
+ }, "*");
+
+ window.onmessage = t.step_func_done(e => {
+ assert_equals(e.data.type, "new document.domain");
+ assert_equals(e.data.result, "{{domains[www1]}}");
+ });
+ }, "Having an origin-keyed subdomain child try to set document.domain " +
+ "must not change the document.domain value it sees");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/document-domain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html
new file mode 100644
index 0000000000..a521934cc9
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster must be implied by cross-origin isolation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="//{{domains[www1]}}:{{location[port]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html"></iframe>
+
+<div id="log"></div>
+
+<script type="module">
+import { testGetter } from "../resources/helpers.mjs";
+
+setup({ explicit_done: true });
+
+window.onload = () => {
+ // Cross-origin isolated pages are always origin-keyed.
+ testGetter(self, true, "self");
+
+ // Child frames of cross-origin isolated pages must also be cross-origin
+ // isolated, and thus also origin-keyed. Make sure the implementation doesn't
+ // treat them specially in some weird way, for the purposes of this
+ // implication.
+ testGetter(0, true, "child");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html.headers
new file mode 100644
index 0000000000..5f8621ef83
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/cross-origin-isolated.sub.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html
new file mode 100644
index 0000000000..e0b5f92376
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a top-level frame sandboxed by CSP with no Origin-Agent-Cluster header</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import { testGetter } from "../resources/helpers.mjs";
+
+// Even without the header, sandboxing makes this page have an opaque origin,
+// so it is origin-keyed.
+testGetter(self, true);
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html.headers
new file mode 100644
index 0000000000..4705ce9ded
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-no.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts;
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html
new file mode 100644
index 0000000000..a2220c5acc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a top-level frame sandboxed by CSP with an Origin-Agent-Cluster header</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import { testGetter } from "../resources/helpers.mjs";
+
+// We're definitely origin-keyed: both the CSP sandboxing and the
+// Origin-Agent-Cluster header should ensure this.
+testGetter(self, true);
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html.headers
new file mode 100644
index 0000000000..a52bf50900
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/csp-sandbox-yes.https.html.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: sandbox allow-scripts;
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-no.https.html
new file mode 100644
index 0000000000..06149cda8a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-no.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a javascript: URL navigated to from a data: URL on a site-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/data-to-javascript-test.mjs";
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html
new file mode 100644
index 0000000000..af6fea0ad9
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a javascript: URL navigated to from a data: URL on an origin-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/data-to-javascript-test.mjs";
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-to-javascript-yes.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-no.https.html
new file mode 100644
index 0000000000..8ae564a072
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-no.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a data: URL on a site-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/data-url-test.mjs";
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html
new file mode 100644
index 0000000000..bcbf098a66
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a data: URL on an origin-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/data-url-test.mjs";
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/data-url-yes.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html
new file mode 100644
index 0000000000..1b54ad42a4
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-no.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a javascript: URL on a site-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/data-url-test.mjs";
+runTest({ expected: false });
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html
new file mode 100644
index 0000000000..e2b7730dd2
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a javascript: URL on an origin-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/data-url-test.mjs";
+runTest({ expected: true });
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/javascript-url-yes.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html
new file mode 100644
index 0000000000..fcf5068908
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a removed frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import { navigateIframe } from "../resources/helpers.mjs";
+
+promise_test(async () => {
+ // We cannot use insertIframe because it sets both `document.domain`s. That
+ // shouldn't matter, but Chrome has a bug (https://crbug.com/1095145), so
+ // let's avoid making the test needlessly fail because of that bug.
+ const iframe = document.createElement("iframe");
+ const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1");
+ document.body.append(iframe);
+ await navigatePromise;
+
+ const frameWindow = iframe.contentWindow;
+
+ assert_equals(frameWindow.originAgentCluster, true, "before");
+ iframe.remove();
+ assert_equals(frameWindow.originAgentCluster, true, "after");
+}, "Removing the iframe does not change originAgentCluster");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/removed-iframe.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-to-javascript-test.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-to-javascript-test.mjs
new file mode 100644
index 0000000000..3a88253ee3
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-to-javascript-test.mjs
@@ -0,0 +1,33 @@
+import { insertCustomIframe, testSupportScript } from "./helpers.mjs";
+import { waitForIframe, testGetter } from "../../resources/helpers.mjs";
+
+const testSupportScriptSuitableForNesting =
+ testSupportScript.replace('</script>', '</scri` + `pt>');
+
+export default () => {
+ promise_setup(async () => {
+ const jsURL = `javascript:'${testSupportScript}'`;
+ const iframe = await insertCustomIframe(`data:text/html,
+ Start page
+ <script>
+ window.onmessage = () => {
+ location.href = \`javascript:'End page${testSupportScriptSuitableForNesting}'\`;
+ };
+ </script>
+ `);
+
+ const waitPromise = waitForIframe(iframe, "javascript: URL");
+
+ // Kick off the navigation. We can't do it directly because only same-origin
+ // pages can navigate to a javascript: URL, and we're not same-origin with
+ // a data: URL.
+ iframe.contentWindow.postMessage(undefined, "*");
+
+ await waitPromise;
+ });
+
+ // The javascript: URL iframe inherits its origin from the previous occupant
+ // of the iframe, which is a data: URL, so it should always be true.
+
+ testGetter(0, true);
+};
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-url-test.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-url-test.mjs
new file mode 100644
index 0000000000..1a9b3be47f
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/data-url-test.mjs
@@ -0,0 +1,13 @@
+import { insertCustomIframe, testSupportScript } from "./helpers.mjs";
+import { testGetter } from "../../resources/helpers.mjs";
+
+export default () => {
+ promise_setup(() => {
+ return insertCustomIframe(`data:text/html,${testSupportScript}`);
+ });
+
+ // The data: URL iframe has an opaque origin, so it should return true, since
+ // for them site === origin so they are always origin-keyed.
+
+ testGetter(0, true, "data: URL child");
+};
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/helpers.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/helpers.mjs
new file mode 100644
index 0000000000..4610ffcad0
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/helpers.mjs
@@ -0,0 +1,28 @@
+import { waitForIframe } from "../../resources/helpers.mjs";
+
+/**
+ * Inserts an iframe, not specialized for origin-keyed agent cluster testing,
+ * pointing to a custom URL. This is just a wrapper to remove some boilerplate.
+ * @param {string} src - The src="" value for the iframe
+ */
+export async function insertCustomIframe(src) {
+ const iframe = document.createElement("iframe");
+ iframe.src = src;
+
+ const waitPromise = waitForIframe(iframe);
+ document.body.append(iframe);
+ await waitPromise;
+
+ return iframe;
+}
+
+/**
+ * This is the part of send-oac-header.py that allows us to reuse testGetter.
+ */
+export const testSupportScript = `
+ <script>
+ window.onmessage = () => {
+ parent.postMessage(self.originAgentCluster, "*");
+ };
+ </script>
+`;
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/javascript-url-test.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/javascript-url-test.mjs
new file mode 100644
index 0000000000..de474d8caf
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/javascript-url-test.mjs
@@ -0,0 +1,14 @@
+import { insertCustomIframe, testSupportScript } from "./helpers.mjs";
+import { testGetter } from "../../resources/helpers.mjs";
+
+export default ({ expected }) => {
+ promise_setup(() => {
+ return insertCustomIframe(`javascript:'${testSupportScript}'`);
+ });
+
+ // The javascript: URL iframe inherits its origin from the previous occupant
+ // of the iframe, which is about:blank, which in turn inherits from the
+ // parent. So, the caller needs to tell us what to expect.
+
+ testGetter(0, expected);
+};
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-iframe-test.sub.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-iframe-test.sub.mjs
new file mode 100644
index 0000000000..9357df00c5
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-iframe-test.sub.mjs
@@ -0,0 +1,20 @@
+import {
+ navigateIframe,
+ testGetter
+} from "../../resources/helpers.mjs";
+
+export default () => {
+ // We do this manually instead of using insertIframe because we want to add a
+ // sandbox="" attribute and we don't want to set both document.domains.
+ promise_setup(() => {
+ const iframe = document.createElement("iframe");
+ iframe.sandbox = "allow-scripts";
+ const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1");
+ document.body.append(iframe);
+ return navigatePromise;
+ });
+
+ // Sandboxed iframes have an opaque origin, so it should return true, since
+ // for them site === origin so they are always origin-keyed.
+ testGetter(0, true);
+};
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-same-origin-iframe-test.sub.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-same-origin-iframe-test.sub.mjs
new file mode 100644
index 0000000000..272f805870
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/resources/sandboxed-same-origin-iframe-test.sub.mjs
@@ -0,0 +1,20 @@
+import {
+ navigateIframe,
+ testGetter
+} from "../../resources/helpers.mjs";
+
+export default ({ expected }) => {
+ // We do this manually instead of using insertIframe because we want to add a
+ // sandbox="" attribute and we don't want to set both document.domains.
+ promise_setup(() => {
+ const iframe = document.createElement("iframe");
+ iframe.sandbox = "allow-scripts allow-same-origin";
+ const navigatePromise = navigateIframe(iframe, "{{hosts[][]}}", "?1");
+ document.body.append(iframe);
+ return navigatePromise;
+ });
+
+ // Since the allow-same-origin token is set, this should behave like a normal
+ // iframe, and follow the embedder.
+ testGetter(0, expected);
+};
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-no.https.html
new file mode 100644
index 0000000000..29758a17b8
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-no.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a sandboxed iframe on a site-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/sandboxed-iframe-test.sub.mjs";
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html
new file mode 100644
index 0000000000..5eb5d08d10
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a sandboxed iframe on an origin-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/sandboxed-iframe-test.sub.mjs";
+runTest();
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-iframe-yes.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html
new file mode 100644
index 0000000000..3ed4096f39
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-no.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a sandboxed, but same-origin, iframe on a site-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/sandboxed-same-origin-iframe-test.sub.mjs";
+runTest({ expected: false });
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html
new file mode 100644
index 0000000000..c7ea5f0693
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>window.originAgentCluster for a sandboxed, but same-origin, iframe on an origin-keyed page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import runTest from "./resources/sandboxed-same-origin-iframe-test.sub.mjs";
+runTest({ expected: true });
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/getter-special-cases/sandboxed-same-origin-iframe-yes.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html
new file mode 100644
index 0000000000..a593619ea6
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, child1 is site-keyed, child1 navigates to a different site, child2 gets inserted and is origin-keyed, child1 navigates back</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ waitForIframe,
+ setBothDocumentDomains,
+ testDifferentAgentClusters,
+ testSameAgentCluster,
+} from "./resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][www]}}");
+});
+
+// Since they're different-origin, the parent's origin-keying request is
+// respected, as is the child's non-request. So the parent ends up in the
+// origin-keyed agent cluster and the child ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0], "Before navigation: parent to child1");
+
+// Navigate the iframe to a different site. These, of course, must not be in the
+// same agent cluster.
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[alt][]}}");
+}, "Navigation");
+
+// Now insert a second iframe, pointing to the same place as the first one
+// originally did, but this time with origin-keying requested. Because of the
+// historical map of agent cluster keys for the browsing context group, the new
+// iframe should still end up in the site-keyed agent cluster.
+
+promise_test(async () => {
+ await insertIframe("{{hosts[][www]}}", "?1");
+}, "Inserting a second iframe");
+
+testDifferentAgentClusters([self, 1], "After navigation: parent to child2");
+
+// Now navigate the first iframe back. The resulting Document should be put in
+// the site-keyed agent cluster, together with the second iframe's Document.
+
+promise_test(async () => {
+ const waitPromise = waitForIframe(frame1);
+ history.back();
+ await waitPromise;
+
+ await setBothDocumentDomains(frames[0]);
+}, "Going back in history (navigating back the first iframe)");
+
+testDifferentAgentClusters([self, 0], "After back: parent to child1");
+testDifferentAgentClusters([self, 1], "After back: parent to child2");
+testSameAgentCluster([0, 1], "child1 to child2");
+testSameAgentCluster([1, 0], "child2 to child1");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html
new file mode 100644
index 0000000000..8237f2f23f
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-port.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, navigate a frame from same-origin site-keyed to different-origin (different-port) origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Nobody requested origin-keying yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testGetter(self, false, "before parent");
+testGetter(0, false, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}", "?1");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, its origin-keying request should be
+// respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testGetter(self, false, "after parent");
+testGetter(0, true, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..00d8c3164a
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-same-2-yes-subdomain.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, navigate a frame from same-origin site-keyed to different-origin (subdomain) origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Nobody requested origin-keying yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testGetter(self, false, "before parent");
+testGetter(0, false, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][www]}}", "?1");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, its origin-keying request should be
+// respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testGetter(self, false, "after parent");
+testGetter(0, true, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..803e684e1c
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, navigate a frame from a subdomain site-keyed to the same subdomain origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][www]}}");
+});
+
+// Nobody requested origin-keying yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testGetter(self, false, "before parent");
+testGetter(0, false, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][www]}}", "?1");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Because this subdomain was previously site-keyed, the second load's
+// origin-keying request is ignored; instead we continue with site-keying.
+
+testSameAgentCluster([self, 0], "After: parent to child");
+testGetter(self, false, "after parent");
+testGetter(0, false, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html
new file mode 100644
index 0000000000..b96d10afd1
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, navigate a frame from a subdomain site-keyed to a second-subdomain origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][www]}}");
+});
+
+// Nobody requested origin-keying yet.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testGetter(self, false, "before parent");
+testGetter(0, false, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][www1]}}", "?1");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Because we're going to a different subdomain (and thus different origin), the
+// origin-keying request is respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testGetter(self, false, "after parent");
+testGetter(0, true, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html
new file mode 100644
index 0000000000..a70ed56670
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, navigate a frame from a subdomain origin-keyed to a second-subdomain site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Since they are different-origin, the child's origin-keying request is
+// respected.
+
+testDifferentAgentClusters([self, 0], "Before: parent to child");
+testGetter(self, false, "before parent");
+testGetter(0, true, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][www1]}}");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Make sure that the different-subdomain page (which doesn't request
+// origin-keying) doesn't somehow get origin-keyed just because its predecessor
+// was.
+
+testSameAgentCluster([self, 0], "After: parent to child");
+testGetter(self, false, "after parent");
+testGetter(0, false, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html
new file mode 100644
index 0000000000..38e2630128
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is site-keyed, navigate a frame from a subdomain origin-keyed to the same subdomain site-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// Since they are different-origin, the child's origin-keying request is
+// respected.
+
+testDifferentAgentClusters([self, 0], "Before: parent to child");
+testGetter(self, false, "before parent");
+testGetter(0, true, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][www]}}");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Because this subdomain was previously origin-keyed, the second load's
+// non-request is ignored; instead we continue origin-keying.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testGetter(self, false, "after parent");
+testGetter(0, true, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html
new file mode 100644
index 0000000000..6211845be1
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, navigate a frame from same-origin site-keyed to different-origin (different-port) origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Since the parent is origin-keyed, the same-origin child's non-request is
+// ignored, so it gets origin-keyed too.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testGetter(self, true, "before parent");
+testGetter(0, true, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][]}}:{{ports[https][1]}}");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, its non-request should be respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testGetter(self, true, "after parent");
+testGetter(0, false, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-port.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html
new file mode 100644
index 0000000000..ead56754a7
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent is origin-keyed, navigate a frame from same-origin site-keyed to different-origin (subdomain) origin-keyed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ navigateIframe,
+ setBothDocumentDomains,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][]}}");
+});
+
+// Since the parent is origin-keyed, the same-origin child's non-request is
+// ignored, so it gets origin-keyed too.
+
+testSameAgentCluster([self, 0], "Before: parent to child");
+testGetter(self, true, "before parent");
+testGetter(0, true, "before child");
+
+promise_test(async () => {
+ await navigateIframe(frame1, "{{hosts[][www]}}");
+ await setBothDocumentDomains(frames[0]);
+}, "Navigation");
+
+// Since the new page is different-origin, its non-request should be respected.
+
+testDifferentAgentClusters([self, 0], "After: parent to child");
+testGetter(self, true, "after parent");
+testGetter(0, false, "after child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/iframe-navigation/parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html
new file mode 100644
index 0000000000..6f9e5d8b73
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Parent requests origin-keying, child requests origin-keying, child is a subdomain of the parent, but all over insecure HTTP</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testGetter
+} from "./resources/helpers.mjs";
+
+promise_setup(async () => {
+ await insertIframe("{{hosts[][www]}}", "?1");
+});
+
+// All origin-keying requests are ignored, since this is over insecure HTTP.
+// So both end up in the site-keyed agent cluster.
+testSameAgentCluster([self, 0]);
+
+testGetter(self, false, "parent");
+testGetter(0, false, "child");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/insecure-http.sub.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups-crash.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups-crash.https.html
new file mode 100644
index 0000000000..dcfb5eb277
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups-crash.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Crash test for https://crbug.com/1099718</title>
+
+<div id="log"></div>
+
+<script>
+window.open("resources/crashy-popup.sub.html", "windowName1", "noopener");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html
new file mode 100644
index 0000000000..a0bf569b12
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-port.sub.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is site-keyed, openee is origin-keyed, openee is different-origin to the opener because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInADifferentAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][]}}:{{ports[https][1]}}", "?1");
+});
+
+// Since they're different-origin, the openee's origin-keying request is
+// respected, so the opener ends up in the site-keyed agent cluster and the
+// openee in the origin-keyed one.
+testOpenedWindowIsInADifferentAgentCluster(() => openee);
+
+testGetter(self, false, "opener");
+testGetter(() => openee, true, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html
new file mode 100644
index 0000000000..196dff1449
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-same.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is site-keyed, openee is origin-keyed, openee is same-origin to the opener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][]}}", "?1");
+});
+
+// Since they're same-origin, and the opener loaded with site-keying, the
+// child's request for origin-keying gets ignored, and both end up site-keyed.
+testOpenedWindowIsInSameAgentCluster(() => openee);
+
+testGetter(self, false, "opener");
+testGetter(() => openee, false, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..f96d2273d5
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-no-openee-yes-subdomain.sub.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is site-keyed, openee is origin-keyed, openee is a subdomain of the opener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInADifferentAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][www]}}", "?1");
+});
+
+// Since they're different-origin, the openee's origin-keying request is
+// respected, so the opener ends up in the site-keyed agent cluster and the
+// openee in the origin-keyed one.
+testOpenedWindowIsInADifferentAgentCluster(() => openee);
+
+testGetter(self, false, "opener");
+testGetter(() => openee, true, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html
new file mode 100644
index 0000000000..51c5a208c5
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is origin-keyed, openee is site-keyed, openee is different-origin to the opener because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInADifferentAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][]}}:{{ports[https][1]}}");
+});
+
+// Since they're different-origin, the openee's non-request is respected, so the
+// opener ends up in the origin-keyed agent cluster and the openee in the
+// site-keyed one.
+testOpenedWindowIsInADifferentAgentCluster(() => openee);
+
+testGetter(self, true, "opener");
+testGetter(() => openee, false, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-port.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html
new file mode 100644
index 0000000000..562ab40c68
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is origin-keyed, openee is site-keyed, openee is same-origin to the opener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][]}}");
+});
+
+// Since they're same-origin, the openee's non-request is ignored, so both end
+// up in the origin-keyed agent cluster.
+testOpenedWindowIsInSameAgentCluster(() => openee);
+
+testGetter(self, true, "opener");
+testGetter(() => openee, true, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-same.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html
new file mode 100644
index 0000000000..d7d4791459
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is origin-keyed, openee is site-keyed, openee is a subdomain of the opener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInADifferentAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][www]}}");
+});
+
+// Since they're different-origin, the openee's non-request is respected, so the
+// opener ends up in the origin-keyed agent cluster and the openee in the
+// site-keyed one.
+testOpenedWindowIsInADifferentAgentCluster(() => openee);
+
+testGetter(self, true, "opener");
+testGetter(() => openee, false, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-no-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html
new file mode 100644
index 0000000000..32a5066d2e
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is origin-keyed, openee is origin-keyed, openee is different-origin to the opener because of a port mismatch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInADifferentAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][]}}:{{ports[https][1]}}", "?1");
+});
+
+// Both request origin-keying, so the opener ends up in one origin-keyed agent
+// cluster (the default port's origin), and the openee ends up in a different
+// origin-keyed agent cluster (the other port's origin).
+testOpenedWindowIsInADifferentAgentCluster(() => openee);
+
+testGetter(self, true, "opener");
+testGetter(() => openee, true, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-port.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html
new file mode 100644
index 0000000000..a85decac3c
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is origin-keyed, openee is origin-keyed, openee is same-origin to the opener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInSameAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][]}}", "?1");
+});
+
+// Both request origin-keying, and they're same-origin, so they both end up in
+// the same origin-keyed agent cluster.
+testOpenedWindowIsInSameAgentCluster(() => openee);
+
+testGetter(self, true, "opener");
+testGetter(() => openee, true, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-same.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html
new file mode 100644
index 0000000000..148b39af23
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener is origin-keyed, openee is origin-keyed, openee is a subdomain of the opener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ openWindow,
+ testOpenedWindowIsInADifferentAgentCluster,
+ testGetter
+} from "../resources/helpers.mjs";
+
+let openee;
+promise_setup(async () => {
+ openee = await openWindow("{{hosts[][www]}}", "?1");
+});
+
+// Both request origin-keyed, so the opener ends up in one origin-keyed agent
+// cluster (the base domain's origin), and the openee ends up in a different
+// origin-keyed agent cluster (the www subdomain's origin).
+testOpenedWindowIsInADifferentAgentCluster(() => openee);
+
+testGetter(self, true, "opener");
+testGetter(() => openee, true, "openee");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/popups/opener-yes-openee-yes-subdomain.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/regression-1399759.https.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/regression-1399759.https.sub.html
new file mode 100644
index 0000000000..d0b09f335d
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/regression-1399759.https.sub.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="variant" content="?pipe=header(Origin-Agent-Cluster,%3F0)">
+<meta name="variant" content="?pipe=header(Origin-Agent-Cluster,%3F1)">
+<title>Origin-Isolation after navigating about:blank.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+</head>
+<body>
+</body>
+<script>
+// Regression test for crbug.com/1399759. This is mainly based on
+// html/infrastructure/urls/terminology-0/document-base-url-initiated-grand-parent.https.window.html,
+// but restricts itself to the exact error condition.
+//
+// This test is run in two variants which differ in the Origin-Agent-Cluster
+// http header values, ?0 and ?1. The test should pass in either case, but the
+// regression we're testing for involves inconsistent clustering decisions,
+// which requires clustering to be enabled in the first place.
+promise_test(async test => {
+ // Create a cross-origin iframe. Use the executor.html, so we can ask it
+ // to execute scripts for us.
+ const child_token = token();
+ const iframe = document.createElement("iframe");
+ iframe.src = get_host_info().HTTPS_REMOTE_ORIGIN +
+ `/common/dispatcher/executor.html?uuid=${child_token}`;
+ document.body.appendChild(iframe);
+
+ // The child creates a grand child in an iframe.
+ const reply_token = token();
+ send(child_token, `
+ const iframe = document.createElement("iframe");
+ iframe.src = "/common/blank.html";
+ iframe.onload = () => {
+ send("${reply_token}", "grand child loaded");
+ };
+ document.body.appendChild(iframe);
+ `);
+ assert_equals(await receive(reply_token), "grand child loaded");
+ const grandchild = iframe.contentWindow[0];
+
+ // Navigate the grand-child toward about:blank.
+ grandchild.location = "about:blank";
+ assert_equals(await receive(reply_token), "grand child loaded");
+
+ // This document and grandchild are same-origin, because about:blank
+ // inherits its origin from the initiator of the navigation, which is us.
+ // This access should not throw.
+ grandchild.document;
+}, "Check the baseURL of an about:blank document cross-origin with its parent");
+
+promise_test(async test => {
+ // This tests the same setup as above, but with about:srcdoc. Since one
+ // cannot just navigate to about:srcdoc, we'll have to include an extra
+ // step: Create an iframe with srcdoc attribute; navigate away; then
+ // navigate to about:srcdoc.
+ // srcdoc does not inherit the origin from the initiator - unlike
+ // about:blank - and so in this case the grandchild.document access should
+ // throw.
+
+ // Create a cross-origin iframe. Use the executor.html, so we can ask it
+ // to execute scripts for us.
+ const child_token = token();
+ const iframe = document.createElement("iframe");
+ iframe.src = get_host_info().HTTPS_REMOTE_ORIGIN +
+ `/common/dispatcher/executor.html?uuid=${child_token}`;
+ document.body.appendChild(iframe);
+
+ // The child creates a grand child in an iframe, using the srcdoc attribute.
+ const reply_token = token();
+ send(child_token, `
+ const iframe = document.createElement("iframe");
+ iframe.onload = () => {
+ send("${reply_token}", "grand child loaded");
+ };
+ iframe.srcdoc = "nothing interesting";
+ document.body.appendChild(iframe);
+ `);
+ assert_equals(await receive(reply_token), "grand child loaded");
+ const grandchild = iframe.contentWindow[0];
+
+ // Navigate the grand child toward a regular URL.
+ grandchild.location = get_host_info().HTTPS_REMOTE_ORIGIN + "/common/blank.html";
+ assert_equals(await receive(reply_token), "grand child loaded");
+
+ // Navigate the grand-child back, to about:srcdoc.
+ grandchild.location = "about:srcdoc";
+ assert_equals(await receive(reply_token), "grand child loaded");
+
+ // This document and grandchild are cross-origin. about:srcdoc does not
+ // inherits its origin from the initiator of the navigation. This access
+ // should throw:
+ assert_throws_dom("SecurityError", () => { grandchild.document; });
+}, "Check that about:srcdoc navigation does not follow about:blank rules.");
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html
new file mode 100644
index 0000000000..b83aa9f5be
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A site-keyed child at a given origin causes future children to also be site-keyed even after the iframe is removed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script type="module">
+import {
+ insertIframe,
+ testSameAgentCluster,
+ testDifferentAgentClusters,
+ testGetter
+} from "./resources/helpers.mjs";
+
+let frame1;
+promise_setup(async () => {
+ frame1 = await insertIframe("{{hosts[][www]}}");
+});
+
+// Since they're different-origin, the parent's origin-keying request is
+// respected, as is the child's non-request. So the parent ends up in the
+// origin-keyed agent cluster and the child ends up in the site-keyed one.
+testDifferentAgentClusters([self, 0], "Before");
+testGetter(self, true, "parent");
+testGetter(0, false, "child1");
+
+promise_test(async () => {
+ frame1.remove();
+
+ await insertIframe("{{hosts[][www]}}", "?1");
+ await insertIframe("{{hosts[][www1]}}");
+}, "Remove the iframe and insert new ones");
+
+// Because of the historical presence of a site-keyed {{hosts[][www]}} iframe,
+// the origin-keying request for child 2 will be ignored. So,
+// child 2 and child 3 both end up in the site-keyed agent cluster.
+testDifferentAgentClusters([self, 0], "Parent to child2");
+testDifferentAgentClusters([self, 1], "Parent to child3");
+testSameAgentCluster([0, 1], "child2 to child3");
+testSameAgentCluster([1, 0], "child3 to child2");
+
+testGetter(0, false, "child2");
+testGetter(1, false, "child3");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/removing-iframes.sub.https.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/README.md b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/README.md
new file mode 100644
index 0000000000..9292fe3894
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/README.md
@@ -0,0 +1,6 @@
+Why are there `.headers` files here for the `.mjs` scripts?
+
+Because `../getter-special-cases/sandboxed-iframe.sub.https.html` is testing an
+opaque origin, which is cross-origin with these scripts. Since
+`<script type="module">` respects the same-origin policy, we need CORS headers
+to allow them to be accessed.
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html
new file mode 100644
index 0000000000..7cbd89b943
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A page with COEP set that will respond when asked</title>
+
+<script type="module" src="send-header-page-script.mjs"></script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/coep-frame.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html
new file mode 100644
index 0000000000..45c8d5074d
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>This page helps exhibit a crash bug when window.open()ed (see ../popups-crash.https.html)</title>
+
+<iframe src="https://{{hosts[][www]}}:{{ports[https][0]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py"></iframe>
+<iframe src="https://{{hosts[][www]}}:{{ports[https][0]}}/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py?header=?1"></iframe>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/crashy-popup.sub.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html
new file mode 100644
index 0000000000..537de07b14
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A frame included by a test page</title>
+
+<script>
+window.onmessage = e => {
+ if (e.data.type === "set document.domain") {
+ document.domain = e.data.newValue;
+ e.source.postMessage({ type: "new document.domain", result: document.domain }, "*");
+ }
+};
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html.headers
new file mode 100644
index 0000000000..79a20f30fc
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/frame.html.headers
@@ -0,0 +1 @@
+Origin-Agent-Cluster: ?1
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs
new file mode 100644
index 0000000000..6bad76e3d9
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs
@@ -0,0 +1,390 @@
+/**
+ * Inserts an iframe usable for origin-keyed agent cluster testing, and returns
+ * a promise fulfilled when the iframe is loaded and its document.domain is set.
+ * The iframe will point to the send-oac-header.py file, on the designated
+ * host.
+ * @param {string} host - The host used to calculate the iframe's src=""
+ * @param {string=} header - The value of the Origin-Agent-Cluster header that
+ * the iframe will set. Omit this to set no header.
+ * @param {object=} options - Rarely-used options.
+ * @param {boolean=} options.redirectFirst - Whether to do a 302 redirect first
+ * before arriving at the page that sets the header. The redirecting page will
+ * not set the Origin-Agent-Cluster header.
+ * @returns {HTMLIFrameElement} The created iframe element
+ */
+export async function insertIframe(host, header, { redirectFirst = false } = {}) {
+ const iframe = document.createElement("iframe");
+ const navigatePromise = navigateIframe(iframe, host, header, { redirectFirst });
+ document.body.append(iframe);
+ await navigatePromise;
+ await setBothDocumentDomains(iframe.contentWindow);
+ return iframe;
+}
+
+/**
+ * Navigates an iframe to a page for origin-keyed agent cluster testing, similar
+ * to insertIframe but operating on an existing iframe.
+ * @param {HTMLIFrameElement} iframeEl - The <iframe> element to navigate
+ * @param {string} host - The host to calculate the iframe's new src=""
+ * @param {string=} header - The value of the Origin-Agent-Cluster header that
+ * the newly-navigated-to page will set. Omit this to set no header.
+ * @param {object=} options - Rarely-used options.
+ * @param {boolean=} options.redirectFirst - Whether to do a 302 redirect first
+ * before arriving at the page that sets the header. The redirecting page will
+ * not set the Origin-Agent-Cluster header.
+ * @returns {Promise} a promise fulfilled when the load event fires, or rejected
+ * if the error event fires
+ */
+export function navigateIframe(iframeEl, host, header, { redirectFirst = false } = {}) {
+ const url = getSendHeaderURL(host, header, { redirectFirst });
+
+ const waitPromise = waitForIframe(iframeEl, url);
+ iframeEl.src = url;
+ return waitPromise;
+}
+
+/**
+ * Returns a promise that is fulfilled when an iframe's load event fires, or
+ * rejected when its error event fires.
+ * @param {HTMLIFrameElement} iframeEl - The <iframe> element to wait on
+ * @param {string} destinationForErrorMessage - A string used in the promise
+ * rejection error message, if the error event fires
+ * @returns {Promise} a promise fulfilled when the load event fires, or rejected
+ * if the error event fires
+ */
+export function waitForIframe(iframeEl, destinationForErrorMessage) {
+ return new Promise((resolve, reject) => {
+ iframeEl.addEventListener("load", () => resolve());
+ iframeEl.addEventListener(
+ "error",
+ () => reject(new Error(`Could not navigate to ${destinationForErrorMessage}`))
+ );
+ });
+}
+
+/**
+ * Opens a new window usable for origin-keyed agent cluster testing, and returns
+ * a promise fulfilled when the window is loaded and its document.domain is set.
+ * The window will point to the send-oac-header.py file, on the designated host.
+ *
+ * The opened window will be automatically closed when all the tests complete.
+ * @param {string} host - The host used to calculate the window's URL
+ * @param {string=} header - The value of the Origin-Agent-Cluster header that
+ * the opened window's page will set. Omit this to set no header.
+ * @returns {WindowProxy} The created window
+ */
+export async function openWindow(host, header) {
+ const url = getSendHeaderURL(host, header, { sendLoadedMessage: true });
+ const openedWindow = window.open(url);
+
+ add_completion_callback(() => openedWindow.close());
+
+ const whatHappened = await waitForMessage(openedWindow);
+ assert_equals(whatHappened, "loaded");
+
+ await setBothDocumentDomains(openedWindow);
+
+ return openedWindow;
+}
+
+/**
+ * Expands into a pair of promise_test() calls to ensure that two Windows are in
+ * the same agent cluster, by checking both that we can send a
+ * WebAssembly.Module, and that we can synchronously access the DOM.
+ * @param {Array} testFrames - An array of either the form [self, frameIndex] or
+ * [frameIndex1, frameIndex2], indicating the two Windows under test. E.g.
+ * [self, 0] or [0, 1].
+ * @param {string=} testLabelPrefix - A prefix used in the test names. This can
+ * be omitted if testSameAgentCluster is only used once in a test file.
+ */
+export function testSameAgentCluster(testFrames, testLabelPrefix) {
+ const prefix = testLabelPrefix === undefined ? "" : `${testLabelPrefix}: `;
+
+ if (testFrames[0] === self) {
+ // Between parent and a child at the index given by testFrames[1]
+
+ promise_test(async () => {
+ const frameWindow = frames[testFrames[1]];
+ const frameElement = document.querySelectorAll("iframe")[testFrames[1]];
+
+ // Must not throw
+ frameWindow.document;
+
+ // Must not throw
+ frameWindow.location.href;
+
+ assert_not_equals(frameElement.contentDocument, null, "contentDocument");
+
+ const whatHappened = await accessFrameElement(frameWindow);
+ assert_equals(whatHappened, "frameElement accessed successfully");
+ }, `${prefix}setting document.domain must give sync access`);
+ } else {
+ // Between the two children at the index given by testFrames[0] and
+ // testFrames[1]
+
+ promise_test(async () => {
+ const whatHappened1 = await accessDocumentBetween(testFrames);
+ assert_equals(whatHappened1, "accessed document successfully");
+
+ const whatHappened2 = await accessLocationHrefBetween(testFrames);
+ assert_equals(whatHappened2, "accessed location.href successfully");
+
+ // We don't test contentDocument/frameElement for these because accessing
+ // those via siblings has to go through the parent anyway.
+ }, `${prefix}setting document.domain must give sync access`);
+ }
+}
+
+/**
+ * Expands into a pair of promise_test() calls to ensure that two Windows are in
+ * different agent clusters, by checking both that we cannot send a
+ * WebAssembly.Module, and that we cannot synchronously access the DOM.
+ * @param {Array} testFrames - An array of either the form [self, frameIndex] or
+ * [frameIndex1, frameIndex2], indicating the two Windows under test. E.g.
+ * [self, 0] or [0, 1].
+ * @param {string=} testLabelPrefix - A prefix used in the test names. This can
+ * be omitted if testDifferentAgentClusters is only used once in a test file.
+ */
+export function testDifferentAgentClusters(testFrames, testLabelPrefix) {
+ const prefix = testLabelPrefix === undefined ? "" : `${testLabelPrefix}: `;
+
+ if (testFrames[0] === self) {
+ // Between parent and a child at the index given by testFrames[1]
+
+ promise_test(async () => {
+ // In general, cross-origin sharing of WebAssembly.Module is prohibited,
+ // so if we're in different agent clusters, it's definitely prohibited.
+ // Basic tests for this cross-origin prohibition are elsewhere; we include
+ // these here as an extra check to make sure there's no weird interactions
+ // with Origin-Agent-Cluster.
+ const frameWindow = frames[testFrames[1]];
+ const whatHappened = await sendWasmModule(frameWindow);
+
+ assert_equals(whatHappened, "messageerror");
+ }, `${prefix}messageerror event must occur`);
+
+ promise_test(async () => {
+ const frameWindow = frames[testFrames[1]];
+ const frameElement = document.querySelectorAll("iframe")[testFrames[1]];
+
+ assert_throws_dom("SecurityError", DOMException, () => {
+ frameWindow.document;
+ });
+
+ assert_throws_dom("SecurityError", DOMException, () => {
+ frameWindow.location.href;
+ });
+
+ assert_equals(frameElement.contentDocument, null, "contentDocument");
+
+ const whatHappened = await accessFrameElement(frameWindow);
+ assert_equals(whatHappened, "null");
+ }, `${prefix}setting document.domain must not give sync access`);
+ } else {
+ // Between the two children at the index given by testFrames[0] and
+ // testFrames[1]
+
+ promise_test(async () => {
+ const whatHappened = await sendWasmModuleBetween(testFrames);
+ assert_equals(whatHappened, "messageerror");
+ }, `${prefix}messageerror event must occur`);
+
+ promise_test(async () => {
+ const whatHappened1 = await accessDocumentBetween(testFrames);
+ assert_equals(whatHappened1, "SecurityError");
+
+ const whatHappened2 = await accessLocationHrefBetween(testFrames);
+ assert_equals(whatHappened2, "SecurityError");
+
+ // We don't test contentDocument/frameElement for these because accessing
+ // those via siblings has to go through the parent anyway.
+ }, `${prefix}setting document.domain must not give sync access`);
+ }
+}
+
+/**
+ * Expands into a pair of promise_test() calls to ensure that the given window,
+ * opened by window.open(), is in a different agent cluster from the current
+ * (opener) window.
+ * @param {function} openedWindowGetter - A function that returns the opened
+ * window
+ */
+export function testOpenedWindowIsInADifferentAgentCluster(openedWindowGetter) {
+ promise_test(async () => {
+ const whatHappened = await sendWasmModule(openedWindowGetter());
+
+ assert_equals(whatHappened, "messageerror");
+ }, `messageerror event must occur`);
+
+ promise_test(async () => {
+ assert_throws_dom("SecurityError", DOMException, () => {
+ openedWindowGetter().document;
+ });
+
+ assert_throws_dom("SecurityError", DOMException, () => {
+ openedWindowGetter().location.href;
+ });
+ }, `setting document.domain must not give sync access`);
+}
+
+/**
+ * Expands into a pair of promise_test() calls to ensure that the given window,
+ * opened by window.open(), is in the same agent cluster as the current
+ * (opener) window.
+ * @param {function} openedWindowGetter - A function that returns the opened
+ * window
+ */
+export function testOpenedWindowIsInSameAgentCluster(openedWindowGetter) {
+ promise_test(async () => {
+ const whatHappened = await sendWasmModule(openedWindowGetter());
+
+ assert_equals(whatHappened, "WebAssembly.Module message received");
+ }, `message event must occur`);
+
+ promise_test(async () => {
+ // Must not throw
+ openedWindowGetter().document;
+
+ // Must not throw
+ openedWindowGetter().location.href;
+ }, `setting document.domain must give sync access`);
+}
+
+/**
+ * Creates a promise_test() to check the value of the originAgentCluster getter
+ * in the given testFrame.
+ * @param {Window|number|function} testFrame - Either self, or a frame index to
+ test, or a function that returns a Window to test.
+ * @param {boolean} expected - The expected value for originAgentCluster.
+ * @param {string=} testLabelPrefix - A prefix used in the test names. This can
+ * be omitted if the function is only used once in a test file.
+ */
+export function testGetter(testFrame, expected, testLabelPrefix) {
+ const prefix = testLabelPrefix === undefined ? "" : `${testLabelPrefix}: `;
+
+ promise_test(async () => {
+ if (testFrame === self) {
+ assert_equals(self.originAgentCluster, expected);
+ } else if (typeof testFrame === "number") {
+ const frameWindow = frames[testFrame];
+ const result = await accessOriginAgentCluster(frameWindow);
+ assert_equals(result, expected);
+ } else {
+ assert_equals(typeof testFrame, "function",
+ "testFrame argument must be self, a number, or a function");
+ const result = await accessOriginAgentCluster(testFrame());
+ assert_equals(result, expected);
+ }
+ }, `${prefix}originAgentCluster must equal ${expected}`);
+}
+
+/**
+ * Sends a WebAssembly.Module instance to the given Window, and waits for it to
+ * send back a message indicating whether it got the module or got a
+ * messageerror event. (This relies on the given Window being derived from
+ * insertIframe or navigateIframe.)
+ * @param {Window} frameWindow - The destination Window
+ * @returns {Promise} A promise which will be fulfilled with either
+ * "WebAssembly.Module message received" or "messageerror"
+ */
+export async function sendWasmModule(frameWindow) {
+ // This function is coupled to ./send-oac-header.py, which ensures that
+ // sending such a message will result in a message back.
+ frameWindow.postMessage(await createWasmModule(), "*");
+ return waitForMessage(frameWindow);
+}
+
+/**
+ * Sets document.domain (to itself) for both the current Window and the given
+ * Window. The latter relies on the given Window being derived from insertIframe
+ * or navigateIframe.
+ * @param frameWindow - The other Window whose document.domain is to be set
+ * @returns {Promise} A promise which will be fulfilled after both
+ * document.domains are set
+ */
+export async function setBothDocumentDomains(frameWindow) {
+ // By setting both this page's document.domain and the iframe's
+ // document.domain to the same value, we ensure that they can synchronously
+ // access each other, unless they are origin-keyed.
+ // NOTE: document.domain being unset is different than it being set to its
+ // current value. It is a terrible API.
+ document.domain = document.domain;
+
+ // This function is coupled to ./send-oac-header.py, which ensures that
+ // sending such a message will result in a message back.
+ frameWindow.postMessage({ command: "set document.domain", newDocumentDomain: document.domain }, "*");
+ const whatHappened = await waitForMessage(frameWindow);
+ assert_equals(whatHappened, "document.domain is set");
+}
+
+async function accessOriginAgentCluster(frameWindow) {
+ // This function is coupled to ./send-oac-header.py, which ensures that
+ // sending such a message will result in a message back.
+ frameWindow.postMessage({ command: "get originAgentCluster" }, "*");
+ return waitForMessage(frameWindow);
+}
+
+function getSendHeaderURL(host, header, { sendLoadedMessage = false, redirectFirst = false } = {}) {
+ const url = new URL("send-oac-header.py", import.meta.url);
+ url.host = host;
+ if (header !== undefined) {
+ url.searchParams.set("header", header);
+ }
+ if (sendLoadedMessage) {
+ url.searchParams.set("send-loaded-message", "");
+ }
+ if (redirectFirst) {
+ url.searchParams.set("redirect-first", "");
+ }
+
+ return url.href;
+}
+
+async function sendWasmModuleBetween(testFrames) {
+ const sourceFrame = frames[testFrames[0]];
+ const indexIntoParentFrameOfDestination = testFrames[1];
+
+ sourceFrame.postMessage({ command: "send WASM module", indexIntoParentFrameOfDestination }, "*");
+ return waitForMessage(sourceFrame);
+}
+
+async function accessDocumentBetween(testFrames) {
+ const sourceFrame = frames[testFrames[0]];
+ const indexIntoParentFrameOfDestination = testFrames[1];
+
+ sourceFrame.postMessage({ command: "access document", indexIntoParentFrameOfDestination }, "*");
+ return waitForMessage(sourceFrame);
+}
+
+async function accessLocationHrefBetween(testFrames) {
+ const sourceFrame = frames[testFrames[0]];
+ const indexIntoParentFrameOfDestination = testFrames[1];
+
+ sourceFrame.postMessage({ command: "access location.href", indexIntoParentFrameOfDestination }, "*");
+ return waitForMessage(sourceFrame);
+}
+
+async function accessFrameElement(frameWindow) {
+ frameWindow.postMessage({ command: "access frameElement" }, "*");
+ return waitForMessage(frameWindow);
+}
+
+function waitForMessage(expectedSource) {
+ return new Promise(resolve => {
+ const handler = e => {
+ if (e.source === expectedSource) {
+ resolve(e.data);
+ window.removeEventListener("message", handler);
+ }
+ };
+ window.addEventListener("message", handler);
+ });
+}
+
+// Any WebAssembly.Module will work fine for our tests; we just want to find out
+// if it gives message or messageerror. So, we reuse one from the /wasm/ tests.
+async function createWasmModule() {
+ const response = await fetch("/wasm/serialization/module/resources/incrementer.wasm");
+ const ab = await response.arrayBuffer();
+ return WebAssembly.compile(ab);
+}
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/helpers.mjs.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs
new file mode 100644
index 0000000000..17b00684d6
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs
@@ -0,0 +1,63 @@
+import { sendWasmModule } from "./helpers.mjs";
+
+// This is done for the window.open() case. For <iframe>s we use the
+// <iframe> element's load event instead.
+const usp = new URLSearchParams(location.search);
+if (usp.has("send-loaded-message")) {
+ opener.postMessage("loaded", "*");
+}
+
+window.onmessage = async (e) => {
+ // These could come from the parent, opener, or siblings.
+ if (e.data.constructor === WebAssembly.Module) {
+ e.source.postMessage("WebAssembly.Module message received", "*");
+ }
+
+ // These could come from the parent or opener.
+ if (e.data.command === "set document.domain") {
+ document.domain = e.data.newDocumentDomain;
+ e.source.postMessage("document.domain is set", "*");
+ } else if (e.data.command === "get originAgentCluster") {
+ e.source.postMessage(self.originAgentCluster, "*");
+ }
+
+ // These only come from the parent.
+ if (e.data.command === "send WASM module") {
+ const destinationFrameWindow = parent.frames[e.data.indexIntoParentFrameOfDestination];
+ const whatHappened = await sendWasmModule(destinationFrameWindow);
+ parent.postMessage(whatHappened, "*");
+ } else if (e.data.command === "access document") {
+ const destinationFrameWindow = parent.frames[e.data.indexIntoParentFrameOfDestination];
+ try {
+ destinationFrameWindow.document;
+ parent.postMessage("accessed document successfully", "*");
+ } catch (e) {
+ parent.postMessage(e.name, "*");
+ }
+ } else if (e.data.command === "access location.href") {
+ const destinationFrameWindow = parent.frames[e.data.indexIntoParentFrameOfDestination];
+ try {
+ destinationFrameWindow.location.href;
+ parent.postMessage("accessed location.href successfully", "*");
+ } catch (e) {
+ parent.postMessage(e.name, "*");
+ }
+ } else if (e.data.command === "access frameElement") {
+ if (frameElement === null) {
+ parent.postMessage("null", "*");
+ } else if (frameElement?.constructor?.name === "HTMLIFrameElement") {
+ parent.postMessage("frameElement accessed successfully", "*");
+ } else {
+ parent.postMessage("something wierd happened", "*");
+ }
+ }
+
+ // We could also receive e.data === "WebAssembly.Module message received",
+ // but that's handled by await sendWasmModule() above.
+};
+
+window.onmessageerror = e => {
+ e.source.postMessage("messageerror", "*");
+};
+
+document.body.textContent = location.href;
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs.headers b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-header-page-script.mjs.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py
new file mode 100644
index 0000000000..cc8860fe75
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py
@@ -0,0 +1,43 @@
+def main(request, response):
+ """Send a response with the Origin-Agent-Cluster header given in the
+ "header" query parameter, or no header if that is not provided. Other query
+ parameters (only their presence/absence matters) are "send-loaded-message"
+ and "redirect-first", which modify the behavior a bit.
+
+ In either case, the response will listen for various messages posted and
+ coordinate with the sender. See ./helpers.mjs for how these handlers are
+ used.
+ """
+
+ if b"redirect-first" in request.GET:
+ # Create a new query string, which is the same as the one we're given but
+ # with the redirect-first component stripped out. This allows tests to use
+ # any value (or no value) for the other query params, in combination with
+ # redirect-first.
+ query_string_pieces = []
+ if b"header" in request.GET:
+ query_string_pieces.append(b"header=" + request.GET.first(b"header"))
+ if b"send-loaded-message" in request.GET:
+ query_string_pieces.append(b"send-loaded-message")
+ query_string = b"?" + b"&".join(query_string_pieces)
+
+ return (
+ 302,
+ [(b"Location", b"/html/browsers/origin/origin-keyed-agent-clusters/resources/send-oac-header.py" + query_string)],
+ u""
+ )
+
+ if b"header" in request.GET:
+ header = request.GET.first(b"header")
+ response.headers.set(b"Origin-Agent-Cluster", header)
+
+ response.headers.set(b"Content-Type", b"text/html")
+
+ return u"""
+ <!DOCTYPE html>
+ <meta charset="utf-8">
+ <title>Helper page for origin-keyed agent cluster tests</title>
+
+ <body>
+ <script type="module" src="send-header-page-script.mjs"></script>
+ """
diff --git a/testing/web-platform/tests/html/browsers/origin/origin-of-data-document.html b/testing/web-platform/tests/html/browsers/origin/origin-of-data-document.html
new file mode 100644
index 0000000000..448f47fa24
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/origin-of-data-document.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Origin of document produced from a 'data:' URL</title>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/browsers.html#origin">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ async_test(function (t) {
+ var i = document.createElement('iframe');
+ i.src = "data:text/html,<script>" +
+ " window.parent.postMessage('Hello!', '*');" +
+ "</scr" + "ipt>";
+
+ window.addEventListener("message", t.step_func_done(function (e) {
+ assert_equals(e.origin, "null", "Messages sent from a 'data:' URL should have an opaque origin (which serializes to 'null').");
+ assert_throws_dom("SecurityError", function () {
+ var couldAccessCrossOriginProperty = e.source.location.href;
+ }, "The 'data:' frame should be cross-origin: 'window.location.href'");
+ assert_equals(i.contentDocument, null, "The 'data:' iframe should be unable to access its contentDocument.");
+ }));
+
+ document.body.appendChild(i);
+ }, "The origin of a 'data:' document in a frame is opaque.");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain.html
new file mode 100644
index 0000000000..d3af35c6d7
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+ <head>
+ <title>document.domain's getter</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ test(function() {
+ assert_equals(typeof document.domain, "string", "document.domain is a string");
+ assert_not_equals(document.domain, "", "document.domain is not empty");
+ }, "basics");
+
+ test(function() {
+ assert_equals(document.domain, window.location.hostname, "equals location.hostname");
+ }, "current document");
+
+ test(function() {
+ var doc = new Document();
+ assert_equals(doc.domain, window.location.hostname, "equals location.hostname");
+ }, "new Document()");
+
+ async_test(t => {
+ const client = new XMLHttpRequest();
+ client.open("GET", "/common/blank.html");
+ client.responseType = "document"
+ client.send();
+ client.onload = t.step_func_done(() => {
+ assert_equals(client.response.domain, window.location.hostname);
+ });
+ }, "XMLHttpRequest's response document");
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html
new file mode 100644
index 0000000000..eb02c96f1d
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_access_details.sub.html
@@ -0,0 +1,305 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/document_domain_frame.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "control1-1", "{{domains[www1]}}");
+ let frame2 = await createFrame(t, "control1-2", "{{domains[www1]}}");
+ let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "control1-2" });
+ assert_equals(result.data, "omg!");
+}, "Access allowed if same-origin with no 'document.domain' modification. (Sanity check)");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "control2-1", "{{domains[www1]}}");
+ let frame2 = await createFrame(t, "control2-2", "{{domains[www2]}}");
+ let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "control2-2" });
+ assert_equals(result.data, "SecurityError");
+}, "Access not allowed if different-origin with no 'document.domain' modification. (Sanity check)");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "one-set-one-not-1", "{{domains[www1]}}");
+ let frame2 = await createFrame(t, "one-set-one-not-2", "{{domains[www1]}}");
+ await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+
+ let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "one-set-one-not-2" });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "one-set-one-not-1" });
+ assert_equals(result.data, "SecurityError");
+}, "Access disallowed if same-origin but only one sets document.domain.");
+
+promise_test(async (t) => {
+ var frame1 = await createFrame(t, "both-set-to-existing-1", "{{domains[www1]}}");
+ var frame2 = await createFrame(t, "both-set-to-existing-2", "{{domains[www1]}}");
+ let result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, { 'poke-at-sibling': "both-set-to-existing-2" });
+ assert_equals(result.data, "omg!");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "both-set-to-existing-1" });
+ assert_equals(result.data, "omg!");
+}, "Access allowed if same-origin and both set document.domain to existing value.");
+
+promise_test(async (t) => {
+ var frame1 = await createFrame(t, "both-set-to-parent-1", "{{domains[www1]}}");
+ var frame2 = await createFrame(t, "both-set-to-parent-2", "{{domains[www2]}}");
+ let result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame2, { domain: "{{domains[]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, { 'poke-at-sibling': "both-set-to-parent-2" });
+ assert_equals(result.data, "omg!");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "both-set-to-parent-1" });
+ assert_equals(result.data, "omg!");
+}, "Access allowed if different-origin but both set document.domain to parent domain.");
+
+promise_test(async (t) => {
+ var frame1 = await createFrame(t, "allow-then-revoke-1", "{{domains[www1]}}");
+ var frame2 = await createFrame(t, "allow-then-revoke-2", "{{domains[www1]}}");
+ let result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, { 'poke-at-sibling': "allow-then-revoke-2" });
+ assert_equals(result.data, "omg!");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "allow-then-revoke-1" });
+ assert_equals(result.data, "omg!");
+
+ result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, { 'poke-at-sibling': "allow-then-revoke-2" });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "allow-then-revoke-1" });
+ assert_equals(result.data, "SecurityError");
+}, "Access disallowed again if same-origin, both set document-domain to existing value, then one sets to parent.");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "revoke-Window-1", "{{domains[www1]}}");
+ let frame2 = await createFrame(t, "revoke-Window-2", "{{domains[www1]}}");
+
+ let result = await postMessageToFrame(frame1, { cache: ["parent", "revoke-Window-2"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 1");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "revoke-Window-1"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 1");
+
+ result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "SecurityError");
+}, "Access is revoked to Window object when we stop being same effective script origin due to document.domain.");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "revoke-Location-1", "{{domains[www1]}}");
+ let frame2 = await createFrame(t, "revoke-Location-2", "{{domains[www1]}}");
+
+ let result = await postMessageToFrame(frame1, { cache: ["parent", "revoke-Location-2", "location"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 3");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "revoke-Location-1", "location"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 3");
+
+ result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "SecurityError");
+}, "Access is revoked to Location object when we stop being same effective script origin due to document.domain.");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "no-revoke-Document-1", "{{domains[www1]}}");
+ let frame2 = await createFrame(t, "no-revoke-Document-2", "{{domains[www1]}}");
+
+ let result = await postMessageToFrame(frame1, { cache: ["parent", "no-revoke-Document-2", "document"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 4");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "no-revoke-Document-1", "document"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "Reachable 4");
+
+ result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 4");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "Reachable 4");
+}, "Access is not revoked to Document object when we stop being same effective script origin due to document.domain.");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "no-revoke-object-1", "{{domains[www1]}}");
+ let frame2 = await createFrame(t, "no-revoke-object-2", "{{domains[www1]}}");
+
+ let result = await postMessageToFrame(frame1, { cache: ["parent", "no-revoke-object-2", "bar"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 2");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "no-revoke-object-1", "bar"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 2");
+
+ result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 2");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "Reachable 2");
+}, "Access is not revoked to random object when we stop being same effective script origin due to document.domain.");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "join-and-diverge-1", "{{domains[www2.www1]}}");
+ let frame2 = await createFrame(t, "join-and-diverge-2", "{{domains[www1.www1]}}");
+
+ // Make sure we can't touch each other.
+ let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-2" });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-1" });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-2", "bar"] });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-1", "document"] });
+ assert_equals(result.data, "SecurityError");
+
+ // Let's join up now.
+ result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ // Now we should be able to touch each other.
+ result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-2" });
+ assert_equals(result.data, "omg!");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-1" });
+ assert_equals(result.data, "omg!");
+
+ // Cache a random object and a document.
+ result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-2", "bar"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 2");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-1", "document"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "Reachable 4");
+
+ // OK, now let's diverge
+ result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" });
+ assert_equals(result.data, "Done");
+
+ // We should still be able to touch our cached things.
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 2");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "Reachable 4");
+}, "Access evolves correctly for non-cross-origin objects when we join up via document.domain and then diverge again.");
+
+promise_test(async (t) => {
+ let frame1 = await createFrame(t, "join-and-diverge-cross-origin-1", "{{domains[www2.www1]}}");
+ let frame2 = await createFrame(t, "join-and-diverge-cross-origin-2", "{{domains[www1.www1]}}");
+
+ // Make sure we can't touch each other.
+ let result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-cross-origin-2" });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-cross-origin-1" });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-2", "bar"] });
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-1", "document"] });
+ assert_equals(result.data, "SecurityError");
+
+ // Let's join up now.
+ result = await postMessageToFrame(frame1, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ result = await postMessageToFrame(frame2, { domain: "{{domains[www1]}}" });
+ assert_equals(result.data, "Done");
+
+ // Now we should be able to touch each other.
+ result = await postMessageToFrame(frame1, { 'poke-at-sibling': "join-and-diverge-cross-origin-2" });
+ assert_equals(result.data, "omg!");
+
+ result = await postMessageToFrame(frame2, { 'poke-at-sibling': "join-and-diverge-cross-origin-1" });
+ assert_equals(result.data, "omg!");
+
+ // Cache a window and a location
+ result = await postMessageToFrame(frame1, { cache: ["parent", "join-and-diverge-cross-origin-2"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "Reachable 1");
+
+ result = await postMessageToFrame(frame2, { cache: ["parent", "join-and-diverge-cross-origin-1", "location"] });
+ assert_equals(result.data, "cached");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "Reachable 3");
+
+ // OK, now let's diverge
+ result = await postMessageToFrame(frame1, { domain: "{{domains[]}}" });
+ assert_equals(result.data, "Done");
+
+ // Now our cross-origin objects should start denying access.
+ result = await postMessageToFrame(frame1, 'touch-cached');
+ assert_equals(result.data, "SecurityError");
+
+ result = await postMessageToFrame(frame2, 'touch-cached');
+ assert_equals(result.data, "SecurityError");
+}, "Access evolves correctly for cross-origin objects when we join up via document.domain and then diverge again.");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html
new file mode 100644
index 0000000000..2539528341
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+ <head>
+ <title>document.domain's setter</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <iframe id="iframe"></iframe>
+ <script>
+ var host_info = get_host_info();
+ var HTTP_PORT = host_info.HTTP_PORT;
+ var ORIGINAL_HOST = host_info.ORIGINAL_HOST;
+ var SUFFIX_HOST = ORIGINAL_HOST.substring(ORIGINAL_HOST.lastIndexOf('.') + 1); // e.g. "test"
+ var REMOTE_HOST = host_info.REMOTE_HOST;
+ var iframe = document.getElementById("iframe");
+ var iframe_url = new URL("support/document_domain_setter_iframe.html", document.location);
+ iframe_url.hostname = REMOTE_HOST;
+ iframe.src = iframe_url;
+
+ test(function() {
+ assert_throws_dom("SecurityError", function() { document.domain = SUFFIX_HOST; });
+ assert_throws_dom("SecurityError", function() { document.domain = "." + SUFFIX_HOST; });
+ assert_throws_dom("SecurityError", function() { document.domain = REMOTE_HOST; });
+ assert_throws_dom("SecurityError", function() { document.domain = "example.com"; });
+ }, "failed setting of document.domain");
+
+ async_test(function(t) {
+ iframe.addEventListener("load", t.step_func_done(function() {
+ // Before setting document.domain, the iframe is not
+ // same-origin-domain, so security checks fail.
+ assert_equals(iframe.contentDocument, null);
+ assert_throws_dom("SecurityError", () => iframe.contentWindow.frameElement);
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.origin; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.href; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.protocol; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.host; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.port; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.hostname; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.pathname; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.hash; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.search; });
+ assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.toString(); });
+ // Set document.domain
+ document.domain = ORIGINAL_HOST;
+ // After setting document.domain, the iframe is
+ // same-origin-domain, so security checks pass.
+ assert_equals(iframe.contentDocument.domain, document.domain);
+ assert_equals(iframe.contentWindow.frameElement, iframe);
+ assert_equals(iframe.contentWindow.origin, iframe_url.origin);
+ assert_equals(iframe.contentWindow.location.href, iframe_url.href);
+ assert_equals(iframe.contentWindow.location.protocol, iframe_url.protocol);
+ assert_equals(iframe.contentWindow.location.host, iframe_url.host);
+ assert_equals(iframe.contentWindow.location.port, iframe_url.port);
+ assert_equals(iframe.contentWindow.location.hostname, iframe_url.hostname);
+ assert_equals(iframe.contentWindow.location.pathname, iframe_url.pathname);
+ assert_equals(iframe.contentWindow.location.hash, iframe_url.hash);
+ assert_equals(iframe.contentWindow.location.search, iframe_url.search);
+ assert_equals(iframe.contentWindow.location.search, iframe_url.search);
+ assert_equals(iframe.contentWindow.location.toString(), iframe_url.toString());
+ // document.open checks for same-origin, not same-origin-domain,
+ // https://github.com/whatwg/html/issues/2282
+ assert_throws_dom("SecurityError", iframe.contentWindow.DOMException,
+ function() { iframe.contentDocument.open(); });
+ }));
+ }, "same-origin-domain iframe");
+
+ test(() => {
+ assert_throws_dom("SecurityError", () => { (new Document).domain = document.domain });
+ assert_throws_dom("SecurityError", () => { document.implementation.createHTMLDocument().domain = document.domain });
+ assert_throws_dom("SecurityError", () => { document.implementation.createDocument(null, "").domain = document.domain });
+ }, "failed setting of document.domain for documents without browsing context");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_srcdoc.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_srcdoc.html
new file mode 100644
index 0000000000..65a7f5c898
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter_srcdoc.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<!-- SEKRITS! -->
+<input id="sekrit" value="omg!">
+
+<script>
+ function postMessageToFrame(frame, message) {
+ return new Promise(resolve => {
+ var c = new MessageChannel();
+ c.port1.onmessage = e => {
+ resolve({ data: e.data, frame: frame })
+ };
+ frame.contentWindow.postMessage(message, '*', [c.port2]);
+ });
+ }
+
+ function createFrame() {
+ return new Promise(resolve => {
+ var i = document.createElement('iframe');
+ i.srcdoc = `
+ <script>
+ window.addEventListener('message', e => {
+ if (e.data.domain !== undefined) {
+ try {
+ document.domain = e.data.domain;
+ e.ports[0].postMessage('Done');
+ } catch(error) {
+ e.ports[0].postMessage(error.name);
+ }
+ } else if (e.data == 'poke-at-parent') {
+ try {
+ var sekrit = window.parent.document.body.querySelector('#sekrit').value;
+ e.ports[0].postMessage(sekrit);
+ } catch(error) {
+ e.ports[0].postMessage(error.name);
+ }
+ }
+ });
+ window.parent.postMessage('Hi!', '*');
+ </scr` + `ipt>`;
+ window.addEventListener('message', m => {
+ if (m.source == i.contentWindow)
+ resolve(i);
+ });
+ document.body.appendChild(i);
+ });
+ }
+
+ promise_test(t => {
+ return createFrame()
+ .then(f => postMessageToFrame(f, 'poke-at-parent'))
+ .then(result => {
+ assert_equals(result.data, document.querySelector('#sekrit').value);
+ result.frame.remove();
+ });
+ }, "srcdoc can access with no 'document.domain' modification.");
+
+ promise_test(t => {
+ return createFrame()
+ .then(f => postMessageToFrame(f, { domain: window.location.hostname }))
+ .then(result => {
+ assert_equals(result.data, 'Done');
+ return postMessageToFrame(result.frame, 'poke-at-parent')
+ .then(result => {
+ assert_equals(result.data, document.querySelector('#sekrit').value);
+ result.frame.remove();
+ });
+ });
+ }, "srcdoc can access with valid 'document.domain'.");
+
+ promise_test(t => {
+ return createFrame()
+ .then(f => {
+ document.domain = window.location.hostname;
+ return postMessageToFrame(f, 'poke-at-parent');
+ })
+ .then(result => {
+ assert_equals(result.data, document.querySelector('#sekrit').value);
+ result.frame.remove();
+ });
+ }, "srcdoc can access when parent modifies 'document.domain'.");
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html
new file mode 100644
index 0000000000..ae1a0ccd56
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>Sandboxed document.domain</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(() => {
+ assert_throws_dom("SecurityError", () => { document.domain = document.domain });
+ });
+ test(() => {
+ assert_throws_dom("SecurityError", () => { (new Document).domain = document.domain });
+ });
+ test(() => {
+ assert_throws_dom("SecurityError", () => { document.implementation.createHTMLDocument().domain = document.domain });
+ });
+ test(() => {
+ assert_throws_dom("SecurityError", () => { document.implementation.createDocument(null, "").domain = document.domain });
+ });
+ test(() => {
+ assert_throws_dom("SecurityError", () => { document.createElement("template").content.ownerDocument.domain = document.domain });
+ });
+</script>
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.headers b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.headers
new file mode 100644
index 0000000000..82e8023d0b
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts allow-same-origin
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html
new file mode 100644
index 0000000000..61f54af359
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<script>
+ let cache = window;
+ // "foo" needs to be a var so it's a property on the global.
+ var foo = 'Reachable 1';
+ // "bar" needs to be a var so it's a property on the global.
+ var bar = { foo: 'Reachable 2' };
+ location.foo = 'Reachable 3';
+ document.foo = 'Reachable 4';
+ window.addEventListener('message', e => {
+ if (e.data.domain !== undefined) {
+ try {
+ document.domain = e.data.domain;
+ e.ports[0].postMessage('Done');
+ } catch(error) {
+ e.ports[0].postMessage(error.name);
+ }
+ } else if (e.data['poke-at-sibling'] !== undefined) {
+ try {
+ var sekrit = parent[e.data['poke-at-sibling']].document.body.querySelector('#sekrit').value;
+ e.ports[0].postMessage(sekrit);
+ } catch(error) {
+ e.ports[0].postMessage(error.name);
+ }
+ } else if (e.data.cache != undefined) {
+ let path = e.data.cache;
+ try {
+ while (path.length != 0) {
+ cache = cache[path.shift()];
+ }
+ e.ports[0].postMessage('cached');
+ } catch (error) {
+ e.ports[0].postMessage(error.name);
+ }
+ } else if (e.data == 'touch-cached') {
+ try {
+ e.ports[0].postMessage(cache.foo);
+ } catch (error) {
+ e.ports[0].postMessage(error.name);
+ }
+ } else if (e.data == 'poke-at-parent') {
+ try {
+ var sekrit = window.parent.document.body.querySelector('#sekrit').value;
+ e.ports[0].postMessage(sekrit);
+ } catch(error) {
+ e.ports[0].postMessage(error.name);
+ }
+ }
+ });
+ window.parent.postMessage('Hi!', '*');
+</script>
+<input id="sekrit" value="omg!">
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.sub.js b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.sub.js
new file mode 100644
index 0000000000..b6631ea4f1
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.sub.js
@@ -0,0 +1,65 @@
+/**
+ * Utilities to be used with document_domain_frame.html.
+ */
+
+/**
+ * Send a message to the frame and resolve a promise when a response is received.
+ *
+ * Supported messages:
+ *
+ * 1) { domain: something }. Has the subframe try to set document.domain to the
+ * given value, and message back 'Done' if that succeeds or an error name if it
+ * fails.
+ *
+ * 2) 'poke-at-parent'. Has the subframe try to synchronously attempt to access
+ * the parent's DOM, read out a string value, and message it back to the parent.
+ * Again, sends back the error name if that fails.
+ *
+ * 3) { 'poke-at-sibling': name }. Has the subframe try to synchronously
+ * attempt to access the DOM of the sibling with the given name, read out a
+ * string value, and message it back to the parent.
+ */
+function postMessageToFrame(frame, message) {
+ return new Promise(resolve => {
+ var c = new MessageChannel();
+ c.port1.onmessage = e => {
+ resolve({ data: e.data, frame: frame })
+ };
+ frame.contentWindow.postMessage(message, '*', [c.port2]);
+ });
+}
+
+/**
+ * Create a frame that loads document_domain_frame.html and resolves a promise
+ * when the frame is loaded enough to be sending and receiving messages.
+ *
+ * If a "name" argument is provided, that name is used for the iframe, so
+ *
+ * If a "hostname" argument is provided, that hostname is used for the load, to
+ * allow testing details of the behavior when different sorts of hostnames are
+ * used.
+ */
+function createFrame(t, name, hostname) {
+ return new Promise(resolve => {
+ var i = document.createElement('iframe');
+ if (hostname) {
+ i.src = `//${hostname}:{{location[port]}}/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_frame.html`;
+ } else {
+ i.src = "support/document_domain_frame.html";
+ }
+ if (name) {
+ i.name = name;
+ }
+ var listener = m => {
+ if (m.source == i.contentWindow)
+ resolve(i);
+ }
+ window.addEventListener('message', listener);
+ t.add_cleanup(() => {
+ i.remove();
+ window.removeEventListener('message', listener);
+ });
+ document.body.appendChild(i);
+ });
+}
+
diff --git a/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html
new file mode 100644
index 0000000000..d3d5260af3
--- /dev/null
+++ b/testing/web-platform/tests/html/browsers/origin/relaxing-the-same-origin-restriction/support/document_domain_setter_iframe.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+ <head>
+ <title></title>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script>
+ document.domain = get_host_info().ORIGINAL_HOST;
+ </script>
+ </head>
+ <body>
+ </body>
+</html>