summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/browsers/origin/cross-origin-objects
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/browsers/origin/cross-origin-objects')
-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
13 files changed, 1248 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>