06d96b11372db8958989f835f67b934600ac3c07
[WebKit-https.git] / LayoutTests / imported / w3c / web-platform-tests / html / browsers / origin / cross-origin-objects / cross-origin-objects.html
1 <!doctype html>
2 <meta charset=utf-8>
3 <meta name="timeout" content="long">
4 <title>Cross-origin behavior of Window and Location</title>
5 <link rel="author" title="Bobby Holley (:bholley)" href="bobbyholley@gmail.com">
6 <link rel="help" href="https://html.spec.whatwg.org/multipage/#security-window">
7 <link rel="help" href="https://html.spec.whatwg.org/multipage/#security-location">
8 <script src="/resources/testharness.js"></script>
9 <script src="/resources/testharnessreport.js"></script>
10 <script src="/common/get-host-info.sub.js"></script>
11 <div id=log></div>
12 <iframe id="B"></iframe>
13 <iframe id="C"></iframe>
14 <script>
15
16 /*
17  * Setup boilerplate. This gives us a same-origin window "B" and a cross-origin
18  * window "C".
19  */
20 var host_info = get_host_info();
21
22 setup({explicit_done: true});
23 path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/frame.html';
24 var B = document.getElementById('B').contentWindow;
25 var C = document.getElementById('C').contentWindow;
26 B.frameElement.uriToLoad = path;
27 C.frameElement.uriToLoad = get_host_info().HTTP_REMOTE_ORIGIN + path;
28
29 function reloadSubframes(cb) {
30   var iframes = document.getElementsByTagName('iframe');
31   iframes.forEach = Array.prototype.forEach;
32   var count = 0;
33   function frameLoaded() {
34     this.onload = null;
35     if (++count == iframes.length)
36       cb();
37   }
38   iframes.forEach(function(ifr) { ifr.onload = frameLoaded; ifr.setAttribute('src', ifr.uriToLoad); });
39 }
40 function isObject(x) { return Object(x) === x; }
41
42 /*
43  * Note: we eschew assert_equals in a lot of these tests, since the harness ends
44  * up throwing when it tries to format a message involving a cross-origin object.
45  */
46
47 var testList = [];
48 function addTest(fun, desc) { testList.push([fun, desc]); }
49
50
51 /*
52  * Basic sanity testing.
53  */
54
55 addTest(function() {
56   // Note: we do not check location.host as its default port semantics are hard to reflect statically
57   assert_equals(location.hostname, host_info.ORIGINAL_HOST, 'Need to run the top-level test from domain ' + host_info.ORIGINAL_HOST);
58   assert_equals(get_port(location), host_info.HTTP_PORT, 'Need to run the top-level test from port ' + host_info.HTTP_PORT);
59   assert_equals(B.parent, window, "window.parent works same-origin");
60   assert_equals(C.parent, window, "window.parent works cross-origin");
61   assert_equals(B.location.pathname, path, "location.href works same-origin");
62   assert_throws("SecurityError", function() { C.location.pathname; }, "location.pathname throws cross-origin");
63   assert_equals(B.frames, 'override', "Overrides visible in the same-origin case");
64   assert_equals(C.frames, C, "Overrides invisible in the cross-origin case");
65 }, "Basic sanity-checking");
66
67 /*
68  * Whitelist behavior.
69  *
70  * Also tests for [[GetOwnProperty]] and [[HasOwnProperty]] behavior.
71  */
72
73 var whitelistedWindowIndices = ['0', '1'];
74 var whitelistedWindowPropNames = ['location', 'postMessage', 'window', 'frames', 'self', 'top', 'parent',
75                                   'opener', 'closed', 'close', 'blur', 'focus', 'length'];
76 whitelistedWindowPropNames = whitelistedWindowPropNames.concat(whitelistedWindowIndices);
77 whitelistedWindowPropNames.sort();
78 var whitelistedLocationPropNames = ['href', 'replace'];
79 whitelistedLocationPropNames.sort();
80 var whitelistedSymbols = [Symbol.toStringTag, Symbol.hasInstance,
81                           Symbol.isConcatSpreadable];
82 var whitelistedWindowProps = whitelistedWindowPropNames.concat(whitelistedSymbols);
83
84 addTest(function() {
85   for (var prop in window) {
86     if (whitelistedWindowProps.indexOf(prop) != -1) {
87       C[prop]; // Shouldn't throw.
88       Object.getOwnPropertyDescriptor(C, prop); // Shouldn't throw.
89       assert_true(Object.prototype.hasOwnProperty.call(C, prop), "hasOwnProperty for " + String(prop));
90     } else {
91       assert_throws("SecurityError", function() { C[prop]; }, "Should throw when accessing " + String(prop) + " on Window");
92       assert_throws("SecurityError", function() { Object.getOwnPropertyDescriptor(C, prop); },
93                     "Should throw when accessing property descriptor for " + prop + " on Window");
94       assert_throws("SecurityError", function() { Object.prototype.hasOwnProperty.call(C, prop); },
95                     "Should throw when invoking hasOwnProperty for " + prop + " on Window");
96     }
97     if (prop != 'location')
98       assert_throws("SecurityError", function() { C[prop] = undefined; }, "Should throw when writing to " + prop + " on Window");
99   }
100   for (var prop in location) {
101     if (prop == 'replace') {
102       C.location[prop]; // Shouldn't throw.
103       Object.getOwnPropertyDescriptor(C.location, prop); // Shouldn't throw.
104       assert_true(Object.prototype.hasOwnProperty.call(C.location, prop), "hasOwnProperty for " + prop);
105     }
106     else {
107       assert_throws("SecurityError", function() { C[prop]; }, "Should throw when accessing " + prop + " on Location");
108       assert_throws("SecurityError", function() { Object.getOwnPropertyDescriptor(C, prop); },
109                     "Should throw when accessing property descriptor for " + prop + " on Location");
110       assert_throws("SecurityError", function() { Object.prototype.hasOwnProperty.call(C, prop); },
111                     "Should throw when invoking hasOwnProperty for " + prop + " on Location");
112     }
113     if (prop != 'href')
114       assert_throws("SecurityError", function() { C[prop] = undefined; }, "Should throw when writing to " + prop + " on Location");
115   }
116 }, "Only whitelisted properties are accessible cross-origin");
117
118 /*
119  * ES Internal Methods.
120  */
121
122 /*
123  * [[GetPrototypeOf]]
124  */
125 addTest(function() {
126   assert_true(Object.getPrototypeOf(C) === null, "cross-origin Window proto is null");
127   assert_true(Object.getPrototypeOf(C.location) === null, "cross-origin Location proto is null (__proto__)");
128   var protoGetter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').get;
129   assert_true(protoGetter.call(C) === null, "cross-origin Window proto is null");
130   assert_true(protoGetter.call(C.location) === null, "cross-origin Location proto is null (__proto__)");
131   assert_throws("SecurityError", function() { C.__proto__; }, "__proto__ property not available cross-origin");
132   assert_throws("SecurityError", function() { C.location.__proto__; }, "__proto__ property not available cross-origin");
133
134 }, "[[GetPrototypeOf]] should return null");
135
136 /*
137  * [[SetPrototypeOf]]
138  */
139 addTest(function() {
140   assert_throws("SecurityError", function() { C.__proto__ = new Object(); }, "proto set on cross-origin Window");
141   assert_throws("SecurityError", function() { C.location.__proto__ = new Object(); }, "proto set on cross-origin Location");
142   var setters = [Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set];
143   if (Object.setPrototypeOf)
144     setters.push(function(p) { Object.setPrototypeOf(this, p); });
145   setters.forEach(function(protoSetter) {
146     assert_throws(new TypeError, function() { protoSetter.call(C, new Object()); }, "proto setter |call| on cross-origin Window");
147     assert_throws(new TypeError, function() { protoSetter.call(C.location, new Object()); }, "proto setter |call| on cross-origin Location");
148   });
149   if (Reflect.setPrototypeOf) {
150     assert_false(Reflect.setPrototypeOf(C, new Object()),
151                  "Reflect.setPrototypeOf on cross-origin Window");
152     assert_false(Reflect.setPrototypeOf(C.location, new Object()),
153                 "Reflect.setPrototypeOf on cross-origin Location");
154   }
155 }, "[[SetPrototypeOf]] should return false");
156
157 /*
158  * [[IsExtensible]]
159  */
160 addTest(function() {
161   assert_true(Object.isExtensible(C), "cross-origin Window should be extensible");
162   assert_true(Object.isExtensible(C.location), "cross-origin Location should be extensible");
163 }, "[[IsExtensible]] should return true for cross-origin objects");
164
165 /*
166  * [[PreventExtensions]]
167  */
168 addTest(function() {
169   assert_throws(new TypeError, function() { Object.preventExtensions(C) },
170                 "preventExtensions on cross-origin Window should throw");
171   assert_throws(new TypeError, function() { Object.preventExtensions(C.location) },
172                 "preventExtensions on cross-origin Location should throw");
173 }, "[[PreventExtensions]] should throw for cross-origin objects");
174
175 /*
176  * [[GetOwnProperty]]
177  */
178
179 addTest(function() {
180   assert_true(isObject(Object.getOwnPropertyDescriptor(C, 'close')), "C.close is |own|");
181   assert_true(isObject(Object.getOwnPropertyDescriptor(C, 'top')), "C.top is |own|");
182   assert_true(isObject(Object.getOwnPropertyDescriptor(C.location, 'href')), "C.location.href is |own|");
183   assert_true(isObject(Object.getOwnPropertyDescriptor(C.location, 'replace')), "C.location.replace is |own|");
184 }, "[[GetOwnProperty]] - Properties on cross-origin objects should be reported |own|");
185
186 function checkPropertyDescriptor(desc, propName, expectWritable) {
187   var isSymbol = (typeof(propName) == "symbol");
188   propName = String(propName);
189   assert_true(isObject(desc), "property descriptor for " + propName + " should exist");
190   assert_equals(desc.configurable, true, "property descriptor for " + propName + " should be configurable");
191   if (isSymbol) {
192     assert_equals(desc.enumerable, false, "symbol-property descriptor for " + propName + " should not be enumerable");
193     assert_true("value" in desc,
194                 "property descriptor for " + propName + " should be a value descriptor");
195     assert_equals(desc.value, undefined,
196                   "symbol-named cross-origin visible prop " + propName +
197                   " should come back as undefined");
198   } else {
199     assert_equals(desc.enumerable, true, "property descriptor for " + propName + " should be enumerable");
200   }
201   if ('value' in desc)
202     assert_equals(desc.writable, expectWritable, "property descriptor for " + propName + " should have writable: " + expectWritable);
203   else
204     assert_equals(typeof desc.set != 'undefined', expectWritable,
205                   "property descriptor for " + propName + " should " + (expectWritable ? "" : "not ") + "have setter");
206 }
207
208 addTest(function() {
209   whitelistedWindowProps.forEach(function(prop) {
210     var desc = Object.getOwnPropertyDescriptor(C, prop);
211     checkPropertyDescriptor(desc, prop, prop == 'location');
212   });
213   checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'replace'), 'replace', false);
214   checkPropertyDescriptor(Object.getOwnPropertyDescriptor(C.location, 'href'), 'href', true);
215   assert_equals(typeof Object.getOwnPropertyDescriptor(C.location, 'href').get, 'undefined', "Cross-origin location should have no href getter");
216   whitelistedSymbols.forEach(function(prop) {
217     var desc = Object.getOwnPropertyDescriptor(C.location, prop);
218     checkPropertyDescriptor(desc, prop, false);
219   });
220 }, "[[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly");
221
222 /*
223  * [[Delete]]
224  */
225 addTest(function() {
226   assert_throws("SecurityError", function() { delete C[0]; }, "Can't delete cross-origin indexed property");
227   assert_throws("SecurityError", function() { delete C[100]; }, "Can't delete cross-origin indexed property");
228   assert_throws("SecurityError", function() { delete C.location; }, "Can't delete cross-origin property");
229   assert_throws("SecurityError", function() { delete C.parent; }, "Can't delete cross-origin property");
230   assert_throws("SecurityError", function() { delete C.length; }, "Can't delete cross-origin property");
231   assert_throws("SecurityError", function() { delete C.document; }, "Can't delete cross-origin property");
232   assert_throws("SecurityError", function() { delete C.foopy; }, "Can't delete cross-origin property");
233   assert_throws("SecurityError", function() { delete C.location.href; }, "Can't delete cross-origin property");
234   assert_throws("SecurityError", function() { delete C.location.replace; }, "Can't delete cross-origin property");
235   assert_throws("SecurityError", function() { delete C.location.port; }, "Can't delete cross-origin property");
236   assert_throws("SecurityError", function() { delete C.location.foopy; }, "Can't delete cross-origin property");
237 }, "[[Delete]] Should throw on cross-origin objects");
238
239 /*
240  * [[DefineOwnProperty]]
241  */
242 function checkDefine(obj, prop) {
243   var valueDesc = { configurable: true, enumerable: false, writable: false, value: 2 };
244   var accessorDesc = { configurable: true, enumerable: false, get: function() {} };
245   assert_throws("SecurityError", function() { Object.defineProperty(obj, prop, valueDesc); }, "Can't define cross-origin value property " + prop);
246   assert_throws("SecurityError", function() { Object.defineProperty(obj, prop, accessorDesc); }, "Can't define cross-origin accessor property " + prop);
247 }
248 addTest(function() {
249   checkDefine(C, 'length');
250   checkDefine(C, 'parent');
251   checkDefine(C, 'location');
252   checkDefine(C, 'document');
253   checkDefine(C, 'foopy');
254   checkDefine(C.location, 'href');
255   checkDefine(C.location, 'replace');
256   checkDefine(C.location, 'port');
257   checkDefine(C.location, 'foopy');
258 }, "[[DefineOwnProperty]] Should throw for cross-origin objects");
259
260 /*
261  * EnumerateObjectProperties (backed by [[OwnPropertyKeys]])
262  */
263
264 addTest(function() {
265   let i = 0;
266   for (var prop in C) {
267     i++;
268     assert_true(whitelistedWindowPropNames.includes(prop), prop + " is not safelisted for a cross-origin Window");
269   }
270   assert_equals(i, whitelistedWindowPropNames.length, "Enumerate all safelisted cross-origin Window properties");
271   i = 0;
272   for (var prop in C.location) {
273     i++;
274     assert_true(whitelistedLocationPropNames.includes(prop), prop + " is not safelisted for a cross-origin Location");
275   }
276   assert_equals(i, whitelistedLocationPropNames.length, "Enumerate all safelisted cross-origin Location properties");
277 }, "Can only enumerate safelisted properties");
278
279 /*
280  * [[OwnPropertyKeys]]
281  */
282
283 addTest(function() {
284   assert_array_equals(Object.getOwnPropertyNames(C).sort(),
285                       whitelistedWindowPropNames,
286                       "Object.getOwnPropertyNames() gives the right answer for cross-origin Window");
287   assert_array_equals(Object.keys(C).sort(),
288                       whitelistedWindowPropNames,
289                       "Object.keys() gives the right answer for cross-origin Window");
290   assert_array_equals(Object.getOwnPropertyNames(C.location).sort(),
291                       whitelistedLocationPropNames,
292                       "Object.getOwnPropertyNames() gives the right answer for cross-origin Location");
293   assert_array_equals(Object.keys(C.location).sort(),
294                       whitelistedLocationPropNames,
295                       "Object.keys() gives the right answer for cross-origin Location");
296 }, "[[OwnPropertyKeys]] should return all properties from cross-origin objects");
297
298 addTest(function() {
299   assert_array_equals(Object.getOwnPropertySymbols(C), whitelistedSymbols,
300     "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Window");
301   assert_array_equals(Object.getOwnPropertySymbols(C.location),
302                       whitelistedSymbols,
303     "Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Location");
304 }, "[[OwnPropertyKeys]] should return the right symbol-named properties for cross-origin objects");
305
306 addTest(function() {
307   var allWindowProps = Reflect.ownKeys(C);
308   indexedWindowProps = allWindowProps.slice(0, whitelistedWindowIndices.length);
309   stringWindowProps = allWindowProps.slice(0, -1 * whitelistedSymbols.length);
310   symbolWindowProps = allWindowProps.slice(-1 * whitelistedSymbols.length);
311   assert_array_equals(indexedWindowProps, whitelistedWindowIndices,
312                       "Reflect.ownKeys should start with the indices exposed on the cross-origin window.");
313   assert_array_equals(stringWindowProps.sort(), whitelistedWindowPropNames,
314                       "Reflect.ownKeys should continue with the cross-origin window properties for a cross-origin Window.");
315   assert_array_equals(symbolWindowProps, whitelistedSymbols,
316                       "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Window.");
317
318   var allLocationProps = Reflect.ownKeys(C.location);
319   stringLocationProps = allLocationProps.slice(0, -1 * whitelistedSymbols.length);
320   symbolLocationProps = allLocationProps.slice(-1 * whitelistedSymbols.length);
321   assert_array_equals(stringLocationProps.sort(), whitelistedLocationPropNames,
322                       "Reflect.ownKeys should start with the cross-origin window properties for a cross-origin Location.")
323   assert_array_equals(symbolLocationProps, whitelistedSymbols,
324                       "Reflect.ownKeys should end with the cross-origin symbols for a cross-origin Location.")
325 }, "[[OwnPropertyKeys]] should place the symbols after the property names after the subframe indices");
326
327 addTest(function() {
328   assert_true(B.eval('parent.C') === C, "A and B observe the same identity for C's Window");
329   assert_true(B.eval('parent.C.location') === C.location, "A and B observe the same identity for C's Location");
330 }, "A and B jointly observe the same identity for cross-origin Window and Location");
331
332 function checkFunction(f, proto) {
333   var name = f.name || '<missing name>';
334   assert_equals(typeof f, 'function', name + " is a function");
335   assert_equals(Object.getPrototypeOf(f), proto, f.name + " has the right prototype");
336 }
337
338 addTest(function() {
339   checkFunction(C.close, Function.prototype);
340   checkFunction(C.location.replace, Function.prototype);
341 }, "Cross-origin functions get local Function.prototype");
342
343 addTest(function() {
344   assert_true(isObject(Object.getOwnPropertyDescriptor(C, 'parent')),
345               "Need to be able to use Object.getOwnPropertyDescriptor do this test");
346   checkFunction(Object.getOwnPropertyDescriptor(C, 'parent').get, Function.prototype);
347   checkFunction(Object.getOwnPropertyDescriptor(C.location, 'href').set, Function.prototype);
348 }, "Cross-origin Window accessors get local Function.prototype");
349
350 addTest(function() {
351   checkFunction(close, Function.prototype);
352   assert_true(close != B.close, 'same-origin Window functions get their own object');
353   assert_true(close != C.close, 'cross-origin Window functions get their own object');
354   var close_B = B.eval('parent.C.close');
355   assert_true(close != close_B, 'close_B is unique when viewed by the parent');
356   assert_true(close_B != C.close, 'different Window functions per-incumbent script settings object');
357   checkFunction(close_B, B.Function.prototype);
358
359   checkFunction(location.replace, Function.prototype);
360   assert_true(location.replace != C.location.replace, "cross-origin Location functions get their own object");
361   var replace_B = B.eval('parent.C.location.replace');
362   assert_true(replace_B != C.location.replace, 'different Location functions per-incumbent script settings object');
363   checkFunction(replace_B, B.Function.prototype);
364 }, "Same-origin observers get different functions for cross-origin objects");
365
366 addTest(function() {
367   assert_true(isObject(Object.getOwnPropertyDescriptor(C, 'parent')),
368               "Need to be able to use Object.getOwnPropertyDescriptor do this test");
369   var get_self_parent = Object.getOwnPropertyDescriptor(window, 'parent').get;
370   var get_parent_A = Object.getOwnPropertyDescriptor(C, 'parent').get;
371   var get_parent_B = B.eval('Object.getOwnPropertyDescriptor(parent.C, "parent").get');
372   assert_true(get_self_parent != get_parent_A, 'different Window accessors per-incumbent script settings object');
373   assert_true(get_parent_A != get_parent_B, 'different Window accessors per-incumbent script settings object');
374   checkFunction(get_self_parent, Function.prototype);
375   checkFunction(get_parent_A, Function.prototype);
376   checkFunction(get_parent_B, B.Function.prototype);
377 }, "Same-origin observers get different accessors for cross-origin Window");
378
379 addTest(function() {
380   var set_self_href = Object.getOwnPropertyDescriptor(window.location, 'href').set;
381   var set_href_A = Object.getOwnPropertyDescriptor(C.location, 'href').set;
382   var set_href_B = B.eval('Object.getOwnPropertyDescriptor(parent.C.location, "href").set');
383   assert_true(set_self_href != set_href_A, 'different Location accessors per-incumbent script settings object');
384   assert_true(set_href_A != set_href_B, 'different Location accessors per-incumbent script settings object');
385   checkFunction(set_self_href, Function.prototype);
386   checkFunction(set_href_A, Function.prototype);
387   checkFunction(set_href_B, B.Function.prototype);
388 }, "Same-origin observers get different accessors for cross-origin Location");
389
390 addTest(function() {
391   assert_equals({}.toString.call(C), "[object Object]");
392   assert_equals({}.toString.call(C.location), "[object Object]");
393 }, "{}.toString.call() does the right thing on cross-origin objects");
394
395 // We do a fresh load of the subframes for each test to minimize side-effects.
396 // It would be nice to reload ourselves as well, but we can't do that without
397 // disrupting the test harness.
398 function runNextTest() {
399   var entry = testList.shift();
400   test(() => entry[0](), entry[1])
401   if (testList.length != 0)
402     reloadSubframes(runNextTest);
403   else
404     done();
405 }
406 reloadSubframes(runNextTest);
407
408 </script>