Do process swap when opening a cross-origin URL via window.open(url, '_blank', 'noope...
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Mar 2018 21:21:43 +0000 (21:21 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Mar 2018 21:21:43 +0000 (21:21 +0000)
https://bugs.webkit.org/show_bug.cgi?id=183962
<rdar://problem/38817833>

Reviewed by Brady Eidson.

Source/WebCore:

Pass extra bits of information to the UIProcess via NavigationAction:
- Is it a cross origin navigation caused by window.open()
- Does the navigated frame have an opener

This information is useful to determine on UIProcess side if we want
to swap WebProcess.

* loader/FrameLoadRequest.h:
(WebCore::FrameLoadRequest::setIsCrossOriginWindowOpenNavigation):
(WebCore::FrameLoadRequest::isCrossOriginWindowOpenNavigation const):
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::loadURL):
* loader/NavigationAction.h:
(WebCore::NavigationAction::setIsCrossOriginWindowOpenNavigation):
(WebCore::NavigationAction::isCrossOriginWindowOpenNavigation const):
(WebCore::NavigationAction::setOpener):
(WebCore::NavigationAction::opener const):
* page/DOMWindow.cpp:
(WebCore::DOMWindow::createWindow):

Source/WebKit:

Swap WebProcess on for the initial navigation in a new Window that was opened
via window.open(), when the new URL is cross-origin compared to the opener's
origin. For now, we only swap process if 'noopener' property is set when calling
window.open(). This is because we do not support the remote DOMWindows yet.

* Shared/NavigationActionData.cpp:
(WebKit::NavigationActionData::encode const):
(WebKit::NavigationActionData::decode):
* Shared/NavigationActionData.h:
* UIProcess/API/APINavigation.h:
(API::Navigation::setIsCrossOriginWindowOpenNavigation):
(API::Navigation::isCrossOriginWindowOpenNavigation const):
(API::Navigation::setOpener):
(API::Navigation::opener const):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::receivedPolicyDecision):
(WebKit::WebPageProxy::decidePolicyForNavigationAction):
* UIProcess/WebProcessPool.cpp:
(WebKit::WebProcessPool::processForNavigation):
* UIProcess/WebProcessPool.h:
* WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
(WebKit::WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction):

Tools:

Add API test coverage.

* TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:
(-[PSONUIDelegate initWithNavigationDelegate:]):
(-[PSONUIDelegate webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:]):

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

15 files changed:
Source/WebCore/ChangeLog
Source/WebCore/loader/FrameLoadRequest.h
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/loader/NavigationAction.h
Source/WebCore/page/DOMWindow.cpp
Source/WebKit/ChangeLog
Source/WebKit/Shared/NavigationActionData.cpp
Source/WebKit/Shared/NavigationActionData.h
Source/WebKit/UIProcess/API/APINavigation.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebProcessPool.cpp
Source/WebKit/UIProcess/WebProcessPool.h
Source/WebKit/WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm

index 13e224d..8d9fe0a 100644 (file)
@@ -1,5 +1,33 @@
 2018-03-28  Chris Dumez  <cdumez@apple.com>
 
+        Do process swap when opening a cross-origin URL via window.open(url, '_blank', 'noopener')
+        https://bugs.webkit.org/show_bug.cgi?id=183962
+        <rdar://problem/38817833>
+
+        Reviewed by Brady Eidson.
+
+        Pass extra bits of information to the UIProcess via NavigationAction:
+        - Is it a cross origin navigation caused by window.open()
+        - Does the navigated frame have an opener
+
+        This information is useful to determine on UIProcess side if we want
+        to swap WebProcess.
+
+        * loader/FrameLoadRequest.h:
+        (WebCore::FrameLoadRequest::setIsCrossOriginWindowOpenNavigation):
+        (WebCore::FrameLoadRequest::isCrossOriginWindowOpenNavigation const):
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::loadURL):
+        * loader/NavigationAction.h:
+        (WebCore::NavigationAction::setIsCrossOriginWindowOpenNavigation):
+        (WebCore::NavigationAction::isCrossOriginWindowOpenNavigation const):
+        (WebCore::NavigationAction::setOpener):
+        (WebCore::NavigationAction::opener const):
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::createWindow):
+
+2018-03-28  Chris Dumez  <cdumez@apple.com>
+
         Thread safety issue in IDBFactory' shouldThrowSecurityException()
         https://bugs.webkit.org/show_bug.cgi?id=184064
 
index 2848dc9..055e8ac 100644 (file)
@@ -84,6 +84,9 @@ public:
 
     InitiatedByMainFrame initiatedByMainFrame() const { return m_initiatedByMainFrame; }
 
+    void setIsCrossOriginWindowOpenNavigation(bool value) { m_isCrossOriginWindowOpenNavigation = value; }
+    bool isCrossOriginWindowOpenNavigation() const { return m_isCrossOriginWindowOpenNavigation; }
+
 private:
     Ref<Document> m_requester;
     Ref<SecurityOrigin> m_requesterSecurityOrigin;
@@ -102,6 +105,7 @@ private:
     ShouldOpenExternalURLsPolicy m_shouldOpenExternalURLsPolicy { ShouldOpenExternalURLsPolicy::ShouldNotAllow };
     AtomicString m_downloadAttribute;
     InitiatedByMainFrame m_initiatedByMainFrame { InitiatedByMainFrame::Unknown };
+    bool m_isCrossOriginWindowOpenNavigation { false };
 };
 
 } // namespace WebCore
index d259e7f..8aa5c98 100644 (file)
@@ -1306,6 +1306,13 @@ void FrameLoader::loadURL(FrameLoadRequest&& frameLoadRequest, const String& ref
         return;
 
     NavigationAction action { frameLoadRequest.requester(), request, frameLoadRequest.initiatedByMainFrame(), newLoadType, isFormSubmission, event, frameLoadRequest.shouldOpenExternalURLsPolicy(), frameLoadRequest.downloadAttribute() };
+    action.setIsCrossOriginWindowOpenNavigation(frameLoadRequest.isCrossOriginWindowOpenNavigation());
+    if (auto* opener = this->opener()) {
+        auto pageID = opener->loader().client().pageID();
+        auto frameID = opener->loader().client().frameID();
+        if (pageID && frameID)
+            action.setOpener(std::make_pair(*pageID, *frameID));
+    }
 
     if (!targetFrame && !frameName.isEmpty()) {
         action = action.copyWithShouldOpenExternalURLsPolicy(shouldOpenExternalURLsPolicyToApply(m_frame, frameLoadRequest));
index 86be91a..a80a70c 100644 (file)
@@ -72,6 +72,12 @@ public:
 
     const AtomicString& downloadAttribute() const { return m_downloadAttribute; }
 
+    void setIsCrossOriginWindowOpenNavigation(bool value) { m_isCrossOriginWindowOpenNavigation = value; }
+    bool isCrossOriginWindowOpenNavigation() const { return m_isCrossOriginWindowOpenNavigation; }
+
+    void setOpener(std::optional<std::pair<uint64_t, uint64_t>>&& opener) { m_opener = WTFMove(opener); }
+    const std::optional<std::pair<uint64_t, uint64_t>>& opener() const { return m_opener; }
+
 private:
     RefPtr<Document> m_sourceDocument;
     ResourceRequest m_resourceRequest;
@@ -81,6 +87,8 @@ private:
     RefPtr<Event> m_event;
     RefPtr<UserGestureToken> m_userGestureToken { UserGestureIndicator::currentUserGesture() };
     AtomicString m_downloadAttribute;
+    bool m_isCrossOriginWindowOpenNavigation { false };
+    std::optional<std::pair<uint64_t /* pageID */, uint64_t /* frameID */>> m_opener;
 };
 
 } // namespace WebCore
index 2d423cf..d5e9d49 100644 (file)
@@ -2279,6 +2279,8 @@ RefPtr<Frame> DOMWindow::createWindow(const String& urlString, const AtomicStrin
     if (created) {
         ResourceRequest resourceRequest { completedURL, referrer, UseProtocolCachePolicy };
         FrameLoadRequest frameLoadRequest { *activeWindow.document(), activeWindow.document()->securityOrigin(), resourceRequest, ASCIILiteral("_self"), LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, activeDocument->shouldOpenExternalURLsPolicyToPropagate(), initiatedByMainFrame };
+        if (openerFrame.document() && !protocolHostAndPortAreEqual(openerFrame.document()->url(), frameLoadRequest.resourceRequest().url()))
+            frameLoadRequest.setIsCrossOriginWindowOpenNavigation(true);
         newFrame->loader().changeLocation(WTFMove(frameLoadRequest));
 
 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
index f0155da..be3bd9c 100644 (file)
@@ -1,3 +1,34 @@
+2018-03-28  Chris Dumez  <cdumez@apple.com>
+
+        Do process swap when opening a cross-origin URL via window.open(url, '_blank', 'noopener')
+        https://bugs.webkit.org/show_bug.cgi?id=183962
+        <rdar://problem/38817833>
+
+        Reviewed by Brady Eidson.
+
+        Swap WebProcess on for the initial navigation in a new Window that was opened
+        via window.open(), when the new URL is cross-origin compared to the opener's
+        origin. For now, we only swap process if 'noopener' property is set when calling
+        window.open(). This is because we do not support the remote DOMWindows yet.
+
+        * Shared/NavigationActionData.cpp:
+        (WebKit::NavigationActionData::encode const):
+        (WebKit::NavigationActionData::decode):
+        * Shared/NavigationActionData.h:
+        * UIProcess/API/APINavigation.h:
+        (API::Navigation::setIsCrossOriginWindowOpenNavigation):
+        (API::Navigation::isCrossOriginWindowOpenNavigation const):
+        (API::Navigation::setOpener):
+        (API::Navigation::opener const):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::receivedPolicyDecision):
+        (WebKit::WebPageProxy::decidePolicyForNavigationAction):
+        * UIProcess/WebProcessPool.cpp:
+        (WebKit::WebProcessPool::processForNavigation):
+        * UIProcess/WebProcessPool.h:
+        * WebProcess/WebCoreSupport/WebFrameLoaderClient.cpp:
+        (WebKit::WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction):
+
 2018-03-28  Per Arne Vollan  <pvollan@apple.com>
 
         Adopt WEBPROCESS_WINDOWSERVER_BLOCKING compiler guard in WebProcess.
index 987da07..600ad59 100644 (file)
@@ -47,6 +47,8 @@ void NavigationActionData::encode(IPC::Encoder& encoder) const
     encoder << downloadAttribute;
     encoder << clickLocationInRootViewCoordinates;
     encoder << isRedirect;
+    encoder << isCrossOriginWindowOpenNavigation;
+    encoder << opener;
 }
 
 std::optional<NavigationActionData> NavigationActionData::decode(IPC::Decoder& decoder)
@@ -95,7 +97,17 @@ std::optional<NavigationActionData> NavigationActionData::decode(IPC::Decoder& d
     if (!isRedirect)
         return std::nullopt;
 
-    return {{ WTFMove(navigationType), WTFMove(modifiers), WTFMove(mouseButton), WTFMove(syntheticClickType), WTFMove(*userGestureTokenIdentifier), WTFMove(*canHandleRequest), WTFMove(shouldOpenExternalURLsPolicy), WTFMove(*downloadAttribute), WTFMove(clickLocationInRootViewCoordinates), WTFMove(*isRedirect) }};
+    std::optional<bool> isCrossOriginWindowOpenNavigation;
+    decoder >> isCrossOriginWindowOpenNavigation;
+    if (!isCrossOriginWindowOpenNavigation)
+        return std::nullopt;
+
+    std::optional<std::optional<std::pair<uint64_t, uint64_t>>> opener;
+    decoder >> opener;
+    if (!opener)
+        return std::nullopt;
+
+    return {{ WTFMove(navigationType), WTFMove(modifiers), WTFMove(mouseButton), WTFMove(syntheticClickType), WTFMove(*userGestureTokenIdentifier), WTFMove(*canHandleRequest), WTFMove(shouldOpenExternalURLsPolicy), WTFMove(*downloadAttribute), WTFMove(clickLocationInRootViewCoordinates), WTFMove(*isRedirect), *isCrossOriginWindowOpenNavigation, WTFMove(*opener) }};
 }
 
 } // namespace WebKit
index b3182bd..32c2059 100644 (file)
@@ -50,6 +50,8 @@ struct NavigationActionData {
     WTF::String downloadAttribute;
     WebCore::FloatPoint clickLocationInRootViewCoordinates;
     bool isRedirect { false };
+    bool isCrossOriginWindowOpenNavigation { false };
+    std::optional<std::pair<uint64_t, uint64_t>> opener;
 };
 
 }
index f1f8957..07c48ee 100644 (file)
@@ -31,7 +31,7 @@
 
 namespace WebCore {
 enum class FrameLoadType;
-};
+}
 
 namespace WebKit {
 class WebBackForwardListItem;
@@ -75,6 +75,12 @@ public:
     void setShouldForceDownload(bool value) { m_shouldForceDownload = value; }
     bool shouldForceDownload() const { return m_shouldForceDownload; }
 
+    void setIsCrossOriginWindowOpenNavigation(bool value) { m_isCrossOriginWindowOpenNavigation = value; }
+    bool isCrossOriginWindowOpenNavigation() const { return m_isCrossOriginWindowOpenNavigation; }
+
+    void setOpener(const std::optional<std::pair<uint64_t, uint64_t>>& opener) { m_opener = opener; }
+    const std::optional<std::pair<uint64_t, uint64_t>>& opener() const { return m_opener; }
+
 #if !LOG_DISABLED
     WTF::String loggingURL() const;
 #endif
@@ -92,6 +98,8 @@ private:
 
     RefPtr<WebKit::WebBackForwardListItem> m_backForwardListItem;
     std::optional<WebCore::FrameLoadType> m_backForwardFrameLoadType;
+    bool m_isCrossOriginWindowOpenNavigation { false };
+    std::optional<std::pair<uint64_t, uint64_t>> m_opener;
 };
 
 } // namespace API
index 326d649..f8b60a0 100644 (file)
@@ -2351,11 +2351,10 @@ void WebPageProxy::receivedPolicyDecision(PolicyAction action, WebFrameProxy& fr
         ASSERT(activePolicyListener->listenerID() == listenerID);
 
         if (action == PolicyAction::Use && navigation) {
-            auto proposedProcess = process().processPool().processForNavigation(*this, navigation->request().url());
+            auto proposedProcess = process().processPool().processForNavigation(*this, *navigation, action);
             if (proposedProcess.ptr() != &process()) {
                 LOG(Loading, "Switching to new process for navigation %" PRIu64 " to url '%s' (WebBackForwardListItem %p)", navigation->navigationID(), navigation->loggingURL().utf8().data(), navigation->backForwardListItem());
 
-                action = PolicyAction::Suspend;
                 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), navigation = makeRef(*navigation), proposedProcess = WTFMove(proposedProcess)]() mutable {
                     continueNavigationInNewProcess(navigation.get(), WTFMove(proposedProcess));
                 });
@@ -3809,19 +3808,14 @@ void WebPageProxy::decidePolicyForNavigationAction(uint64_t frameID, const Secur
     
     uint64_t newNavigationID { 0 };
     Ref<WebFramePolicyListenerProxy> listener = frame->setUpPolicyListenerProxy(listenerID, PolicyListenerType::NavigationAction);
-    if (!navigationID) {
-        auto navigation = m_navigationState->createLoadRequestNavigation(ResourceRequest(request));
-        newNavigationID = navigation->navigationID();
-        navigation->setWasUserInitiated(!!navigationActionData.userGestureTokenIdentifier);
-        navigation->setShouldForceDownload(!navigationActionData.downloadAttribute.isNull());
-        listener->setNavigation(WTFMove(navigation));
-    } else {
-        auto& navigation = m_navigationState->navigation(navigationID);
-        newNavigationID = navigationID;
-        navigation.setWasUserInitiated(!!navigationActionData.userGestureTokenIdentifier);
-        navigation.setShouldForceDownload(!navigationActionData.downloadAttribute.isNull());
-        listener->setNavigation(navigation);
-    }
+    Ref<API::Navigation> navigation = navigationID ? makeRef(m_navigationState->navigation(navigationID)) : m_navigationState->createLoadRequestNavigation(ResourceRequest(request));
+
+    newNavigationID = navigation->navigationID();
+    navigation->setWasUserInitiated(!!navigationActionData.userGestureTokenIdentifier);
+    navigation->setShouldForceDownload(!navigationActionData.downloadAttribute.isNull());
+    navigation->setIsCrossOriginWindowOpenNavigation(navigationActionData.isCrossOriginWindowOpenNavigation);
+    navigation->setOpener(navigationActionData.opener);
+    listener->setNavigation(WTFMove(navigation));
 
 #if ENABLE(CONTENT_FILTERING)
     if (frame->didHandleContentFilterUnblockNavigation(request))
index 0ee2a91..b3799f0 100644 (file)
@@ -1965,15 +1965,26 @@ ServiceWorkerProcessProxy* WebProcessPool::serviceWorkerProcessProxyFromPageID(u
 }
 #endif
 
-Ref<WebProcessProxy> WebProcessPool::processForNavigation(WebPageProxy& page, const URL& targetURL)
+Ref<WebProcessProxy> WebProcessPool::processForNavigation(WebPageProxy& page, const API::Navigation& navigation, PolicyAction& action)
 {
     if (!m_configuration->processSwapsOnNavigation())
         return page.process();
 
+    // FIXME: We should support process swap when a window has an opener.
+    if (navigation.opener())
+        return page.process();
+
+    if (navigation.isCrossOriginWindowOpenNavigation()) {
+        action = PolicyAction::Ignore;
+        return createNewWebProcess(page.websiteDataStore());
+    }
+
+    auto targetURL = navigation.request().url();
     auto url = URL { ParsedURLString, page.pageLoadState().url() };
-    if (protocolHostAndPortAreEqual(url, targetURL) || url.isBlankURL())
+    if (!url.isValid() || url.isBlankURL() || protocolHostAndPortAreEqual(url, targetURL))
         return page.process();
 
+    action = PolicyAction::Suspend;
     return createNewWebProcess(page.websiteDataStore());
 }
 
index 895fc33..85838aa 100644 (file)
@@ -445,7 +445,7 @@ public:
     BackgroundWebProcessToken backgroundWebProcessToken() const { return BackgroundWebProcessToken(m_backgroundWebProcessCounter.count()); }
 #endif
 
-    Ref<WebProcessProxy> processForNavigation(WebPageProxy&, const WebCore::URL&);
+    Ref<WebProcessProxy> processForNavigation(WebPageProxy&, const API::Navigation&, WebCore::PolicyAction&);
 
 private:
     void platformInitialize();
index af38668..fab290a 100644 (file)
@@ -861,6 +861,8 @@ void WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction(const Navigat
     navigationActionData.shouldOpenExternalURLsPolicy = navigationAction.shouldOpenExternalURLsPolicy();
     navigationActionData.downloadAttribute = navigationAction.downloadAttribute();
     navigationActionData.isRedirect = didReceiveRedirectResponse;
+    navigationActionData.isCrossOriginWindowOpenNavigation = navigationAction.isCrossOriginWindowOpenNavigation();
+    navigationActionData.opener = navigationAction.opener();
 
     WebCore::Frame* coreFrame = m_frame->coreFrame();
     if (!coreFrame)
index 9bbd816..3efd692 100644 (file)
@@ -1,3 +1,17 @@
+2018-03-28  Chris Dumez  <cdumez@apple.com>
+
+        Do process swap when opening a cross-origin URL via window.open(url, '_blank', 'noopener')
+        https://bugs.webkit.org/show_bug.cgi?id=183962
+        <rdar://problem/38817833>
+
+        Reviewed by Brady Eidson.
+
+        Add API test coverage.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:
+        (-[PSONUIDelegate initWithNavigationDelegate:]):
+        (-[PSONUIDelegate webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:]):
+
 2018-03-28  Carlos Eduardo Ramalho  <cadubentzen@gmail.com>
 
         Add Carlos Eduardo Ramalho as contributor
index f222cbe..53bff6d 100644 (file)
@@ -30,6 +30,7 @@
 #import <WebKit/WKNavigationDelegate.h>
 #import <WebKit/WKPreferencesPrivate.h>
 #import <WebKit/WKProcessPoolPrivate.h>
+#import <WebKit/WKUIDelegatePrivate.h>
 #import <WebKit/WKURLSchemeHandler.h>
 #import <WebKit/WKURLSchemeTaskPrivate.h>
 #import <WebKit/WKWebViewConfigurationPrivate.h>
@@ -40,6 +41,7 @@
 #import <WebKit/_WKExperimentalFeature.h>
 #import <WebKit/_WKProcessPoolConfiguration.h>
 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
+#import <WebKit/_WKWebsitePolicies.h>
 #import <wtf/Deque.h>
 #import <wtf/HashMap.h>
 #import <wtf/RetainPtr.h>
@@ -50,6 +52,7 @@
 #if WK_API_ENABLED
 
 static bool done;
+static bool didCreateWebView;
 static int numberOfDecidePolicyCalls;
 
 static RetainPtr<NSMutableArray> receivedMessages = adoptNS([@[] mutableCopy]);
@@ -83,6 +86,35 @@ static bool receivedMessage;
 
 @end
 
+static RetainPtr<WKWebView> createdWebView;
+
+@interface PSONUIDelegate : NSObject <WKUIDelegatePrivate>
+- (instancetype)initWithNavigationDelegate:(PSONNavigationDelegate *)navigationDelegate;
+@end
+
+@implementation PSONUIDelegate {
+    RetainPtr<PSONNavigationDelegate> _navigationDelegate;
+}
+
+- (instancetype)initWithNavigationDelegate:(PSONNavigationDelegate *)navigationDelegate
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _navigationDelegate = navigationDelegate;
+    return self;
+}
+
+- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+    createdWebView = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration]);
+    [createdWebView setNavigationDelegate:_navigationDelegate.get()];
+    didCreateWebView = true;
+    return createdWebView.get();
+}
+
+@end
+
 @interface PSONScheme : NSObject <WKURLSchemeHandler> {
     const char* _bytes;
 }
@@ -140,6 +172,35 @@ window.onpageshow = function(evt) {
 </head>
 )PSONRESOURCE";
 
+static const char* windowOpenCrossOriginNoOpenerTestBytes = R"PSONRESOURCE(
+<script>
+window.onload = function() {
+    window.open("pson2://host/main2.html", "_blank", "noopener");
+}
+</script>
+)PSONRESOURCE";
+
+static const char* windowOpenCrossOriginWithOpenerTestBytes = R"PSONRESOURCE(
+<script>
+window.onload = function() {
+    window.open("pson2://host/main2.html");
+}
+</script>
+)PSONRESOURCE";
+
+static const char* windowOpenSameOriginNoOpenerTestBytes = R"PSONRESOURCE(
+<script>
+window.onload = function() {
+    if (!opener)
+        window.open("pson1://host/main2.html", "_blank", "noopener");
+}
+</script>
+)PSONRESOURCE";
+
+static const char* dummyBytes = R"PSONRESOURCE(
+<body>TEST</body>
+)PSONRESOURCE";
+
 TEST(ProcessSwap, Basic)
 {
     auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
@@ -253,4 +314,126 @@ TEST(ProcessSwap, Back)
     EXPECT_FALSE(pid1 == pid3);
 }
 
+TEST(ProcessSwap, CrossOriginWindowOpenNoOpener)
+{
+    auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
+    processPoolConfiguration.get().processSwapsOnNavigation = YES;
+    auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
+
+    auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [webViewConfiguration setProcessPool:processPool.get()];
+    RetainPtr<PSONScheme> handler1 = adoptNS([[PSONScheme alloc] initWithBytes:windowOpenCrossOriginNoOpenerTestBytes]);
+    RetainPtr<PSONScheme> handler2 = adoptNS([[PSONScheme alloc] initWithBytes:dummyBytes]);
+    [webViewConfiguration setURLSchemeHandler:handler1.get() forURLScheme:@"PSON1"];
+    [webViewConfiguration setURLSchemeHandler:handler2.get() forURLScheme:@"PSON2"];
+
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
+    auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
+    [webView setNavigationDelegate:navigationDelegate.get()];
+    auto uiDelegate = adoptNS([[PSONUIDelegate alloc] initWithNavigationDelegate:navigationDelegate.get()]);
+    [webView setUIDelegate:uiDelegate.get()];
+
+    numberOfDecidePolicyCalls = 0;
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    TestWebKitAPI::Util::run(&didCreateWebView);
+    didCreateWebView = false;
+
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_EQ(2, numberOfDecidePolicyCalls);
+
+    auto pid1 = [webView _webProcessIdentifier];
+    EXPECT_TRUE(!!pid1);
+    auto pid2 = [createdWebView _webProcessIdentifier];
+    EXPECT_TRUE(!!pid2);
+
+    EXPECT_NE(pid1, pid2);
+}
+
+TEST(ProcessSwap, CrossOriginWindowOpenWithOpener)
+{
+    auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
+    processPoolConfiguration.get().processSwapsOnNavigation = YES;
+    auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
+
+    auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [webViewConfiguration setProcessPool:processPool.get()];
+    RetainPtr<PSONScheme> handler1 = adoptNS([[PSONScheme alloc] initWithBytes:windowOpenCrossOriginWithOpenerTestBytes]);
+    RetainPtr<PSONScheme> handler2 = adoptNS([[PSONScheme alloc] initWithBytes:dummyBytes]);
+    [webViewConfiguration setURLSchemeHandler:handler1.get() forURLScheme:@"PSON1"];
+    [webViewConfiguration setURLSchemeHandler:handler2.get() forURLScheme:@"PSON2"];
+
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
+    auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
+    [webView setNavigationDelegate:navigationDelegate.get()];
+    auto uiDelegate = adoptNS([[PSONUIDelegate alloc] initWithNavigationDelegate:navigationDelegate.get()]);
+    [webView setUIDelegate:uiDelegate.get()];
+
+    numberOfDecidePolicyCalls = 0;
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    TestWebKitAPI::Util::run(&didCreateWebView);
+    didCreateWebView = false;
+
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_EQ(2, numberOfDecidePolicyCalls);
+
+    auto pid1 = [webView _webProcessIdentifier];
+    EXPECT_TRUE(!!pid1);
+    auto pid2 = [createdWebView _webProcessIdentifier];
+    EXPECT_TRUE(!!pid2);
+
+    // FIXME: This should eventually be false once we support process swapping when there is an opener.
+    EXPECT_EQ(pid1, pid2);
+}
+
+TEST(ProcessSwap, SamOriginWindowOpenNoOpener)
+{
+    auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
+    processPoolConfiguration.get().processSwapsOnNavigation = YES;
+    auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
+
+    auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [webViewConfiguration setProcessPool:processPool.get()];
+    RetainPtr<PSONScheme> handler = adoptNS([[PSONScheme alloc] initWithBytes:windowOpenSameOriginNoOpenerTestBytes]);
+    [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON1"];
+
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
+    auto navigationDelegate = adoptNS([[PSONNavigationDelegate alloc] init]);
+    [webView setNavigationDelegate:navigationDelegate.get()];
+    auto uiDelegate = adoptNS([[PSONUIDelegate alloc] initWithNavigationDelegate:navigationDelegate.get()]);
+    [webView setUIDelegate:uiDelegate.get()];
+
+    numberOfDecidePolicyCalls = 0;
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    TestWebKitAPI::Util::run(&didCreateWebView);
+    didCreateWebView = false;
+
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_EQ(2, numberOfDecidePolicyCalls);
+
+    auto pid1 = [webView _webProcessIdentifier];
+    EXPECT_TRUE(!!pid1);
+    auto pid2 = [createdWebView _webProcessIdentifier];
+    EXPECT_TRUE(!!pid2);
+
+    EXPECT_EQ(pid1, pid2);
+}
+
 #endif // WK_API_ENABLED