Add initial support for 'Cross-Origin-Options' HTTP response header
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 May 2018 00:33:37 +0000 (00:33 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 May 2018 00:33:37 +0000 (00:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=184996
<rdar://problem/39664620>

Reviewed by Geoff Garen.

Source/WebCore:

Add initial support for 'Cross-Origin-Options' HTTP response header behind an experimental
feature flag, on by default. When the HTTP server services this HTTP response header for a
main resource, we'll set these options on the corresponding Document. This will impact the
behavior of the Document's associated Window API when cross-origin.

The HTTP header has 3 possible values:
- allow: This is the default. Regular cross-origin Window API is available.
- allow-postmessage: Only postMessage() is available on a cross-origin window, trying to
  access anything else will throw a SecurityError.
- deny: Trying to do anything with a cross-origin window will throw a SecurityError.

The header has no effect when accessing same origin windows.

Note that on cross-origin access from Window A to Window B, we check the cross-origin
options for both Window A and Window B and use the lowest common denominator as effective
cross-origin options for the access. So if Window A has 'Cross-Origin-Options: deny' and
tries to call postMessage() on Window B which has 'Cross-Origin-Options: allow-postmessage',
we will throw a SecurityError. This is because Window A's more restrictive options (deny)
apply.

Tests: http/wpt/cross-origin-options/allow-postmessage-from-deny.html
       http/wpt/cross-origin-options/allow-postmessage.html
       http/wpt/cross-origin-options/cross-origin-options-header.html

* bindings/js/JSDOMBindingSecurity.cpp:
(WebCore::BindingSecurity::shouldAllowAccessToDOMWindowGivenMinimumCrossOriginOptions):
* bindings/js/JSDOMBindingSecurity.h:
* bindings/js/JSDOMWindowCustom.cpp:
(WebCore::effectiveCrossOriginOptionsForAccess):
(WebCore::jsDOMWindowGetOwnPropertySlotRestrictedAccess):
(WebCore::JSDOMWindow::getOwnPropertySlot):
(WebCore::JSDOMWindow::getOwnPropertySlotByIndex):
(WebCore::addCrossOriginWindowPropertyNames):
(WebCore::addScopedChildrenIndexes):
(WebCore::addCrossOriginWindowOwnPropertyNames):
(WebCore::JSDOMWindow::getOwnPropertyNames):
* bindings/js/JSDOMWindowCustom.h:
* bindings/js/JSRemoteDOMWindowCustom.cpp:
(WebCore::JSRemoteDOMWindow::getOwnPropertySlot):
(WebCore::JSRemoteDOMWindow::getOwnPropertySlotByIndex):
(WebCore::JSRemoteDOMWindow::getOwnPropertyNames):
* bindings/scripts/CodeGeneratorJS.pm:
(GenerateAttributeGetterBodyDefinition):
(GetCrossOriginsOptionsFromExtendedAttributeValue):
(GenerateAttributeSetterBodyDefinition):
(GenerateOperationBodyDefinition):
* bindings/scripts/IDLAttributes.json:
* dom/Document.cpp:
(WebCore::Document::setCrossOriginOptions):
* dom/Document.h:
(WebCore::Document::crossOriginOptions const):
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::didBeginDocument):
* page/AbstractDOMWindow.cpp:
(WebCore::AbstractDOMWindow::AbstractDOMWindow):
* page/AbstractDOMWindow.h:
(WebCore::AbstractDOMWindow::crossOriginOptions):
(WebCore::AbstractDOMWindow::setCrossOriginOptions):
* page/DOMWindow.cpp:
(WebCore::DOMWindow::DOMWindow):
(WebCore::DOMWindow::didSecureTransitionTo):
* page/DOMWindow.idl:
* page/Frame.h:
* page/RemoteDOMWindow.cpp:
(WebCore::RemoteDOMWindow::RemoteDOMWindow):
* page/RemoteDOMWindow.h:
* page/Settings.yaml:
* platform/network/HTTPHeaderNames.in:
* platform/network/HTTPParsers.cpp:
(WebCore::parseCrossOriginOptionsHeader):
* platform/network/HTTPParsers.h:

Source/WebKit:

* Shared/WebPreferences.yaml:
Add this as an experimental feature, on by default.

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::frameBecameRemote):
Make sure we pass the cross-origin options from the local Window
to the remote one when transitioning.

LayoutTests:

Add layout test coverage.

* http/wpt/cross-origin-options/allow-postmessage-expected.txt: Added.
* http/wpt/cross-origin-options/allow-postmessage-from-deny-expected.txt: Added.
* http/wpt/cross-origin-options/allow-postmessage-from-deny.html: Added.
* http/wpt/cross-origin-options/allow-postmessage-from-deny.html.headers: Added.
* http/wpt/cross-origin-options/allow-postmessage.html: Added.
* http/wpt/cross-origin-options/cross-origin-options-header-expected.txt: Added.
* http/wpt/cross-origin-options/cross-origin-options-header.html: Added.
* http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html: Added.
* http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html.headers: Added.
* http/wpt/cross-origin-options/resources/serve-cross-origin-options-header.py: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@231622 268f45cc-cd09-0410-ab3c-d52691b4dbfc

48 files changed:
LayoutTests/ChangeLog
LayoutTests/http/wpt/cross-origin-options/allow-postmessage-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny.html [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny.html.headers [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/allow-postmessage.html [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/cross-origin-options-header-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/cross-origin-options-header.html [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html.headers [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/resources/serve-cross-origin-options-header.py [new file with mode: 0644]
LayoutTests/http/wpt/cross-origin-options/resources/utils.js [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSDOMBindingSecurity.cpp
Source/WebCore/bindings/js/JSDOMBindingSecurity.h
Source/WebCore/bindings/js/JSDOMWindowCustom.cpp
Source/WebCore/bindings/js/JSDOMWindowCustom.h
Source/WebCore/bindings/js/JSRemoteDOMWindowCustom.cpp
Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
Source/WebCore/bindings/scripts/IDLAttributes.json
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/page/AbstractDOMWindow.cpp
Source/WebCore/page/AbstractDOMWindow.h
Source/WebCore/page/DOMWindow.cpp
Source/WebCore/page/DOMWindow.idl
Source/WebCore/page/Frame.h
Source/WebCore/page/RemoteDOMWindow.cpp
Source/WebCore/page/RemoteDOMWindow.h
Source/WebCore/page/Settings.yaml
Source/WebCore/platform/network/HTTPHeaderNames.in
Source/WebCore/platform/network/HTTPParsers.cpp
Source/WebCore/platform/network/HTTPParsers.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKitLegacy/mac/WebView/WebPreferenceKeysPrivate.h
Source/WebKitLegacy/mac/WebView/WebPreferences.mm
Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h
Source/WebKitLegacy/mac/WebView/WebView.mm
Source/WebKitLegacy/win/Interfaces/IWebPreferencesPrivate.idl
Source/WebKitLegacy/win/WebPreferenceKeysPrivate.h
Source/WebKitLegacy/win/WebPreferences.cpp
Source/WebKitLegacy/win/WebPreferences.h
Source/WebKitLegacy/win/WebView.cpp
Tools/DumpRenderTree/mac/DumpRenderTree.mm
Tools/DumpRenderTree/win/DumpRenderTree.cpp

index d7e0571..7fcb698 100644 (file)
@@ -1,3 +1,24 @@
+2018-05-09  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for 'Cross-Origin-Options' HTTP response header
+        https://bugs.webkit.org/show_bug.cgi?id=184996
+        <rdar://problem/39664620>
+
+        Reviewed by Geoff Garen.
+
+        Add layout test coverage.
+
+        * http/wpt/cross-origin-options/allow-postmessage-expected.txt: Added.
+        * http/wpt/cross-origin-options/allow-postmessage-from-deny-expected.txt: Added.
+        * http/wpt/cross-origin-options/allow-postmessage-from-deny.html: Added.
+        * http/wpt/cross-origin-options/allow-postmessage-from-deny.html.headers: Added.
+        * http/wpt/cross-origin-options/allow-postmessage.html: Added.
+        * http/wpt/cross-origin-options/cross-origin-options-header-expected.txt: Added.
+        * http/wpt/cross-origin-options/cross-origin-options-header.html: Added.
+        * http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html: Added.
+        * http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html.headers: Added.
+        * http/wpt/cross-origin-options/resources/serve-cross-origin-options-header.py: Added.
+
 2018-05-09  Ryosuke Niwa  <rniwa@webkit.org>
 
         Release assert in TreeScopeOrderedMap::remove via HTMLImageElement::removedFromAncestor
diff --git a/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-expected.txt b/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-expected.txt
new file mode 100644 (file)
index 0000000..0e99987
--- /dev/null
@@ -0,0 +1,5 @@
+
+
+PASS postMessage() on Cross-origin iframe with 'Cross-Origin-Options: allow-postmessage' HTTP header 
+PASS postMessage() on Cross-origin popup with 'Cross-Origin-Options: allow-postmessage' HTTP header 
+
diff --git a/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny-expected.txt b/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny-expected.txt
new file mode 100644 (file)
index 0000000..49f6c93
--- /dev/null
@@ -0,0 +1,5 @@
+
+
+PASS postMessage() on Cross-origin iframe with 'Cross-Origin-Options: allow-postmessage' but current window has 'deny' option 
+PASS postMessage() on Cross-origin popup with 'Cross-Origin-Options: allow-postmessage' but current window has 'deny' option 
+
diff --git a/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny.html b/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny.html
new file mode 100644 (file)
index 0000000..3494f0a
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Tests calling postMessage() on a window with 'Cross-Origin-Options: allow-postmessage' from a window with 'Cross-Origin-Options: deny'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+</head>
+<body>
+<script>
+
+promise_test(function(test) {
+    return withIframe("cross-origin-options-allow-postmessage-pong.html", true /* isCrossOrigin */).then((f) => {
+        assert_throws("SecurityError", function() { f.contentWindow.length }, "length property access");
+        assert_throws("SecurityError", function() { f.contentWindow.postMessage("PING", "*"); }, "Calling postMessage() should throw");
+    });
+}, "postMessage() on Cross-origin iframe with 'Cross-Origin-Options: allow-postmessage' but current window has 'deny' option");
+
+promise_test(function(test) {
+    return withPopup("cross-origin-options-allow-postmessage-pong.html", true /* isCrossOrigin */).then((result) => {
+        assert_throws("SecurityError", function() { result.window.length }, "length property access");
+        assert_throws("SecurityError", function() { result.window.postMessage("PING", "*"); }, "Calling postMessage() should throw");
+    });
+}, "postMessage() on Cross-origin popup with 'Cross-Origin-Options: allow-postmessage' but current window has 'deny' option");
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny.html.headers b/LayoutTests/http/wpt/cross-origin-options/allow-postmessage-from-deny.html.headers
new file mode 100644 (file)
index 0000000..bf59633
--- /dev/null
@@ -0,0 +1 @@
+Cross-Origin-Options: deny
diff --git a/LayoutTests/http/wpt/cross-origin-options/allow-postmessage.html b/LayoutTests/http/wpt/cross-origin-options/allow-postmessage.html
new file mode 100644 (file)
index 0000000..b11e562
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Tests that postMessage() works when 'Cross-Origin-Options: allow-postmessage' HTTP header is served</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+</head>
+<body>
+<script>
+
+promise_test(function(test) {
+    return withIframe("cross-origin-options-allow-postmessage-pong.html", true /* isCrossOrigin */).then((f) => {
+        return new Promise((resolve) => {
+            window.onmessage = (msg) => {
+                window.onmessage = null;
+                assert_equals(msg.data, "PONG");
+                assert_equals(msg.source, f.contentWindow);
+                resolve();
+            };
+            assert_throws("SecurityError", function() { f.contentWindow.length }, "length property access");
+            f.contentWindow.postMessage("PING", "*");
+        });
+    });
+}, "postMessage() on Cross-origin iframe with 'Cross-Origin-Options: allow-postmessage' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("cross-origin-options-allow-postmessage-pong.html", true /* isCrossOrigin */).then((result) => {
+        return new Promise((resolve) => {
+            window.onmessage = (msg) => {
+                window.onmessage = null;
+                assert_equals(msg.data, "PONG");
+                assert_equals(msg.source, result.window);
+                resolve();
+            };
+            assert_throws("SecurityError", function() { result.window.length }, "length property access");
+            result.window.postMessage("PING", "*");
+        });
+    });
+}, "postMessage() on Cross-origin popup with 'Cross-Origin-Options: allow-postmessage' HTTP header");
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/wpt/cross-origin-options/cross-origin-options-header-expected.txt b/LayoutTests/http/wpt/cross-origin-options/cross-origin-options-header-expected.txt
new file mode 100644 (file)
index 0000000..fb31084
--- /dev/null
@@ -0,0 +1,19 @@
+  
+
+PASS Cross-origin iframe with 'Cross-Origin-Options: deny' HTTP header 
+PASS Cross-origin iframe with 'Cross-Origin-Options: allow-postmessage' HTTP header 
+PASS Cross-origin iframe with 'Cross-Origin-Options: allow' HTTP header 
+PASS Cross-origin iframe with 'Cross-Origin-Options: invalid' HTTP header 
+PASS Same-origin iframe with 'Cross-Origin-Options: deny' HTTP header 
+PASS Same-origin iframe with 'Cross-Origin-Options: allow-postmessage' HTTP header 
+PASS Same-origin iframe with 'Cross-Origin-Options: allow' HTTP header 
+PASS Same-origin iframe with 'Cross-Origin-Options: invalid' HTTP header 
+PASS Cross-origin popup with 'Cross-Origin-Options: deny' HTTP header 
+PASS Cross-origin popup with 'Cross-Origin-Options: allow-postmessage' HTTP header 
+PASS Cross-origin popup with 'Cross-Origin-Options: allow' HTTP header 
+PASS Cross-origin popup with 'Cross-Origin-Options: invalid' HTTP header 
+PASS Same-origin popup with 'Cross-Origin-Options: deny' HTTP header 
+PASS Same-origin popup with 'Cross-Origin-Options: allow-postmessage' HTTP header 
+PASS Same-origin popup with 'Cross-Origin-Options: allow' HTTP header 
+PASS Same-origin popup with 'Cross-Origin-Options: invalid' HTTP header 
+
diff --git a/LayoutTests/http/wpt/cross-origin-options/cross-origin-options-header.html b/LayoutTests/http/wpt/cross-origin-options/cross-origin-options-header.html
new file mode 100644 (file)
index 0000000..2215d0f
--- /dev/null
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Basic testing for Cross-Origin-Options HTTP header</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+</head>
+<body>
+<script>
+
+// Test frame has a subframe so we expect an indexed property with index 0.
+crossOriginPropertyNames.push('0');
+
+function checkIframePropertyValues(w)
+{
+    assert_equals(w.parent, window, "'parent' property value");
+    assert_equals(w.top, window, "'top' property value");
+    assert_equals(w.opener, null, "'opener' property value");
+    assert_equals(w.length, 1, "'length' property value");
+    assert_not_throwing(function() { w[0]; }, "Subframe access via index");
+    assert_equals(w['subframe'], w[0], "Subframe access by name");
+}
+
+function checkPopupPropertyValues(w)
+{
+    assert_equals(w.parent, w, "'parent' property value");
+    assert_equals(w.top, w, "'top' property value");
+    assert_equals(w.opener, window, "'opener' property value");
+    assert_equals(w.length, 1, "'length' property value");
+    assert_not_throwing(function() { w[0]; }, "Subframe access via index");
+    assert_equals(w['subframe'], w[0], "Subframe access by name");
+}
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=deny", true /* isCrossOrigin */).then((f) => {
+        testCrossOriginOption(f.contentWindow, "deny", true /* isCrossOrigin */);
+    });
+}, "Cross-origin iframe with 'Cross-Origin-Options: deny' HTTP header");
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=allow-postmessage", true /* isCrossOrigin */).then((f) => {
+        testCrossOriginOption(f.contentWindow, "allow-postmessage", true /* isCrossOrigin */);
+    });
+}, "Cross-origin iframe with 'Cross-Origin-Options: allow-postmessage' HTTP header");
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=allow", true /* isCrossOrigin */).then((f) => {
+        const w = f.contentWindow;
+        testCrossOriginOption(w, "allow", true /* isCrossOrigin */);
+
+        checkIframePropertyValues(w);
+    });
+}, "Cross-origin iframe with 'Cross-Origin-Options: allow' HTTP header");
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=invalid", true /* isCrossOrigin */).then((f) => {
+        const w = f.contentWindow;
+        testCrossOriginOption(w, "allow", true /* isCrossOrigin */);
+        
+        checkIframePropertyValues(w);
+    });
+}, "Cross-origin iframe with 'Cross-Origin-Options: invalid' HTTP header");
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=deny", false /* isCrossOrigin */).then((f) => {
+         const w = f.contentWindow;
+        testCrossOriginOption(w, "deny", false /* isCrossOrigin */);
+
+        checkIframePropertyValues(w);
+    });
+}, "Same-origin iframe with 'Cross-Origin-Options: deny' HTTP header");
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=allow-postmessage", false /* isCrossOrigin */).then((f) => {
+         const w = f.contentWindow;
+        testCrossOriginOption(w, "allow-postmessage", false /* isCrossOrigin */);
+
+        checkIframePropertyValues(w);
+    });
+}, "Same-origin iframe with 'Cross-Origin-Options: allow-postmessage' HTTP header");
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=allow", false /* isCrossOrigin */).then((f) => {
+         const w = f.contentWindow;
+        testCrossOriginOption(w, "allow", false /* isCrossOrigin */);
+
+        checkIframePropertyValues(w);
+    });
+}, "Same-origin iframe with 'Cross-Origin-Options: allow' HTTP header");
+
+promise_test(function(test) {
+    return withIframe("serve-cross-origin-options-header.py?value=invalid", false /* isCrossOrigin */).then((f) => {
+         const w = f.contentWindow;
+        testCrossOriginOption(w, "allow", false /* isCrossOrigin */);
+
+        checkIframePropertyValues(w);
+    });
+}, "Same-origin iframe with 'Cross-Origin-Options: invalid' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=deny", true /* isCrossOrigin */).then((result) => {
+        testCrossOriginOption(result.window, "deny", true /* isCrossOrigin */);
+    });
+}, "Cross-origin popup with 'Cross-Origin-Options: deny' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=allow-postmessage", true /* isCrossOrigin */).then((result) => {
+        testCrossOriginOption(result.window, "allow-postmessage", true /* isCrossOrigin */);
+    });
+}, "Cross-origin popup with 'Cross-Origin-Options: allow-postmessage' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=allow", true /* isCrossOrigin */).then((result) => {
+        const w = result.window;
+        testCrossOriginOption(w, "allow", true /* isCrossOrigin */);
+
+        checkPopupPropertyValues(w);
+    });
+}, "Cross-origin popup with 'Cross-Origin-Options: allow' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=invalid", true /* isCrossOrigin */).then((result) => {
+        const w = result.window;
+        testCrossOriginOption(w, "allow", true /* isCrossOrigin */);
+
+        checkPopupPropertyValues(w);
+    });
+}, "Cross-origin popup with 'Cross-Origin-Options: invalid' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=deny", false /* isCrossOrigin */).then((result) => {
+         const w = result.window;
+        testCrossOriginOption(w, "deny", false /* isCrossOrigin */);
+
+        checkPopupPropertyValues(w);
+    });
+}, "Same-origin popup with 'Cross-Origin-Options: deny' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=allow-postmessage", false /* isCrossOrigin */).then((result) => {
+         const w = result.window;
+        testCrossOriginOption(w, "allow-postmessage", false /* isCrossOrigin */);
+
+        checkPopupPropertyValues(w);
+    });
+}, "Same-origin popup with 'Cross-Origin-Options: allow-postmessage' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=allow", false /* isCrossOrigin */).then((result) => {
+         const w = result.window;
+        testCrossOriginOption(w, "allow", false /* isCrossOrigin */);
+
+        checkPopupPropertyValues(w);
+    });
+}, "Same-origin popup with 'Cross-Origin-Options: allow' HTTP header");
+
+promise_test(function(test) {
+    return withPopup("serve-cross-origin-options-header.py?value=invalid", false /* isCrossOrigin */).then((result) => {
+         const w = result.window;
+        testCrossOriginOption(w, "allow", false /* isCrossOrigin */);
+
+        checkPopupPropertyValues(w);
+    });
+}, "Same-origin popup with 'Cross-Origin-Options: invalid' HTTP header");
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html b/LayoutTests/http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html
new file mode 100644 (file)
index 0000000..9a67dd7
--- /dev/null
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+addEventListener('message', (msg) => {
+    if (event.data === "PING")
+        event.source.postMessage("PONG", "*");
+    });
+    window.addEventListener('load', () => {
+        const ownerWindow = window.opener ? window.opener : window.top;
+        try {
+            ownerWindow.postMessage("READY", "*");
+        } catch (e) { }
+    });
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html.headers b/LayoutTests/http/wpt/cross-origin-options/resources/cross-origin-options-allow-postmessage-pong.html.headers
new file mode 100644 (file)
index 0000000..cfb12bb
--- /dev/null
@@ -0,0 +1 @@
+Cross-Origin-Options: allow-postmessage
diff --git a/LayoutTests/http/wpt/cross-origin-options/resources/serve-cross-origin-options-header.py b/LayoutTests/http/wpt/cross-origin-options/resources/serve-cross-origin-options-header.py
new file mode 100644 (file)
index 0000000..ea90655
--- /dev/null
@@ -0,0 +1,14 @@
+def main(request, response):
+    headers = [("Content-Type", "text/html"),
+               ("Cross-Origin-Options", request.GET['value']),]
+    return 200, headers, """TEST
+        <iframe name="subframe"></iframe>
+        <script>
+            window.addEventListener('load', () => {
+                const ownerWindow = window.opener ? window.opener : window.top;
+                try {
+                    ownerWindow.postMessage("READY", "*");
+                } catch (e) { }
+            });
+        </script>
+    """
diff --git a/LayoutTests/http/wpt/cross-origin-options/resources/utils.js b/LayoutTests/http/wpt/cross-origin-options/resources/utils.js
new file mode 100644 (file)
index 0000000..3d31672
--- /dev/null
@@ -0,0 +1,150 @@
+const RESOURCES_DIR = "/WebKit/cross-origin-options/resources/";
+
+function isCrossOriginWindow(w)
+{
+    try {
+        w.name;
+    } catch (e) {
+        return true;
+    }
+    return false;
+}
+
+async function waitForCrossOriginLoad(w)
+{
+    return new Promise((resolve) => {
+        window.addEventListener('message', (msg) => {
+            if (msg.source != w || msg.data != "READY")
+                return;
+            resolve();
+        });
+
+        let handle = setInterval(() => {
+            if (isCrossOriginWindow(w)) {
+                clearInterval(handle);
+                try {
+                    w.postMessage;
+                } catch (e) {
+                    // No point in waiting for "READY" message from window since postMessage is
+                    // not available.
+                    resolve();
+                }
+            }
+        }, 5);
+    });
+}
+
+async function withIframe(resourceFile, crossOrigin)
+{
+    return new Promise((resolve) => {
+        let resourceURL = crossOrigin ? get_host_info().HTTP_REMOTE_ORIGIN : get_host_info().HTTP_ORIGIN;
+        resourceURL += RESOURCES_DIR;
+        resourceURL += resourceFile;
+        let frame = document.createElement("iframe");
+        frame.src = resourceURL;
+        if (crossOrigin) {
+            document.body.appendChild(frame);
+            waitForCrossOriginLoad(frame.contentWindow).then(() => {
+                resolve(frame);
+            });
+        } else {
+            frame.onload = function() { resolve(frame); };
+            document.body.appendChild(frame);
+        }
+    });
+}
+
+async function withPopup(resourceFile, crossOrigin)
+{
+    return new Promise((resolve) => { 
+        let resourceURL = crossOrigin ? get_host_info().HTTP_REMOTE_ORIGIN : get_host_info().HTTP_ORIGIN;
+        resourceURL += RESOURCES_DIR;
+        resourceURL += resourceFile;
+
+        let w = open(resourceURL);
+        if (crossOrigin) {
+            waitForCrossOriginLoad(w).then(() => {
+                resolve({ 'window': w });
+            });
+        } else {
+            w.onload = function() { resolve({ 'window': w }); };
+        }
+   });
+}
+
+const crossOriginPropertyNames = [ 'blur', 'close', 'closed', 'focus', 'frames', 'length', 'location', 'opener', 'parent', 'postMessage', 'self', 'top', 'window' ];
+const forbiddenPropertiesCrossOrigin = ["name", "document", "history", "locationbar", "status", "frameElement", "navigator", "alert", "localStorage", "sessionStorage", "event", "foo", "bar"];
+
+function assert_not_throwing(f, message)
+{
+    try {
+        f();
+    } catch (e) {
+        assert_unreached(message);
+    }
+}
+
+function checkCrossOriginPropertiesAccess(w)
+{
+    for (let crossOriginPropertyName of crossOriginPropertyNames)
+       assert_not_throwing(function() { w[crossOriginPropertyName]; }, "Accessing property '" + crossOriginPropertyName +"' on Window should not throw");
+   
+    assert_false(w.closed, "'closed' property value"); 
+    assert_equals(w.frames, w, "'frames' property value");
+    assert_equals(w.self, w, "'self' property value");
+    assert_equals(w.window, w, "'window' property value");
+
+    assert_not_throwing(function() { w.blur(); }, "Calling blur() on Window should should throw");
+    assert_not_throwing(function() { w.focus(); }, "Calling focus() on Window should should throw");
+    assert_not_throwing(function() { w.postMessage('test', '*'); }, "Calling postMessage() on Window should should throw");
+}
+
+function testCrossOriginOption(w, headerValue, isCrossOrigin)
+{
+    if (!isCrossOrigin) {
+        checkCrossOriginPropertiesAccess(w);
+        for (let forbiddenPropertyCrossOrigin of forbiddenPropertiesCrossOrigin)
+            assert_not_throwing(function() { eval("w." + forbiddenPropertyCrossOrigin); }, "Accessing property '" + forbiddenPropertyCrossOrigin + "' on Window should not throw");
+        assert_not_throwing(function() { w.foo = 1; }, "Setting expando property should not throw");
+        assert_equals(w.foo, 1, "expando property value");
+        return;
+    }
+
+    // Cross-origin case.
+    for (let forbiddenPropertyCrossOrigin of forbiddenPropertiesCrossOrigin) {
+        assert_throws("SecurityError", function() { eval("w." + forbiddenPropertyCrossOrigin); }, "Accessing property '" + forbiddenPropertyCrossOrigin + "' on Window should throw");
+        let desc = Object.getOwnPropertyDescriptor(window, forbiddenPropertyCrossOrigin);
+        if (desc && desc.value)
+            assert_throws("SecurityError", function() { desc.value.call(w); }, "Calling function '" + forbiddenPropertyCrossOrigin + "' on Window should throw (using getter from other window)");
+        else if (desc && desc.get)
+            assert_throws("SecurityError", function() { desc.get.call(w); }, "Accessing property '" + forbiddenPropertyCrossOrigin + "' on Window should throw (using getter from other window)");
+    }
+    assert_throws("SecurityError", function() { w.foo = 1; }, "Setting an expando property should throw");
+
+    if (headerValue == "deny" || headerValue == "allow-postmessage") {
+        for (let crossOriginPropertyName of crossOriginPropertyNames) {
+            if (headerValue == "allow-postmessage" && crossOriginPropertyName == "postMessage") {
+                assert_not_throwing(function() { w[crossOriginPropertyName]; }, "Accessing property '" + crossOriginPropertyName +"' on Window should not throw");
+            } else {
+                assert_throws("SecurityError", function() { w[crossOriginPropertyName]; }, "Accessing '" + crossOriginPropertyName + "' property");
+
+                let desc = Object.getOwnPropertyDescriptor(window, crossOriginPropertyName);
+                if (desc && desc.value)
+                    assert_throws("SecurityError", function() { desc.value.call(w); }, "Calling function '" + crossOriginPropertyName + "' on Window should throw (using getter from other window)");
+                else if (desc && desc.get)
+                    assert_throws("SecurityError", function() { desc.get.call(w); }, "Accessing property '" + crossOriginPropertyName + "' on Window should throw (using getter from other window)");
+            }
+        }
+        if (headerValue == "allow-postmessage") {
+            assert_not_throwing(function() { w.postMessage('test', '*'); }, "Calling postMessage() on Window should not throw");
+            assert_not_throwing(function() { Object.getOwnPropertyDescriptor(window, 'postMessage').value.call(w, 'test', '*'); }, "Calling postMessage() on Window should not throw (using getter from other window)");
+        }
+
+        assert_array_equals(Object.getOwnPropertyNames(w).sort(), headerValue == "allow-postmessage" ? ['postMessage'] : [], "Object.getOwnPropertyNames()");
+
+        return;
+    }
+
+    assert_array_equals(Object.getOwnPropertyNames(w).sort(), crossOriginPropertyNames.sort(), "Object.getOwnPropertyNames()");
+    checkCrossOriginPropertiesAccess(w);
+}
index d6ebbf0..0b3e159 100644 (file)
@@ -1,3 +1,83 @@
+2018-05-09  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for 'Cross-Origin-Options' HTTP response header
+        https://bugs.webkit.org/show_bug.cgi?id=184996
+        <rdar://problem/39664620>
+
+        Reviewed by Geoff Garen.
+
+        Add initial support for 'Cross-Origin-Options' HTTP response header behind an experimental
+        feature flag, on by default. When the HTTP server services this HTTP response header for a
+        main resource, we'll set these options on the corresponding Document. This will impact the
+        behavior of the Document's associated Window API when cross-origin.
+
+        The HTTP header has 3 possible values:
+        - allow: This is the default. Regular cross-origin Window API is available.
+        - allow-postmessage: Only postMessage() is available on a cross-origin window, trying to
+          access anything else will throw a SecurityError.
+        - deny: Trying to do anything with a cross-origin window will throw a SecurityError.
+
+        The header has no effect when accessing same origin windows.
+
+        Note that on cross-origin access from Window A to Window B, we check the cross-origin
+        options for both Window A and Window B and use the lowest common denominator as effective
+        cross-origin options for the access. So if Window A has 'Cross-Origin-Options: deny' and
+        tries to call postMessage() on Window B which has 'Cross-Origin-Options: allow-postmessage',
+        we will throw a SecurityError. This is because Window A's more restrictive options (deny)
+        apply.
+
+        Tests: http/wpt/cross-origin-options/allow-postmessage-from-deny.html
+               http/wpt/cross-origin-options/allow-postmessage.html
+               http/wpt/cross-origin-options/cross-origin-options-header.html
+
+        * bindings/js/JSDOMBindingSecurity.cpp:
+        (WebCore::BindingSecurity::shouldAllowAccessToDOMWindowGivenMinimumCrossOriginOptions):
+        * bindings/js/JSDOMBindingSecurity.h:
+        * bindings/js/JSDOMWindowCustom.cpp:
+        (WebCore::effectiveCrossOriginOptionsForAccess):
+        (WebCore::jsDOMWindowGetOwnPropertySlotRestrictedAccess):
+        (WebCore::JSDOMWindow::getOwnPropertySlot):
+        (WebCore::JSDOMWindow::getOwnPropertySlotByIndex):
+        (WebCore::addCrossOriginWindowPropertyNames):
+        (WebCore::addScopedChildrenIndexes):
+        (WebCore::addCrossOriginWindowOwnPropertyNames):
+        (WebCore::JSDOMWindow::getOwnPropertyNames):
+        * bindings/js/JSDOMWindowCustom.h:
+        * bindings/js/JSRemoteDOMWindowCustom.cpp:
+        (WebCore::JSRemoteDOMWindow::getOwnPropertySlot):
+        (WebCore::JSRemoteDOMWindow::getOwnPropertySlotByIndex):
+        (WebCore::JSRemoteDOMWindow::getOwnPropertyNames):
+        * bindings/scripts/CodeGeneratorJS.pm:
+        (GenerateAttributeGetterBodyDefinition):
+        (GetCrossOriginsOptionsFromExtendedAttributeValue):
+        (GenerateAttributeSetterBodyDefinition):
+        (GenerateOperationBodyDefinition):
+        * bindings/scripts/IDLAttributes.json:
+        * dom/Document.cpp:
+        (WebCore::Document::setCrossOriginOptions):
+        * dom/Document.h:
+        (WebCore::Document::crossOriginOptions const):
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::didBeginDocument):
+        * page/AbstractDOMWindow.cpp:
+        (WebCore::AbstractDOMWindow::AbstractDOMWindow):
+        * page/AbstractDOMWindow.h:
+        (WebCore::AbstractDOMWindow::crossOriginOptions):
+        (WebCore::AbstractDOMWindow::setCrossOriginOptions):
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::DOMWindow):
+        (WebCore::DOMWindow::didSecureTransitionTo):
+        * page/DOMWindow.idl:
+        * page/Frame.h:
+        * page/RemoteDOMWindow.cpp:
+        (WebCore::RemoteDOMWindow::RemoteDOMWindow):
+        * page/RemoteDOMWindow.h:
+        * page/Settings.yaml:
+        * platform/network/HTTPHeaderNames.in:
+        * platform/network/HTTPParsers.cpp:
+        (WebCore::parseCrossOriginOptionsHeader):
+        * platform/network/HTTPParsers.h:
+
 2018-05-09  Ryosuke Niwa  <rniwa@webkit.org>
 
         Release assert in TreeScopeOrderedMap::remove via HTMLImageElement::removedFromAncestor
index e2816db..2b67585 100644 (file)
@@ -25,6 +25,7 @@
 #include "DOMWindow.h"
 #include "Document.h"
 #include "Frame.h"
+#include "HTTPParsers.h"
 #include "JSDOMExceptionHandling.h"
 #include "JSDOMWindowBase.h"
 #include "SecurityOrigin.h"
@@ -99,4 +100,19 @@ bool BindingSecurity::shouldAllowAccessToNode(JSC::ExecState& state, Node* targe
     return !target || canAccessDocument(&state, &target->document(), LogSecurityError);
 }
 
+bool BindingSecurity::shouldAllowAccessToDOMWindowGivenMinimumCrossOriginOptions(JSC::ExecState* state, DOMWindow& target, CrossOriginOptions minimumCrossOriginOptions, SecurityReportingOption reportingOption)
+{
+    DOMWindow& source = activeDOMWindow(*state);
+    ASSERT(minimumCrossOriginOptions > CrossOriginOptions::Deny);
+
+    static_assert(CrossOriginOptions::Deny < CrossOriginOptions::AllowPostMessage && CrossOriginOptions::AllowPostMessage < CrossOriginOptions::Allow, "More restrictive cross-origin options should have lower values");
+
+    // Fast path.
+    auto effectiveCrossOriginOptions = std::min(source.crossOriginOptions(), target.crossOriginOptions());
+    if (effectiveCrossOriginOptions >= minimumCrossOriginOptions)
+        return true;
+
+    return shouldAllowAccessToDOMWindow(state, target, reportingOption);
+}
+
 } // namespace WebCore
index 7b4085a..5c2af99 100644 (file)
@@ -36,6 +36,8 @@ class DOMWindow;
 class Frame;
 class Node;
 
+enum class CrossOriginOptions;
+
 void printErrorMessageForFrame(Frame*, const String& message);
 
 enum SecurityReportingOption { DoNotReportSecurityError, LogSecurityError, ThrowSecurityError };
@@ -53,6 +55,8 @@ bool shouldAllowAccessToFrame(JSC::ExecState*, Frame*, SecurityReportingOption =
 bool shouldAllowAccessToFrame(JSC::ExecState&, Frame&, String& message);
 bool shouldAllowAccessToNode(JSC::ExecState&, Node*);
 
+bool shouldAllowAccessToDOMWindowGivenMinimumCrossOriginOptions(JSC::ExecState*, DOMWindow&, CrossOriginOptions, SecurityReportingOption = LogSecurityError);
+
 };
 
 template<typename T> inline T* BindingSecurity::checkSecurityForNode(JSC::ExecState& state, T& node)
index 281ea4e..5db1d5c 100644 (file)
@@ -26,6 +26,7 @@
 #include "HTMLCollection.h"
 #include "HTMLDocument.h"
 #include "HTMLFrameOwnerElement.h"
+#include "HTTPParsers.h"
 #include "JSDOMBindingSecurity.h"
 #include "JSDOMConvertNullable.h"
 #include "JSDOMConvertNumbers.h"
 namespace WebCore {
 using namespace JSC;
 
+static CrossOriginOptions effectiveCrossOriginOptionsForAccess(ExecState& state, AbstractDOMWindow& target)
+{
+    static_assert(CrossOriginOptions::Deny < CrossOriginOptions::AllowPostMessage && CrossOriginOptions::AllowPostMessage < CrossOriginOptions::Allow, "More restrictive cross-origin options should have lower values");
+    return std::min(activeDOMWindow(state).crossOriginOptions(), target.crossOriginOptions());
+}
+
 EncodedJSValue JSC_HOST_CALL jsDOMWindowInstanceFunctionShowModalDialog(ExecState*);
 
 void JSDOMWindow::visitAdditionalChildren(SlotVisitor& visitor)
@@ -80,9 +87,9 @@ static EncodedJSValue jsDOMWindowWebKit(ExecState* exec, EncodedJSValue thisValu
 #endif
 
 template <DOMWindowType windowType>
-bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject* thisObject, AbstractFrame* frame, ExecState* exec, PropertyName propertyName, PropertySlot& slot, const String& errorMessage)
+bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject* thisObject, AbstractDOMWindow& window, ExecState& state, PropertyName propertyName, PropertySlot& slot, const String& errorMessage)
 {
-    VM& vm = exec->vm();
+    VM& vm = state.vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
 
     auto& builtinNames = static_cast<JSVMClientData*>(vm.clientData)->builtinNames();
@@ -93,6 +100,21 @@ bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject* thisObject
         return true;
     }
 
+    switch (effectiveCrossOriginOptionsForAccess(state, window)) {
+    case CrossOriginOptions::AllowPostMessage:
+        if (propertyName == builtinNames.postMessagePublicName()) {
+            slot.setCustom(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum), windowType == DOMWindowType::Remote ? nonCachingStaticFunctionGetter<jsRemoteDOMWindowInstanceFunctionPostMessage, 0> : nonCachingStaticFunctionGetter<jsDOMWindowInstanceFunctionPostMessage, 2>);
+            return true;
+        }
+        FALLTHROUGH;
+    case CrossOriginOptions::Deny:
+        throwSecurityError(state, scope, errorMessage);
+        slot.setUndefined();
+        return false;
+    case CrossOriginOptions::Allow:
+        break;
+    }
+
     // These are the functions we allow access to cross-origin (DoNotCheckSecurity in IDL).
     // Always provide the original function, on a fresh uncached function object.
     if (propertyName == builtinNames.blurPublicName()) {
@@ -136,7 +158,7 @@ bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject* thisObject
         // For any other entries in the static property table, deny access. (Early return also prevents
         // named getter from returning frames with matching names - this seems a little questionable, see
         // FIXME comment on prototype search below.)
-        throwSecurityError(*exec, scope, errorMessage);
+        throwSecurityError(state, scope, errorMessage);
         slot.setUndefined();
         return false;
     }
@@ -146,19 +168,20 @@ bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject* thisObject
     // properties that are in Moz but not IE. Since we have some of these, we have to do it
     // the Moz way.
     // FIXME: Add support to named attributes on RemoteFrames.
+    auto* frame = window.frame();
     if (frame && is<Frame>(*frame)) {
         if (auto* scopedChild = downcast<Frame>(*frame).tree().scopedChild(propertyNameToAtomicString(propertyName))) {
-            slot.setValue(thisObject, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum, toJS(exec, scopedChild->document()->domWindow()));
+            slot.setValue(thisObject, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum, toJS(&state, scopedChild->document()->domWindow()));
             return true;
         }
     }
 
-    throwSecurityError(*exec, scope, errorMessage);
+    throwSecurityError(state, scope, errorMessage);
     slot.setUndefined();
     return false;
 }
-template bool jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(JSDOMGlobalObject*, AbstractFrame*, ExecState*, PropertyName, PropertySlot&, const String&);
-template bool jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Remote>(JSDOMGlobalObject*, AbstractFrame*, ExecState*, PropertyName, PropertySlot&, const String&);
+template bool jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(JSDOMGlobalObject*, AbstractDOMWindow&, ExecState&, PropertyName, PropertySlot&, const String&);
+template bool jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Remote>(JSDOMGlobalObject*, AbstractDOMWindow&, ExecState&, PropertyName, PropertySlot&, const String&);
 
 // Property access sequence is:
 // (1) indexed properties,
@@ -172,13 +195,12 @@ bool JSDOMWindow::getOwnPropertySlot(JSObject* object, ExecState* state, Propert
         return getOwnPropertySlotByIndex(object, state, index.value(), slot);
 
     auto* thisObject = jsCast<JSDOMWindow*>(object);
-    auto* frame = thisObject->wrapped().frame();
 
     // Hand off all cross-domain access to jsDOMWindowGetOwnPropertySlotRestrictedAccess.
     String errorMessage;
     if (!BindingSecurity::shouldAllowAccessToDOMWindow(*state, thisObject->wrapped(), errorMessage))
-        return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(thisObject, frame, state, propertyName, slot, errorMessage);
-    
+        return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(thisObject, thisObject->wrapped(), *state, propertyName, slot, errorMessage);
+
     // FIXME: this need more explanation.
     // (Particularly, is it correct that this exists here but not in getOwnPropertySlotByIndex?)
     slot.setWatchpointSet(thisObject->m_windowCloseWatchpoints);
@@ -186,6 +208,8 @@ bool JSDOMWindow::getOwnPropertySlot(JSObject* object, ExecState* state, Propert
     // (2) Regular own properties.
     PropertySlot slotCopy = slot;
     if (Base::getOwnPropertySlot(thisObject, state, propertyName, slot)) {
+        auto* frame = thisObject->wrapped().frame();
+
         // Detect when we're getting the property 'showModalDialog', this is disabled, and has its original value.
         bool isShowModalDialogAndShouldHide = propertyName == static_cast<JSVMClientData*>(state->vm().clientData)->builtinNames().showModalDialogPublicName()
             && (!frame || !DOMWindow::canShowModalDialog(*frame))
@@ -213,22 +237,39 @@ bool JSDOMWindow::getOwnPropertySlot(JSObject* object, ExecState* state, Propert
 bool JSDOMWindow::getOwnPropertySlotByIndex(JSObject* object, ExecState* state, unsigned index, PropertySlot& slot)
 {
     auto* thisObject = jsCast<JSDOMWindow*>(object);
-    auto* frame = thisObject->wrapped().frame();
+    auto& window = thisObject->wrapped();
+    auto* frame = window.frame();
 
     // Indexed getters take precendence over regular properties, so caching would be invalid.
     slot.disableCaching();
 
+    String errorMessage;
+    std::optional<bool> cachedIsCrossOriginAccess;
+    auto isCrossOriginAccess = [&] {
+        if (!cachedIsCrossOriginAccess)
+            cachedIsCrossOriginAccess = !BindingSecurity::shouldAllowAccessToDOMWindow(*state, window, errorMessage);
+        return *cachedIsCrossOriginAccess;
+    };
+
     // (1) First, indexed properties.
-    // These are also allowed cross-orgin, so come before the access check.
-    if (frame && index < frame->tree().scopedChildCount()) {
-        slot.setValue(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly), toJS(state, frame->tree().scopedChild(index)->document()->domWindow()));
-        return true;
+    // These are also allowed cross-origin, so come before the access check.
+    switch (effectiveCrossOriginOptionsForAccess(*state, window)) {
+    case CrossOriginOptions::Deny:
+    case CrossOriginOptions::AllowPostMessage:
+        if (isCrossOriginAccess())
+            break;
+        FALLTHROUGH;
+    case CrossOriginOptions::Allow:
+        if (frame && index < frame->tree().scopedChildCount()) {
+            slot.setValue(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly), toJS(state, frame->tree().scopedChild(index)->document()->domWindow()));
+            return true;
+        }
+        break;
     }
 
     // Hand off all cross-domain/frameless access to jsDOMWindowGetOwnPropertySlotRestrictedAccess.
-    String errorMessage;
-    if (!BindingSecurity::shouldAllowAccessToDOMWindow(*state, thisObject->wrapped(), errorMessage))
-        return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(thisObject, frame, state, Identifier::from(state, index), slot, errorMessage);
+    if (isCrossOriginAccess())
+        return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(thisObject, window, *state, Identifier::from(state, index), slot, errorMessage);
 
     // (2) Regular own properties.
     return Base::getOwnPropertySlotByIndex(thisObject, state, index, slot);
@@ -287,8 +328,10 @@ bool JSDOMWindow::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned
 }
 
 // https://html.spec.whatwg.org/#crossoriginproperties-(-o-)
-static void addCrossOriginWindowPropertyNames(VM& vm, PropertyNameArray& propertyNames)
+static void addCrossOriginWindowPropertyNames(ExecState& state, AbstractDOMWindow& window, PropertyNameArray& propertyNames)
 {
+    auto& vm = state.vm();
+
     static const Identifier* const properties[] = {
         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().blurPublicName(),
         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().closePublicName(),
@@ -304,8 +347,18 @@ static void addCrossOriginWindowPropertyNames(VM& vm, PropertyNameArray& propert
         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().topPublicName(),
         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().windowPublicName()
     };
-    for (auto* property : properties)
-        propertyNames.add(*property);
+
+    switch (effectiveCrossOriginOptionsForAccess(state, window)) {
+    case CrossOriginOptions::Allow:
+        for (auto* property : properties)
+            propertyNames.add(*property);
+        break;
+    case CrossOriginOptions::AllowPostMessage:
+        propertyNames.add(static_cast<JSVMClientData*>(vm.clientData)->builtinNames().postMessagePublicName());
+        break;
+    case CrossOriginOptions::Deny:
+        break;
+    }
 }
 
 static void addScopedChildrenIndexes(ExecState& state, DOMWindow& window, PropertyNameArray& propertyNames)
@@ -318,17 +371,25 @@ static void addScopedChildrenIndexes(ExecState& state, DOMWindow& window, Proper
     if (!frame)
         return;
 
+    switch (effectiveCrossOriginOptionsForAccess(state, window)) {
+    case CrossOriginOptions::Allow:
+        break;
+    case CrossOriginOptions::Deny:
+    case CrossOriginOptions::AllowPostMessage:
+        return;
+    }
+
     unsigned scopedChildCount = frame->tree().scopedChildCount();
     for (unsigned i = 0; i < scopedChildCount; ++i)
         propertyNames.add(Identifier::from(&state, i));
 }
 
 // https://html.spec.whatwg.org/#crossoriginownpropertykeys-(-o-)
-void addCrossOriginWindowOwnPropertyNames(ExecState& state, PropertyNameArray& propertyNames)
+void addCrossOriginWindowOwnPropertyNames(ExecState& state, AbstractDOMWindow& window, PropertyNameArray& propertyNames)
 {
-    VM& vm = state.vm();
-    addCrossOriginWindowPropertyNames(vm, propertyNames);
+    addCrossOriginWindowPropertyNames(state, window, propertyNames);
 
+    auto& vm = state.vm();
     propertyNames.add(vm.propertyNames->toStringTagSymbol);
     propertyNames.add(vm.propertyNames->hasInstanceSymbol);
     propertyNames.add(vm.propertyNames->isConcatSpreadableSymbol);
@@ -343,7 +404,7 @@ void JSDOMWindow::getOwnPropertyNames(JSObject* object, ExecState* exec, Propert
 
     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped(), DoNotReportSecurityError)) {
         if (mode.includeDontEnumProperties())
-            addCrossOriginWindowOwnPropertyNames(*exec, propertyNames);
+            addCrossOriginWindowOwnPropertyNames(*exec, thisObject->wrapped(), propertyNames);
         return;
     }
     Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
index cfda3e0..9fe9742 100644 (file)
@@ -22,6 +22,7 @@
 
 namespace WebCore {
 
+class AbstractDOMWindow;
 class AbstractFrame;
 
 inline JSDOMWindow* asJSDOMWindow(JSC::JSGlobalObject* globalObject)
@@ -37,8 +38,8 @@ inline const JSDOMWindow* asJSDOMWindow(const JSC::JSGlobalObject* globalObject)
 enum class DOMWindowType { Local, Remote };
 
 template <DOMWindowType windowType>
-bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject*, AbstractFrame*, JSC::ExecState*, JSC::PropertyName, JSC::PropertySlot&, const String&);
+bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject*, AbstractDOMWindow&, JSC::ExecState&, JSC::PropertyName, JSC::PropertySlot&, const String&);
 
-void addCrossOriginWindowOwnPropertyNames(JSC::ExecState&, JSC::PropertyNameArray&);
+void addCrossOriginWindowOwnPropertyNames(JSC::ExecState&, AbstractDOMWindow&, JSC::PropertyNameArray&);
 
 } // namespace WebCore
index b7f7ae9..8cd4252 100644 (file)
@@ -37,22 +37,19 @@ bool JSRemoteDOMWindow::getOwnPropertySlot(JSObject* object, ExecState* state, P
         return getOwnPropertySlotByIndex(object, state, index.value(), slot);
 
     auto* thisObject = jsCast<JSRemoteDOMWindow*>(object);
-    auto* frame = thisObject->wrapped().frame();
-
-    return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Remote>(thisObject, frame, state, propertyName, slot, String());
+    return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Remote>(thisObject, thisObject->wrapped(), *state, propertyName, slot, String());
 }
 
 bool JSRemoteDOMWindow::getOwnPropertySlotByIndex(JSObject* object, ExecState* state, unsigned index, PropertySlot& slot)
 {
     auto* thisObject = jsCast<JSRemoteDOMWindow*>(object);
-    auto* frame = thisObject->wrapped().frame();
 
     // Indexed getters take precendence over regular properties, so caching would be invalid.
     slot.disableCaching();
 
     // FIXME: Add support for indexed properties.
 
-    return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Remote>(thisObject, frame, state, Identifier::from(state, index), slot, String());
+    return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Remote>(thisObject, thisObject->wrapped(), *state, Identifier::from(state, index), slot, String());
 }
 
 bool JSRemoteDOMWindow::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
@@ -100,12 +97,14 @@ bool JSRemoteDOMWindow::deletePropertyByIndex(JSCell*, ExecState* state, unsigne
     return false;
 }
 
-void JSRemoteDOMWindow::getOwnPropertyNames(JSObject*, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
+void JSRemoteDOMWindow::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
 {
+    auto* thisObject = jsCast<JSRemoteDOMWindow*>(object);
+
     // FIXME: Add scoped children indexes.
 
     if (mode.includeDontEnumProperties())
-        addCrossOriginWindowOwnPropertyNames(*exec, propertyNames);
+        addCrossOriginWindowOwnPropertyNames(*exec, thisObject->wrapped(), propertyNames);
 }
 
 bool JSRemoteDOMWindow::defineOwnProperty(JSC::JSObject*, JSC::ExecState* state, JSC::PropertyName, const JSC::PropertyDescriptor&, bool)
index 30a374c..b69c6dc 100644 (file)
@@ -4706,7 +4706,13 @@ sub GenerateAttributeGetterBodyDefinition
         !$attribute->extendedAttributes->{DoNotCheckSecurityOnGetter}) {
         AddToImplIncludes("JSDOMBindingSecurity.h", $conditional);
         if ($interface->type->name eq "DOMWindow") {
-            push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindow(&state, thisObject.wrapped(), ThrowSecurityError))\n");
+            if ($attribute->extendedAttributes->{DoNotCheckSecurityIf}) {
+                my $crossOriginOptions = GetCrossOriginsOptionsFromExtendedAttributeValue($attribute->extendedAttributes->{DoNotCheckSecurityIf});
+                AddToImplIncludes("HTTPParsers.h", $conditional);
+                push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindowGivenMinimumCrossOriginOptions(&state, thisObject.wrapped(), $crossOriginOptions, ThrowSecurityError))\n");
+            } else {
+                push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindow(&state, thisObject.wrapped(), ThrowSecurityError))\n");
+            }
         } else {
             push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToFrame(&state, thisObject.wrapped().frame(), ThrowSecurityError))\n");
         }
@@ -4814,6 +4820,15 @@ sub GenerateAttributeGetterDefinition
     push(@$outputArray, "#endif\n\n") if $conditional;
 }
 
+sub GetCrossOriginsOptionsFromExtendedAttributeValue
+{
+    my $extendedAttributeValue = shift;
+
+    return "CrossOriginOptions::Allow" if $extendedAttributeValue eq "CrossOriginOptionsAllow";
+    return "CrossOriginOptions::AllowPostMessage" if $extendedAttributeValue eq "CrossOriginOptionsAllowPostMessage";
+    die "Unsupported CrossOriginOptions: " + $extendedAttributeValue;
+}
+
 sub GenerateAttributeSetterBodyDefinition
 {
     my ($outputArray, $interface, $className, $attribute, $attributeSetterBodyName, $conditional) = @_;
@@ -4836,7 +4851,13 @@ sub GenerateAttributeSetterBodyDefinition
     if ($interface->extendedAttributes->{CheckSecurity} && !$attribute->extendedAttributes->{DoNotCheckSecurity} && !$attribute->extendedAttributes->{DoNotCheckSecurityOnSetter}) {
         AddToImplIncludes("JSDOMBindingSecurity.h", $conditional);
         if ($interface->type->name eq "DOMWindow") {
-            push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindow(&state, thisObject.wrapped(), ThrowSecurityError))\n");
+            if ($attribute->extendedAttributes->{DoNotCheckSecurityIf}) {
+                my $crossOriginOptions = GetCrossOriginsOptionsFromExtendedAttributeValue($attribute->extendedAttributes->{DoNotCheckSecurityIf});
+                AddToImplIncludes("HTTPParsers.h", $conditional);
+                push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindowGivenMinimumCrossOriginOptions(&state, thisObject.wrapped(), $crossOriginOptions, ThrowSecurityError))\n");
+            } else {
+                push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindow(&state, thisObject.wrapped(), ThrowSecurityError))\n");
+            }
         } else {
             push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToFrame(&state, thisObject.wrapped().frame(), ThrowSecurityError))\n");
         }
@@ -5057,7 +5078,13 @@ sub GenerateOperationBodyDefinition
             
             AddToImplIncludes("JSDOMBindingSecurity.h", $conditional);
             if ($interface->type->name eq "DOMWindow") {
-                push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindow(state, castedThis->wrapped(), ThrowSecurityError))\n");
+                if ($operation->extendedAttributes->{DoNotCheckSecurityIf}) {
+                    my $crossOriginOptions = GetCrossOriginsOptionsFromExtendedAttributeValue($operation->extendedAttributes->{DoNotCheckSecurityIf});
+                    AddToImplIncludes("HTTPParsers.h", $conditional);
+                    push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindowGivenMinimumCrossOriginOptions(state, castedThis->wrapped(), $crossOriginOptions, ThrowSecurityError))\n");
+                } else {
+                    push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToDOMWindow(state, castedThis->wrapped(), ThrowSecurityError))\n");
+                }
                 push(@$outputArray, "        return JSValue::encode(jsUndefined());\n");
             } else {
                 push(@$outputArray, "    if (!BindingSecurity::shouldAllowAccessToFrame(state, castedThis->wrapped().frame(), ThrowSecurityError))\n");
index c332b11..37d61e0 100644 (file)
         "DoNotCheckSecurity": {
             "contextsAllowed": ["attribute", "operation"]
         },
+        "DoNotCheckSecurityIf": {
+            "contextsAllowed": ["attribute", "operation"],
+            "values": ["CrossOriginOptionsAllow", "CrossOriginOptionsAllowPostMessage"]
+        },
         "DoNotCheckSecurityOnGetter": {
             "contextsAllowed": ["attribute"]
         },
index 95a3033..f8c3d9c 100644 (file)
@@ -517,6 +517,7 @@ Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsig
     , m_didAssociateFormControlsTimer(*this, &Document::didAssociateFormControlsTimerFired)
     , m_cookieCacheExpiryTimer(*this, &Document::invalidateDOMCookieCache)
     , m_socketProvider(page() ? &page()->socketProvider() : nullptr)
+    , m_crossOriginOptions { CrossOriginOptions::Allow }
     , m_isSynthesized(constructionFlags & Synthesized)
     , m_isNonRenderedPlaceholder(constructionFlags & NonRenderedPlaceholder)
     , m_orientationNotifier(currentOrientation(frame))
@@ -7806,4 +7807,11 @@ String Document::signedPublicKeyAndChallengeString(unsigned keySizeIndex, const
     return page->chrome().client().signedPublicKeyAndChallengeString(keySizeIndex, challengeString, url);
 }
 
+void Document::setCrossOriginOptions(CrossOriginOptions value)
+{
+    m_crossOriginOptions = value;
+    if (auto* window = domWindow())
+        window->setCrossOriginOptions(value);
+}
+
 } // namespace WebCore
index b9eff10..6d7c525 100644 (file)
@@ -194,6 +194,7 @@ class XPathResult;
 template<typename> class ExceptionOr;
 
 enum CollectionType;
+enum class CrossOriginOptions;
 enum class ShouldOpenExternalURLsPolicy;
 
 enum class RouteSharingPolicy;
@@ -1430,6 +1431,9 @@ public:
 
     String signedPublicKeyAndChallengeString(unsigned keySizeIndex, const String& challengeString, const URL&);
 
+    CrossOriginOptions crossOriginOptions() const { return m_crossOriginOptions; }
+    void setCrossOriginOptions(CrossOriginOptions value);
+
 protected:
     enum ConstructionFlags { Synthesized = 1, NonRenderedPlaceholder = 1 << 1 };
     Document(Frame*, const URL&, unsigned = DefaultDocumentClass, unsigned constructionFlags = 0);
@@ -1815,6 +1819,8 @@ private:
 
     unsigned m_writeRecursionDepth { 0 };
 
+    CrossOriginOptions m_crossOriginOptions;
+
     InheritedBool m_designMode { inherit };
     MediaProducer::MediaStateFlags m_mediaState { MediaProducer::IsNotPlaying };
     bool m_userHasInteractedWithMediaElement { false };
index 000b9a4..729f253 100644 (file)
@@ -742,6 +742,12 @@ void FrameLoader::didBeginDocument(bool dispatch)
             if (!headerContentLanguage.isEmpty())
                 m_frame.document()->setContentLanguage(headerContentLanguage);
         }
+
+        if (m_frame.settings().crossOriginOptionsSupportEnabled()) {
+            String crossOriginOptionsHeader = m_documentLoader->response().httpHeaderField(HTTPHeaderName::CrossOriginOptions);
+            if (!crossOriginOptionsHeader.isEmpty())
+                m_frame.document()->setCrossOriginOptions(parseCrossOriginOptionsHeader(crossOriginOptionsHeader));
+        }
     }
 
     history().restoreDocumentState();
index ba74519..582668f 100644 (file)
@@ -37,8 +37,9 @@ HashMap<GlobalWindowIdentifier, AbstractDOMWindow*>& AbstractDOMWindow::allWindo
     return map;
 }
 
-AbstractDOMWindow::AbstractDOMWindow(GlobalWindowIdentifier&& identifier)
+AbstractDOMWindow::AbstractDOMWindow(GlobalWindowIdentifier&& identifier, CrossOriginOptions crossOriginOptions)
     : m_identifier(WTFMove(identifier))
+    , m_crossOriginOptions(crossOriginOptions)
 {
     ASSERT(!allWindows().contains(identifier));
     allWindows().add(identifier, this);
index 5a5debd..e26c2b7 100644 (file)
@@ -35,6 +35,8 @@ namespace WebCore {
 
 class AbstractFrame;
 
+enum class CrossOriginOptions;
+
 // FIXME: Rename DOMWindow to LocalWindow and AbstractDOMWindow to DOMWindow.
 class AbstractDOMWindow : public RefCounted<AbstractDOMWindow>, public EventTargetWithInlineData {
 public:
@@ -52,8 +54,11 @@ public:
     using RefCounted::ref;
     using RefCounted::deref;
 
+    CrossOriginOptions crossOriginOptions() { return m_crossOriginOptions; }
+    void setCrossOriginOptions(CrossOriginOptions value) { m_crossOriginOptions = value; }
+
 protected:
-    explicit AbstractDOMWindow(GlobalWindowIdentifier&&);
+    AbstractDOMWindow(GlobalWindowIdentifier&&, CrossOriginOptions);
 
     EventTargetInterface eventTargetInterface() const final { return DOMWindowEventTargetInterfaceType; }
     void refEventTarget() final { ref(); }
@@ -61,6 +66,7 @@ protected:
 
 private:
     GlobalWindowIdentifier m_identifier;
+    CrossOriginOptions m_crossOriginOptions;
 };
 
 } // namespace WebCore
index e402a99..f4249d3 100644 (file)
@@ -402,7 +402,7 @@ void DOMWindow::setCanShowModalDialogOverride(bool allow)
 }
 
 DOMWindow::DOMWindow(Document& document)
-    : AbstractDOMWindow(GlobalWindowIdentifier { Process::identifier(), generateObjectIdentifier<WindowIdentifierType>() })
+    : AbstractDOMWindow(GlobalWindowIdentifier { Process::identifier(), generateObjectIdentifier<WindowIdentifierType>() }, document.crossOriginOptions())
     , ContextDestructionObserver(&document)
     , FrameDestructionObserver(document.frame())
 {
@@ -413,6 +413,7 @@ DOMWindow::DOMWindow(Document& document)
 void DOMWindow::didSecureTransitionTo(Document& document)
 {
     observeContext(&document);
+    setCrossOriginOptions(document.crossOriginOptions());
 }
 
 DOMWindow::~DOMWindow()
index e696178..cd412e9 100644 (file)
@@ -49,11 +49,11 @@ typedef USVString CSSOMString;
     PrimaryGlobal,
 ] interface DOMWindow : EventTarget {
     // The current browsing context.
-    [DoNotCheckSecurity, Unforgeable, ImplementedAs=self] readonly attribute WindowProxy window;
-    [Replaceable, DoNotCheckSecurityOnGetter] readonly attribute WindowProxy self;
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, Unforgeable, ImplementedAs=self] readonly attribute WindowProxy window;
+    [Replaceable, DoNotCheckSecurityIf=CrossOriginOptionsAllow] readonly attribute WindowProxy self;
     [Unforgeable] readonly attribute Document document;
     attribute DOMString name;
-    [DoNotCheckSecurity, PutForwards=href, Unforgeable] readonly attribute Location? location; // FIXME: Should not be nullable.
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, PutForwards=href, Unforgeable] readonly attribute Location? location; // FIXME: Should not be nullable.
     readonly attribute History history;
     [EnabledAtRuntime=CustomElements, ImplementedAs=ensureCustomElementRegistry] readonly attribute CustomElementRegistry customElements;
     [Replaceable] readonly attribute BarProp locationbar;
@@ -63,18 +63,18 @@ typedef USVString CSSOMString;
     [Replaceable] readonly attribute BarProp statusbar;
     [Replaceable] readonly attribute BarProp toolbar;
     attribute DOMString status;
-    [DoNotCheckSecurity, CallWith=IncumbentDocument, ForwardDeclareInHeader] void close();
-    [DoNotCheckSecurity, ForwardDeclareInHeader] readonly attribute boolean closed;
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, CallWith=IncumbentDocument, ForwardDeclareInHeader] void close();
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, ForwardDeclareInHeader] readonly attribute boolean closed;
     void stop();
-    [DoNotCheckSecurity, CallWith=IncumbentWindow, ForwardDeclareInHeader] void focus();
-    [DoNotCheckSecurity, ForwardDeclareInHeader] void blur();
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, CallWith=IncumbentWindow, ForwardDeclareInHeader] void focus();
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, ForwardDeclareInHeader] void blur();
 
     // Other browsing contexts.
-    [Replaceable, DoNotCheckSecurityOnGetter, ImplementedAs=self] readonly attribute WindowProxy frames;
-    [Replaceable, DoNotCheckSecurityOnGetter] readonly attribute unsigned long length;
-    [DoNotCheckSecurityOnGetter, Unforgeable] readonly attribute WindowProxy? top;
-    [DoNotCheckSecurityOnGetter, CustomSetter] attribute WindowProxy? opener;
-    [Replaceable, DoNotCheckSecurityOnGetter] readonly attribute WindowProxy? parent;
+    [Replaceable, DoNotCheckSecurityIf=CrossOriginOptionsAllow, ImplementedAs=self] readonly attribute WindowProxy frames;
+    [Replaceable, DoNotCheckSecurityIf=CrossOriginOptionsAllow] readonly attribute unsigned long length;
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, Unforgeable] readonly attribute WindowProxy? top;
+    [DoNotCheckSecurityIf=CrossOriginOptionsAllow, CustomSetter] attribute WindowProxy? opener;
+    [Replaceable, DoNotCheckSecurityIf=CrossOriginOptionsAllow] readonly attribute WindowProxy? parent;
     [CheckSecurityForNode] readonly attribute Element? frameElement;
     [CallWith=ActiveWindow&FirstWindow] WindowProxy? open(optional USVString url = "about:blank", optional DOMString target = "_blank", optional [TreatNullAs=EmptyString] DOMString features = "");
 
@@ -92,7 +92,7 @@ typedef USVString CSSOMString;
     long requestAnimationFrame(RequestAnimationFrameCallback callback); // FIXME: Should return an unsigned long.
     void cancelAnimationFrame(long handle); // FIXME: handle should be an unsigned long.
 
-    [CallWith=ScriptState&IncumbentWindow, DoNotCheckSecurity, ForwardDeclareInHeader, MayThrowException] void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer = []);
+    [CallWith=ScriptState&IncumbentWindow, DoNotCheckSecurityIf=CrossOriginOptionsAllowPostMessage, ForwardDeclareInHeader, MayThrowException] void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer = []);
 
     // Obsolete members, still part of the HTML specification (https://html.spec.whatwg.org/#Window-partial).
     void captureEvents(); // Not implemented. Also not in modern standards. Empty function may help compatibility with legacy content.
index 156ed12..25a34f8 100644 (file)
@@ -136,7 +136,7 @@ public:
 
     WEBCORE_EXPORT ~Frame();
 
-    DOMWindow* window() const;
+    WEBCORE_EXPORT DOMWindow* window() const;
 
     void addDestructionObserver(FrameDestructionObserver*);
     void removeDestructionObserver(FrameDestructionObserver*);
index 92f7793..03a925d 100644 (file)
@@ -32,8 +32,8 @@
 
 namespace WebCore {
 
-RemoteDOMWindow::RemoteDOMWindow(Ref<RemoteFrame>&& frame, GlobalWindowIdentifier&& identifier)
-    : AbstractDOMWindow(WTFMove(identifier))
+RemoteDOMWindow::RemoteDOMWindow(Ref<RemoteFrame>&& frame, GlobalWindowIdentifier&& identifier, CrossOriginOptions crossOriginOptions)
+    : AbstractDOMWindow(WTFMove(identifier), crossOriginOptions)
     , m_frame(WTFMove(frame))
 {
     m_frame->setWindow(this);
index cb8611c..ede2a75 100644 (file)
@@ -44,9 +44,9 @@ class Location;
 
 class RemoteDOMWindow final : public AbstractDOMWindow {
 public:
-    static Ref<RemoteDOMWindow> create(Ref<RemoteFrame>&& frame, GlobalWindowIdentifier&& identifier)
+    static Ref<RemoteDOMWindow> create(Ref<RemoteFrame>&& frame, GlobalWindowIdentifier&& identifier, CrossOriginOptions crossOriginOptions)
     {
-        return adoptRef(*new RemoteDOMWindow(WTFMove(frame), WTFMove(identifier)));
+        return adoptRef(*new RemoteDOMWindow(WTFMove(frame), WTFMove(identifier), crossOriginOptions));
     }
 
     ~RemoteDOMWindow() final;
@@ -68,7 +68,7 @@ public:
     void postMessage(JSC::ExecState&, DOMWindow& incumbentWindow, JSC::JSValue message, const String& targetOrigin, Vector<JSC::Strong<JSC::JSObject>>&&);
 
 private:
-    WEBCORE_EXPORT RemoteDOMWindow(Ref<RemoteFrame>&&, GlobalWindowIdentifier&&);
+    WEBCORE_EXPORT RemoteDOMWindow(Ref<RemoteFrame>&&, GlobalWindowIdentifier&&, CrossOriginOptions);
 
     bool isRemoteDOMWindow() const final { return true; }
     bool isLocalDOMWindow() const final { return false; }
index 5a2ca70..a032340 100644 (file)
@@ -742,6 +742,9 @@ clientCoordinatesRelativeToLayoutViewport:
   initial: false
   onChange: setNeedsRecalcStyleInAllFrames
 
+crossOriginOptionsSupportEnabled:
+  initial: true
+
 accessibilityEventsEnabled:
   initial: true
   conditional: ACCESSIBILITY_EVENTS
index 3e19285..9d2b396 100644 (file)
@@ -50,6 +50,7 @@ Content-Type
 Content-Range
 Cookie
 Cookie2
+Cross-Origin-Options
 Date
 DNT
 Default-Style
index c8f45e5..cf14322 100644 (file)
@@ -913,4 +913,19 @@ FromOriginDisposition parseFromOriginHeader(const String& header)
     return FromOriginDisposition::Invalid;
 }
 
+CrossOriginOptions parseCrossOriginOptionsHeader(StringView header)
+{
+    auto strippedHeader = stripLeadingAndTrailingHTTPSpaces(header);
+    if (strippedHeader.isEmpty())
+        return CrossOriginOptions::Allow;
+
+    if (equalLettersIgnoringASCIICase(strippedHeader, "deny"))
+        return CrossOriginOptions::Deny;
+
+    if (equalLettersIgnoringASCIICase(strippedHeader, "allow-postmessage"))
+        return CrossOriginOptions::AllowPostMessage;
+
+    return CrossOriginOptions::Allow;
+}
+
 }
index e14fc04..4bb4a73 100644 (file)
@@ -71,6 +71,13 @@ enum class FromOriginDisposition {
     Invalid
 };
 
+// Should be sorted from most restrictive to most permissive.
+enum class CrossOriginOptions {
+    Deny,
+    AllowPostMessage,
+    Allow,
+};
+
 bool isValidReasonPhrase(const String&);
 bool isValidHTTPHeaderValue(const String&);
 bool isValidAcceptHeaderValue(const String&);
@@ -111,6 +118,7 @@ bool isCrossOriginSafeRequestHeader(HTTPHeaderName, const String&);
 String normalizeHTTPMethod(const String&);
 
 WEBCORE_EXPORT FromOriginDisposition parseFromOriginHeader(const String&);
+CrossOriginOptions parseCrossOriginOptionsHeader(StringView);
 
 inline bool isHTTPSpace(UChar character)
 {
index 6c50dfe..be701a7 100644 (file)
@@ -1,3 +1,19 @@
+2018-05-09  Chris Dumez  <cdumez@apple.com>
+
+        Add initial support for 'Cross-Origin-Options' HTTP response header
+        https://bugs.webkit.org/show_bug.cgi?id=184996
+        <rdar://problem/39664620>
+
+        Reviewed by Geoff Garen.
+
+        * Shared/WebPreferences.yaml:
+        Add this as an experimental feature, on by default.
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::frameBecameRemote):
+        Make sure we pass the cross-origin options from the local Window
+        to the remote one when transitioning.
+
 2018-05-09  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Extra zoom mode] fast/viewport/extrazoom/viewport-change-min-device-width.html sometimes fails
index 3189575..cc6b498 100644 (file)
@@ -1097,6 +1097,13 @@ ConstantPropertiesEnabled:
   humanReadableDescription: "Enable CSS constant() values"
   category: experimental
 
+CrossOriginOptionsSupportEnabled:
+  type: bool
+  defaultValue: true
+  humanReadableName: "Cross-Origin-Options HTTP Header"
+  humanReadableDescription: "Enable support for Cross-Origin-Options HTTP Header"
+  category: experimental
+
 SpringTimingFunctionEnabled:
   type: bool
   defaultValue: DEFAULT_EXPERIMENTAL_FEATURES_ENABLED
index fdd2056..f5d55ef 100644 (file)
@@ -5905,8 +5905,13 @@ void WebPage::frameBecameRemote(uint64_t frameID, GlobalFrameIdentifier&& remote
     if (frame->page() != this)
         return;
 
+    auto* coreFrame = frame->coreFrame();
+    auto* previousWindow = coreFrame->window();
+    if (!previousWindow)
+        return;
+
     auto remoteFrame = RemoteFrame::create(WTFMove(remoteFrameIdentifier));
-    auto remoteWindow = RemoteDOMWindow::create(remoteFrame.copyRef(), WTFMove(remoteWindowIdentifier));
+    auto remoteWindow = RemoteDOMWindow::create(remoteFrame.copyRef(), WTFMove(remoteWindowIdentifier), previousWindow->crossOriginOptions());
     UNUSED_PARAM(remoteWindow);
 
     remoteFrame->setOpener(frame->coreFrame()->loader().opener());
@@ -5915,7 +5920,6 @@ void WebPage::frameBecameRemote(uint64_t frameID, GlobalFrameIdentifier&& remote
     remoteFrame->windowProxy().setJSWindowProxies(WTFMove(jsWindowProxies));
     remoteFrame->windowProxy().setDOMWindow(remoteWindow.ptr());
 
-    auto* coreFrame = frame->coreFrame();
     coreFrame->setView(nullptr);
     coreFrame->willDetachPage();
     coreFrame->detachFromPage();
index 5cc4a2b..71185d2 100644 (file)
 #define WebKitDataTransferItemsEnabledPreferenceKey @"WebKitDataTransferItemsEnabled"
 #define WebKitCustomPasteboardDataEnabledPreferenceKey @"WebKitCustomPasteboardDataEnabled"
 #define WebKitCacheAPIEnabledPreferenceKey @"WebKitCacheAPIEnabled"
+#define WebKitCrossOriginOptionsSupportEnabledPreferenceKey @"WebKitCrossOriginOptionsSupportEnabled"
 #define WebKitFetchAPIEnabledPreferenceKey @"WebKitFetchAPIEnabled"
 #define WebKitWritableStreamAPIEnabledPreferenceKey @"WebKitWritableStreamAPIEnabled"
 #define WebKitReadableByteStreamAPIEnabledPreferenceKey @"WebKitReadableByteStreamAPIEnabled"
index 6858597..a16022a 100644 (file)
@@ -634,6 +634,7 @@ public:
         [NSNumber numberWithBool:NO], WebKitWebGPUEnabledPreferenceKey,
 #endif
         [NSNumber numberWithBool:NO], WebKitCacheAPIEnabledPreferenceKey,
+        [NSNumber numberWithBool:NO], WebKitCrossOriginOptionsSupportEnabledPreferenceKey,
         [NSNumber numberWithBool:YES], WebKitFetchAPIEnabledPreferenceKey,
 
 #if ENABLE(STREAMS_API)
@@ -3009,6 +3010,16 @@ static NSString *classIBCreatorID = nil;
     [self _setBoolValue:flag forKey:WebKitCacheAPIEnabledPreferenceKey];
 }
 
+- (BOOL)crossOriginOptionsSupportEnabled
+{
+    return [self _boolValueForKey:WebKitCrossOriginOptionsSupportEnabledPreferenceKey];
+}
+
+- (void)setCrossOriginOptionsSupportEnabled:(BOOL)flag
+{
+    [self _setBoolValue:flag forKey:WebKitCrossOriginOptionsSupportEnabledPreferenceKey];
+}
+
 - (BOOL)fetchAPIEnabled
 {
     return [self _boolValueForKey:WebKitFetchAPIEnabledPreferenceKey];
index fdaba84..c90ce46 100644 (file)
@@ -543,6 +543,9 @@ extern NSString *WebPreferencesCacheModelChangedInternalNotification WEBKIT_DEPR
 - (BOOL)cacheAPIEnabled;
 - (void)setCacheAPIEnabled:(BOOL)enabled;
 
+- (BOOL)crossOriginOptionsSupportEnabled;
+- (void)setCrossOriginOptionsSupportEnabled:(BOOL)enabled;
+
 - (void)setFetchAPIEnabled:(BOOL)flag;
 - (BOOL)fetchAPIEnabled;
 
index fefadfc..335641d 100644 (file)
@@ -3062,6 +3062,7 @@ static bool needsSelfRetainWhileLoadingQuirk()
     settings.setViewportFitEnabled([preferences viewportFitEnabled]);
     settings.setConstantPropertiesEnabled([preferences constantPropertiesEnabled]);
     settings.setColorFilterEnabled([preferences colorFilterEnabled]);
+    settings.setCrossOriginOptionsSupportEnabled([preferences crossOriginOptionsSupportEnabled]);
 
 #if ENABLE(GAMEPAD)
     RuntimeEnabledFeatures::sharedFeatures().setGamepadsEnabled([preferences gamepadsEnabled]);
index 891a7d0..f33b471 100644 (file)
@@ -228,3 +228,10 @@ interface IWebPreferencesPrivate6 : IWebPreferencesPrivate5
     HRESULT menuItemElementEnabled([out, retval] BOOL* enabled);
     HRESULT setMenuItemElementEnabled([in] BOOL enabled);
 }
+
+[uuid(9A49D1DE-53DD-11E8-95E6-003EE1C28AB6)]
+interface IWebPreferencesPrivate7 : IWebPreferencesPrivate6
+{
+    HRESULT crossOriginOptionsSupportEnabled([out, retval] BOOL* enabled);
+    HRESULT setCrossOriginOptionsSupportEnabled([in] BOOL enabled);
+}
index 37d73e6..872b647 100644 (file)
 
 #define WebKitMenuItemElementEnabledPreferenceKey "WebKitMenuItemElementEnabled"
 
+#define WebKitCrossOriginOptionsSupportEnabledPreferenceKey "WebKitCrossOriginOptionsSupportEnabled"
+
 #define WebKitModernMediaControlsEnabledPreferenceKey "WebKitModernMediaControlsEnabled"
 
 #define WebKitWebAnimationsEnabledPreferenceKey "WebKitWebAnimationsEnabled"
index e460d13..b78c37a 100644 (file)
@@ -249,6 +249,7 @@ void WebPreferences::initializeDefaultSettings()
     CFDictionaryAddValue(defaults, CFSTR(WebKitShouldDisplaySubtitlesPreferenceKey), kCFBooleanFalse);
     CFDictionaryAddValue(defaults, CFSTR(WebKitShouldDisplayCaptionsPreferenceKey), kCFBooleanFalse);
     CFDictionaryAddValue(defaults, CFSTR(WebKitShouldDisplayTextDescriptionsPreferenceKey), kCFBooleanFalse);
+    CFDictionaryAddValue(defaults, CFSTR(WebKitCrossOriginOptionsSupportEnabledPreferenceKey), kCFBooleanFalse);
 
     RetainPtr<CFStringRef> linkBehaviorStringRef = adoptCF(CFStringCreateWithFormat(0, 0, CFSTR("%d"), WebKitEditableLinkDefaultBehavior));
     CFDictionaryAddValue(defaults, CFSTR(WebKitEditableLinkBehaviorPreferenceKey), linkBehaviorStringRef.get());
@@ -2029,6 +2030,20 @@ HRESULT WebPreferences::setMenuItemElementEnabled(BOOL enabled)
     return S_OK;
 }
 
+HRESULT WebPreferences::crossOriginOptionsSupportEnabled(_Out_ BOOL* enabled)
+{
+    if (!enabled)
+        return E_POINTER;
+    *enabled = boolValueForKey(WebKitCrossOriginOptionsSupportEnabledPreferenceKey);
+    return S_OK;
+}
+
+HRESULT WebPreferences::setCrossOriginOptionsSupportEnabled(BOOL enabled)
+{
+    setBoolValue(WebKitCrossOriginOptionsSupportEnabledPreferenceKey, enabled);
+    return S_OK;
+}
+
 HRESULT WebPreferences::setModernMediaControlsEnabled(BOOL enabled)
 {
     setBoolValue(WebKitModernMediaControlsEnabledPreferenceKey, enabled);
index ddd8dcc..fc3782a 100644 (file)
@@ -276,6 +276,10 @@ public:
     virtual HRESULT STDMETHODCALLTYPE menuItemElementEnabled(_Out_ BOOL*);
     virtual HRESULT STDMETHODCALLTYPE setMenuItemElementEnabled(BOOL);
 
+    // IWebPreferencesPrivate7
+    virtual HRESULT STDMETHODCALLTYPE crossOriginOptionsSupportEnabled(_Out_ BOOL*);
+    virtual HRESULT STDMETHODCALLTYPE setCrossOriginOptionsSupportEnabled(BOOL);
+
     // WebPreferences
 
     // This method accesses a different preference key than developerExtrasEnabled.
index d81c831..64de9ee 100644 (file)
@@ -5150,7 +5150,7 @@ HRESULT WebView::notifyPreferencesChanged(IWebNotification* notification)
     settings.setShouldDisplayTextDescriptions(enabled);
 #endif
 
-    COMPtr<IWebPreferencesPrivate6> prefsPrivate { Query, preferences };
+    COMPtr<IWebPreferencesPrivate7> prefsPrivate { Query, preferences };
     if (prefsPrivate) {
         hr = prefsPrivate->localStorageDatabasePath(&str);
         if (FAILED(hr))
@@ -5279,6 +5279,11 @@ HRESULT WebView::notifyPreferencesChanged(IWebNotification* notification)
         return hr;
     settings.setVisualViewportAPIEnabled(!!enabled);
 
+    hr = prefsPrivate->crossOriginOptionsSupportEnabled(&enabled);
+    if (FAILED(hr))
+        return hr;
+    settings.setCrossOriginOptionsSupportEnabled(!!enabled);
+
     hr = preferences->privateBrowsingEnabled(&enabled);
     if (FAILED(hr))
         return hr;
index 5ac28d2..73d5786 100644 (file)
@@ -862,6 +862,7 @@ static void enableExperimentalFeatures(WebPreferences* preferences)
     [preferences setAccessibilityObjectModelEnabled:YES];
     [preferences setVisualViewportAPIEnabled:YES];
     [preferences setColorFilterEnabled:YES];
+    [preferences setCrossOriginOptionsSupportEnabled:YES];
 }
 
 // Called before each test.
index ac6519f..cdc6d23 100644 (file)
@@ -788,6 +788,7 @@ static void enableExperimentalFeatures(IWebPreferences* preferences)
     prefsPrivate->setWebAnimationsEnabled(TRUE);
     // FIXME: WebGL2
     // FIXME: WebRTC
+    prefsPrivate->setCrossOriginOptionsSupportEnabled(TRUE);
 }
 
 static void resetWebPreferencesToConsistentValues(IWebPreferences* preferences)