Add initial support for 'Cross-Origin-Options' HTTP response header
[WebKit-https.git] / LayoutTests / http / wpt / cross-origin-options / resources / utils.js
1 const RESOURCES_DIR = "/WebKit/cross-origin-options/resources/";
2
3 function isCrossOriginWindow(w)
4 {
5     try {
6         w.name;
7     } catch (e) {
8         return true;
9     }
10     return false;
11 }
12
13 async function waitForCrossOriginLoad(w)
14 {
15     return new Promise((resolve) => {
16         window.addEventListener('message', (msg) => {
17             if (msg.source != w || msg.data != "READY")
18                 return;
19             resolve();
20         });
21
22         let handle = setInterval(() => {
23             if (isCrossOriginWindow(w)) {
24                 clearInterval(handle);
25                 try {
26                     w.postMessage;
27                 } catch (e) {
28                     // No point in waiting for "READY" message from window since postMessage is
29                     // not available.
30                     resolve();
31                 }
32             }
33         }, 5);
34     });
35 }
36
37 async function withIframe(resourceFile, crossOrigin)
38 {
39     return new Promise((resolve) => {
40         let resourceURL = crossOrigin ? get_host_info().HTTP_REMOTE_ORIGIN : get_host_info().HTTP_ORIGIN;
41         resourceURL += RESOURCES_DIR;
42         resourceURL += resourceFile;
43         let frame = document.createElement("iframe");
44         frame.src = resourceURL;
45         if (crossOrigin) {
46             document.body.appendChild(frame);
47             waitForCrossOriginLoad(frame.contentWindow).then(() => {
48                 resolve(frame);
49             });
50         } else {
51             frame.onload = function() { resolve(frame); };
52             document.body.appendChild(frame);
53         }
54     });
55 }
56
57 async function withPopup(resourceFile, crossOrigin)
58 {
59     return new Promise((resolve) => { 
60         let resourceURL = crossOrigin ? get_host_info().HTTP_REMOTE_ORIGIN : get_host_info().HTTP_ORIGIN;
61         resourceURL += RESOURCES_DIR;
62         resourceURL += resourceFile;
63
64         let w = open(resourceURL);
65         if (crossOrigin) {
66             waitForCrossOriginLoad(w).then(() => {
67                 resolve({ 'window': w });
68             });
69         } else {
70             w.onload = function() { resolve({ 'window': w }); };
71         }
72    });
73 }
74
75 const crossOriginPropertyNames = [ 'blur', 'close', 'closed', 'focus', 'frames', 'length', 'location', 'opener', 'parent', 'postMessage', 'self', 'top', 'window' ];
76 const forbiddenPropertiesCrossOrigin = ["name", "document", "history", "locationbar", "status", "frameElement", "navigator", "alert", "localStorage", "sessionStorage", "event", "foo", "bar"];
77
78 function assert_not_throwing(f, message)
79 {
80     try {
81         f();
82     } catch (e) {
83         assert_unreached(message);
84     }
85 }
86
87 function checkCrossOriginPropertiesAccess(w)
88 {
89     for (let crossOriginPropertyName of crossOriginPropertyNames)
90        assert_not_throwing(function() { w[crossOriginPropertyName]; }, "Accessing property '" + crossOriginPropertyName +"' on Window should not throw");
91    
92     assert_false(w.closed, "'closed' property value"); 
93     assert_equals(w.frames, w, "'frames' property value");
94     assert_equals(w.self, w, "'self' property value");
95     assert_equals(w.window, w, "'window' property value");
96
97     assert_not_throwing(function() { w.blur(); }, "Calling blur() on Window should should throw");
98     assert_not_throwing(function() { w.focus(); }, "Calling focus() on Window should should throw");
99     assert_not_throwing(function() { w.postMessage('test', '*'); }, "Calling postMessage() on Window should should throw");
100 }
101
102 function testCrossOriginOption(w, headerValue, isCrossOrigin)
103 {
104     if (!isCrossOrigin) {
105         checkCrossOriginPropertiesAccess(w);
106         for (let forbiddenPropertyCrossOrigin of forbiddenPropertiesCrossOrigin)
107             assert_not_throwing(function() { eval("w." + forbiddenPropertyCrossOrigin); }, "Accessing property '" + forbiddenPropertyCrossOrigin + "' on Window should not throw");
108         assert_not_throwing(function() { w.foo = 1; }, "Setting expando property should not throw");
109         assert_equals(w.foo, 1, "expando property value");
110         return;
111     }
112
113     // Cross-origin case.
114     for (let forbiddenPropertyCrossOrigin of forbiddenPropertiesCrossOrigin) {
115         assert_throws("SecurityError", function() { eval("w." + forbiddenPropertyCrossOrigin); }, "Accessing property '" + forbiddenPropertyCrossOrigin + "' on Window should throw");
116         let desc = Object.getOwnPropertyDescriptor(window, forbiddenPropertyCrossOrigin);
117         if (desc && desc.value)
118             assert_throws("SecurityError", function() { desc.value.call(w); }, "Calling function '" + forbiddenPropertyCrossOrigin + "' on Window should throw (using getter from other window)");
119         else if (desc && desc.get)
120             assert_throws("SecurityError", function() { desc.get.call(w); }, "Accessing property '" + forbiddenPropertyCrossOrigin + "' on Window should throw (using getter from other window)");
121     }
122     assert_throws("SecurityError", function() { w.foo = 1; }, "Setting an expando property should throw");
123
124     if (headerValue == "deny" || headerValue == "allow-postmessage") {
125         for (let crossOriginPropertyName of crossOriginPropertyNames) {
126             if (headerValue == "allow-postmessage" && crossOriginPropertyName == "postMessage") {
127                 assert_not_throwing(function() { w[crossOriginPropertyName]; }, "Accessing property '" + crossOriginPropertyName +"' on Window should not throw");
128             } else {
129                 assert_throws("SecurityError", function() { w[crossOriginPropertyName]; }, "Accessing '" + crossOriginPropertyName + "' property");
130
131                 let desc = Object.getOwnPropertyDescriptor(window, crossOriginPropertyName);
132                 if (desc && desc.value)
133                     assert_throws("SecurityError", function() { desc.value.call(w); }, "Calling function '" + crossOriginPropertyName + "' on Window should throw (using getter from other window)");
134                 else if (desc && desc.get)
135                     assert_throws("SecurityError", function() { desc.get.call(w); }, "Accessing property '" + crossOriginPropertyName + "' on Window should throw (using getter from other window)");
136             }
137         }
138         if (headerValue == "allow-postmessage") {
139             assert_not_throwing(function() { w.postMessage('test', '*'); }, "Calling postMessage() on Window should not throw");
140             assert_not_throwing(function() { Object.getOwnPropertyDescriptor(window, 'postMessage').value.call(w, 'test', '*'); }, "Calling postMessage() on Window should not throw (using getter from other window)");
141         }
142
143         assert_array_equals(Object.getOwnPropertyNames(w).sort(), headerValue == "allow-postmessage" ? ['postMessage'] : [], "Object.getOwnPropertyNames()");
144
145         return;
146     }
147
148     assert_array_equals(Object.getOwnPropertyNames(w).sort(), crossOriginPropertyNames.sort(), "Object.getOwnPropertyNames()");
149     checkCrossOriginPropertiesAccess(w);
150 }