| <!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. |
| Object.getOwnPropertyDescriptor(win, prop); // Shouldn't throw. |
| 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"); |
| }, "[[PreventExtensions]] should throw for 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 [LenientThis] 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 [LenientThis] 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"); |
| }, "LenientThis 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> |