First piece of process swapping on navigation.
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Mar 2018 00:05:58 +0000 (00:05 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Mar 2018 00:05:58 +0000 (00:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=183665

Reviewed by Andy Estes.

Source/WebCore:

Covered by API test(s)

This patch:
- A new PolicyAction::Suspend for future use in this feature
- Makes sure that loads triggered as part of a process swap do *not* re-consult the policy delegate

* loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::continueAfterContentPolicy):

* loader/FrameLoadRequest.h:
(WebCore::FrameLoadRequest::setShouldCheckNavigationPolicy):
(WebCore::FrameLoadRequest::shouldCheckNavigationPolicy const):

* loader/FrameLoader.cpp:
(WebCore::FrameLoader::load):
(WebCore::FrameLoader::loadWithDocumentLoader):
* loader/FrameLoader.h:

* loader/FrameLoaderTypes.h: Add a new Policy type "Suspend" to be used in the future
  by the process-swap-on-navigation mechanism.

* loader/PolicyChecker.cpp:
(WebCore::PolicyChecker::checkNavigationPolicy):
(WebCore::PolicyChecker::checkNewWindowPolicy):

Source/WebKit:

This patch adds the first pieces of the following feature:
"When a navigation originating inside a WKWebView goes to a different origin,
 swap to a new WebProcess for that navigation"

There are significant bugs to be resolved and significant optimizations to be made.
Which is why the feature is disabled by default.

Besides the core logic implementing the feature, this patch does a lot of related
work such as:
- Removing some now-invalid ASSERTs
- Adding some ASSERTs
- Update various switch states to handle the new "Suspend" policy and "NavigationSwap"
  process termination reason

* NetworkProcess/NetworkDataTaskBlob.cpp:
(WebKit::NetworkDataTaskBlob::dispatchDidReceiveResponse):

* NetworkProcess/capture/NetworkDataTaskReplay.cpp:
(WebKit::NetworkCapture::NetworkDataTaskReplay::didReceiveResponse):

* NetworkProcess/cocoa/NetworkSessionCocoa.mm:
(toNSURLSessionResponseDisposition):

* Platform/Logging.h:

* Shared/LoadParameters.cpp:
(WebKit::LoadParameters::encode const):
(WebKit::LoadParameters::decode):
* Shared/LoadParameters.h:

* Shared/ProcessTerminationReason.h: Add "NavigationSwap" as a process termination reason.

* UIProcess/API/APINavigation.h:

* UIProcess/API/APIProcessPoolConfiguration.cpp:
(API::ProcessPoolConfiguration::copy):

* UIProcess/API/C/WKAPICast.h:
(WebKit::toAPI):

* UIProcess/Cocoa/NavigationState.mm:
(WebKit::wkProcessTerminationReason):

* UIProcess/WebFramePolicyListenerProxy.cpp:
(WebKit::WebFramePolicyListenerProxy::WebFramePolicyListenerProxy):
* UIProcess/WebFramePolicyListenerProxy.h:
(WebKit::WebFramePolicyListenerProxy::create):
(WebKit::WebFramePolicyListenerProxy::policyListenerType const):

* UIProcess/WebFrameProxy.cpp:
(WebKit::WebFrameProxy::setUpPolicyListenerProxy):
(WebKit::WebFrameProxy::activePolicyListenerProxy):
* UIProcess/WebFrameProxy.h:

* UIProcess/WebNavigationState.cpp:
(WebKit::WebNavigationState::navigation):
(WebKit::WebNavigationState::takeNavigation):

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::reattachToWebProcess):
(WebKit::WebPageProxy::attachToProcessForNavigation): Pretend that the existing process
  terminated using the new "NavigationSwap" reason, then manually start the next load.
(WebKit::WebPageProxy::loadRequest):
(WebKit::WebPageProxy::loadRequestWithNavigation):
(WebKit::WebPageProxy::receivedPolicyDecision):
(WebKit::WebPageProxy::continueNavigationInNewProcess):
(WebKit::WebPageProxy::decidePolicyForNavigationAction):
(WebKit::WebPageProxy::decidePolicyForResponse):
(WebKit::WebPageProxy::processDidTerminate):
(WebKit::WebPageProxy::resetState):
(WebKit::WebPageProxy::resetStateAfterProcessExited):
* UIProcess/WebPageProxy.h:

* UIProcess/WebProcessPool.cpp:
(WebKit::WebProcessPool::processForNavigation): Determine which process should be used
  for a proposed navigation, creating a new one if necessary.
* UIProcess/WebProcessPool.h:

* UIProcess/WebStorage/StorageManager.cpp:
(WebKit::StorageManager::SessionStorageNamespace::setAllowedConnection):

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::loadRequest):

Tools:

Expose the "swaps processes on navigation" setting in MiniBrowser UI for testing:

* MiniBrowser/mac/AppDelegate.m:
(defaultConfiguration):
* MiniBrowser/mac/SettingsController.h:
* MiniBrowser/mac/SettingsController.m:
(-[SettingsController _populateMenu]):
(-[SettingsController validateMenuItem:]):
(-[SettingsController processSwapOnNavigationEnabled]):
(-[SettingsController toggleProcessSwapOnNavigation:]):

Makes sure the current behavior is tested:
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm: Added.
(-[PSONNavigationDelegate webView:didFinishNavigation:]):
(-[PSONScheme webView:startURLSchemeTask:]):
(-[PSONScheme webView:stopURLSchemeTask:]):
(TEST):

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

36 files changed:
Source/WebCore/ChangeLog
Source/WebCore/loader/DocumentLoader.cpp
Source/WebCore/loader/FrameLoadRequest.h
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/loader/FrameLoader.h
Source/WebCore/loader/FrameLoaderTypes.h
Source/WebCore/loader/PolicyChecker.cpp
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/NetworkDataTaskBlob.cpp
Source/WebKit/NetworkProcess/capture/NetworkDataTaskReplay.cpp
Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm
Source/WebKit/Platform/Logging.h
Source/WebKit/Shared/LoadParameters.cpp
Source/WebKit/Shared/LoadParameters.h
Source/WebKit/Shared/ProcessTerminationReason.h
Source/WebKit/UIProcess/API/APINavigation.h
Source/WebKit/UIProcess/API/APIProcessPoolConfiguration.cpp
Source/WebKit/UIProcess/API/C/WKAPICast.h
Source/WebKit/UIProcess/Cocoa/NavigationState.mm
Source/WebKit/UIProcess/WebFramePolicyListenerProxy.cpp
Source/WebKit/UIProcess/WebFramePolicyListenerProxy.h
Source/WebKit/UIProcess/WebFrameProxy.cpp
Source/WebKit/UIProcess/WebFrameProxy.h
Source/WebKit/UIProcess/WebNavigationState.cpp
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebProcessPool.cpp
Source/WebKit/UIProcess/WebProcessPool.h
Source/WebKit/UIProcess/WebStorage/StorageManager.cpp
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Tools/ChangeLog
Tools/MiniBrowser/mac/AppDelegate.m
Tools/MiniBrowser/mac/SettingsController.h
Tools/MiniBrowser/mac/SettingsController.m
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm [new file with mode: 0644]

index e71a5ff..1d6a295 100644 (file)
@@ -1,3 +1,35 @@
+2018-03-20  Brady Eidson  <beidson@apple.com>
+
+        First piece of process swapping on navigation.
+        https://bugs.webkit.org/show_bug.cgi?id=183665
+
+        Reviewed by Andy Estes.
+
+        Covered by API test(s)
+
+        This patch:
+        - A new PolicyAction::Suspend for future use in this feature
+        - Makes sure that loads triggered as part of a process swap do *not* re-consult the policy delegate
+
+        * loader/DocumentLoader.cpp:
+        (WebCore::DocumentLoader::continueAfterContentPolicy):
+
+        * loader/FrameLoadRequest.h:
+        (WebCore::FrameLoadRequest::setShouldCheckNavigationPolicy):
+        (WebCore::FrameLoadRequest::shouldCheckNavigationPolicy const):
+
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::load):
+        (WebCore::FrameLoader::loadWithDocumentLoader):
+        * loader/FrameLoader.h:
+
+        * loader/FrameLoaderTypes.h: Add a new Policy type "Suspend" to be used in the future
+          by the process-swap-on-navigation mechanism.
+
+        * loader/PolicyChecker.cpp:
+        (WebCore::PolicyChecker::checkNavigationPolicy):
+        (WebCore::PolicyChecker::checkNewWindowPolicy):
+
 2018-03-20  Chris Dumez  <cdumez@apple.com>
 
         QuickLook.NavigationDelegate API test is failing on iOS with async policy delegates
index 466030d..fc118dc 100644 (file)
@@ -896,6 +896,9 @@ void DocumentLoader::continueAfterContentPolicy(PolicyAction policy)
             static_cast<ResourceLoader*>(mainResourceLoader())->didFail(interruptedForPolicyChangeError());
         return;
     }
+    case PolicyAction::Suspend:
+        // It is invalid to get a Suspend policy based on navigation response.
+        RELEASE_ASSERT_NOT_REACHED();
     case PolicyAction::Ignore:
         if (ResourceLoader* mainResourceLoader = this->mainResourceLoader())
             InspectorInstrumentation::continueWithPolicyIgnore(*m_frame, mainResourceLoader->identifier(), *this, m_response);
index 857b035..2848dc9 100644 (file)
@@ -60,6 +60,9 @@ public:
     void setShouldCheckNewWindowPolicy(bool checkPolicy) { m_shouldCheckNewWindowPolicy = checkPolicy; }
     bool shouldCheckNewWindowPolicy() const { return m_shouldCheckNewWindowPolicy; }
 
+    void setShouldCheckNavigationPolicy(bool checkPolicy) { m_shouldCheckNavigationPolicy = checkPolicy; }
+    bool shouldCheckNavigationPolicy() const { return m_shouldCheckNavigationPolicy; }
+
     const SubstituteData& substituteData() const { return m_substituteData; }
     void setSubstituteData(const SubstituteData& data) { m_substituteData = data; }
     bool hasSubstituteData() { return m_substituteData.isValid(); }
@@ -89,6 +92,7 @@ private:
     SubstituteData m_substituteData;
 
     bool m_shouldCheckNewWindowPolicy { false };
+    bool m_shouldCheckNavigationPolicy { true };
     LockHistory m_lockHistory;
     LockBackForwardList m_lockBackForwardList;
     ShouldSendReferrer m_shouldSendReferrer;
index 16b1525..61d1223 100644 (file)
 #include "XMLDocumentParser.h"
 #include <wtf/CompletionHandler.h>
 #include <wtf/Ref.h>
+#include <wtf/SetForScope.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/SystemTracing.h>
 #include <wtf/text/CString.h>
@@ -1403,6 +1404,7 @@ void FrameLoader::load(FrameLoadRequest&& request)
     Ref<DocumentLoader> loader = m_client.createDocumentLoader(request.resourceRequest(), request.substituteData());
     applyShouldOpenExternalURLsPolicyToNewDocumentLoader(m_frame, loader, request);
 
+    SetForScope<bool> currentLoadShouldCheckNavigationPolicyGuard(m_currentLoadShouldCheckNavigationPolicy, request.shouldCheckNavigationPolicy());
     load(loader.ptr());
 }
 
@@ -1538,6 +1540,11 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t
 
     m_frame.navigationScheduler().cancel(true);
 
+    if (!m_currentLoadShouldCheckNavigationPolicy) {
+        continueLoadAfterNavigationPolicy(loader->request(), formState, true, allowNavigationToInvalidURL);
+        return;
+    }
+
     policyChecker().checkNavigationPolicy(ResourceRequest(loader->request()), false /* didReceiveRedirectResponse */, loader, formState, [this, protectedFrame = makeRef(m_frame), allowNavigationToInvalidURL, completionHandler = completionHandlerCaller.release()] (const ResourceRequest& request, FormState* formState, bool shouldContinue) {
         continueLoadAfterNavigationPolicy(request, formState, shouldContinue, allowNavigationToInvalidURL);
         completionHandler();
index 23f4754..8e4b95e 100644 (file)
@@ -461,6 +461,7 @@ private:
     std::optional<ResourceRequestCachePolicy> m_overrideCachePolicyForTesting;
     std::optional<ResourceLoadPriority> m_overrideResourceLoadPriorityForTesting;
     bool m_isStrictRawResourceValidationPolicyDisabledForTesting { false };
+    bool m_currentLoadShouldCheckNavigationPolicy { true };
 
     URL m_previousURL;
     RefPtr<HistoryItem> m_requestedHistoryItem;
index 0a47180..59b6a62 100644 (file)
@@ -41,7 +41,8 @@ enum FrameState {
 enum class PolicyAction {
     Use,
     Download,
-    Ignore
+    Ignore,
+    Suspend,
 };
 
 enum class ReloadOption {
@@ -158,7 +159,8 @@ template<> struct EnumTraits<WebCore::PolicyAction> {
         WebCore::PolicyAction,
         WebCore::PolicyAction::Use,
         WebCore::PolicyAction::Download,
-        WebCore::PolicyAction::Ignore
+        WebCore::PolicyAction::Ignore,
+        WebCore::PolicyAction::Suspend
     >;
 };
 
index 17c0f4c..2650df6 100644 (file)
@@ -159,6 +159,9 @@ void PolicyChecker::checkNavigationPolicy(ResourceRequest&& request, bool didRec
             FALLTHROUGH;
         case PolicyAction::Ignore:
             return function({ }, nullptr, false);
+        case PolicyAction::Suspend:
+            LOG(Loading, "PolicyAction::Suspend encountered - Treating as PolicyAction::Ignore for now");
+            return function({ }, nullptr, false);
         case PolicyAction::Use:
             if (!m_frame.loader().client().canHandleRequest(request)) {
                 handleUnimplementablePolicy(m_frame.loader().client().cannotShowURLError(request));
@@ -186,6 +189,9 @@ void PolicyChecker::checkNewWindowPolicy(NavigationAction&& navigationAction, co
         case PolicyAction::Ignore:
             function({ }, nullptr, { }, { }, false);
             return;
+        case PolicyAction::Suspend:
+            // It is invalid to get a "Suspend" policy for new windows, as the old document is not going away.
+            RELEASE_ASSERT_NOT_REACHED();
         case PolicyAction::Use:
             function(request, formState.get(), frameName, navigationAction, true);
             return;
index bbb6505..1329b6c 100644 (file)
@@ -1,3 +1,94 @@
+2018-03-20  Brady Eidson  <beidson@apple.com>
+
+        First piece of process swapping on navigation.
+        https://bugs.webkit.org/show_bug.cgi?id=183665
+
+        Reviewed by Andy Estes.
+
+        This patch adds the first pieces of the following feature:
+        "When a navigation originating inside a WKWebView goes to a different origin,
+         swap to a new WebProcess for that navigation"
+
+        There are significant bugs to be resolved and significant optimizations to be made.
+        Which is why the feature is disabled by default.
+
+        Besides the core logic implementing the feature, this patch does a lot of related 
+        work such as:
+        - Removing some now-invalid ASSERTs
+        - Adding some ASSERTs
+        - Update various switch states to handle the new "Suspend" policy and "NavigationSwap"
+          process termination reason
+
+        * NetworkProcess/NetworkDataTaskBlob.cpp:
+        (WebKit::NetworkDataTaskBlob::dispatchDidReceiveResponse):
+
+        * NetworkProcess/capture/NetworkDataTaskReplay.cpp:
+        (WebKit::NetworkCapture::NetworkDataTaskReplay::didReceiveResponse):
+
+        * NetworkProcess/cocoa/NetworkSessionCocoa.mm:
+        (toNSURLSessionResponseDisposition):
+
+        * Platform/Logging.h:
+
+        * Shared/LoadParameters.cpp:
+        (WebKit::LoadParameters::encode const):
+        (WebKit::LoadParameters::decode):
+        * Shared/LoadParameters.h:
+
+        * Shared/ProcessTerminationReason.h: Add "NavigationSwap" as a process termination reason.
+
+        * UIProcess/API/APINavigation.h:
+
+        * UIProcess/API/APIProcessPoolConfiguration.cpp:
+        (API::ProcessPoolConfiguration::copy):
+
+        * UIProcess/API/C/WKAPICast.h:
+        (WebKit::toAPI):
+
+        * UIProcess/Cocoa/NavigationState.mm:
+        (WebKit::wkProcessTerminationReason):
+
+        * UIProcess/WebFramePolicyListenerProxy.cpp:
+        (WebKit::WebFramePolicyListenerProxy::WebFramePolicyListenerProxy):
+        * UIProcess/WebFramePolicyListenerProxy.h:
+        (WebKit::WebFramePolicyListenerProxy::create):
+        (WebKit::WebFramePolicyListenerProxy::policyListenerType const):
+
+        * UIProcess/WebFrameProxy.cpp:
+        (WebKit::WebFrameProxy::setUpPolicyListenerProxy):
+        (WebKit::WebFrameProxy::activePolicyListenerProxy):
+        * UIProcess/WebFrameProxy.h:
+
+        * UIProcess/WebNavigationState.cpp:
+        (WebKit::WebNavigationState::navigation):
+        (WebKit::WebNavigationState::takeNavigation):
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::reattachToWebProcess):
+        (WebKit::WebPageProxy::attachToProcessForNavigation): Pretend that the existing process 
+          terminated using the new "NavigationSwap" reason, then manually start the next load.
+        (WebKit::WebPageProxy::loadRequest):
+        (WebKit::WebPageProxy::loadRequestWithNavigation):
+        (WebKit::WebPageProxy::receivedPolicyDecision):
+        (WebKit::WebPageProxy::continueNavigationInNewProcess):
+        (WebKit::WebPageProxy::decidePolicyForNavigationAction):
+        (WebKit::WebPageProxy::decidePolicyForResponse):
+        (WebKit::WebPageProxy::processDidTerminate):
+        (WebKit::WebPageProxy::resetState):
+        (WebKit::WebPageProxy::resetStateAfterProcessExited):
+        * UIProcess/WebPageProxy.h:
+
+        * UIProcess/WebProcessPool.cpp:
+        (WebKit::WebProcessPool::processForNavigation): Determine which process should be used
+          for a proposed navigation, creating a new one if necessary.
+        * UIProcess/WebProcessPool.h:
+
+        * UIProcess/WebStorage/StorageManager.cpp:
+        (WebKit::StorageManager::SessionStorageNamespace::setAllowedConnection):
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::loadRequest):
+
 2018-03-20  Youenn Fablet  <youenn@apple.com>
 
         ServiceWorkerClientFetch::didReceiveData should check for m_encodedDataLength
index 56a9928..5aa5e0c 100644 (file)
@@ -317,6 +317,9 @@ void NetworkDataTaskBlob::dispatchDidReceiveResponse(Error errorCode)
             m_buffer.resize(bufferSize);
             read();
             break;
+        case PolicyAction::Suspend:
+            LOG_ERROR("PolicyAction::Suspend encountered - Treating as PolicyAction::Ignore for now");
+            FALLTHROUGH;
         case PolicyAction::Ignore:
             break;
         case PolicyAction::Download:
index f3a65ed..6680bb3 100644 (file)
@@ -252,6 +252,9 @@ void NetworkDataTaskReplay::didReceiveResponse(WebCore::ResourceResponse&& respo
         case WebCore::PolicyAction::Use:
             enqueueEventHandler();
             break;
+        case WebCore::PolicyAction::Suspend:
+            LOG_ERROR("PolicyAction::Suspend encountered - Treating as PolicyAction::Ignore for now");
+            FALLTHROUGH;
         case WebCore::PolicyAction::Ignore:
             complete();
             break;
index 3f76093..8582f82 100644 (file)
@@ -62,6 +62,9 @@ using namespace WebKit;
 static NSURLSessionResponseDisposition toNSURLSessionResponseDisposition(WebCore::PolicyAction disposition)
 {
     switch (disposition) {
+    case WebCore::PolicyAction::Suspend:
+        LOG_ERROR("PolicyAction::Suspend encountered - Treating as PolicyAction::Ignore for now");
+        FALLTHROUGH;
     case WebCore::PolicyAction::Ignore:
         return NSURLSessionResponseCancel;
     case WebCore::PolicyAction::Use:
index 4754a58..f6139c4 100644 (file)
@@ -50,6 +50,7 @@ extern "C" {
     M(IPC) \
     M(KeyHandling) \
     M(Layers) \
+    M(Loading) \
     M(Network) \
     M(NetworkCache) \
     M(NetworkCacheSpeculativePreloading) \
index 9bfd9cb..a6791ef 100644 (file)
@@ -48,6 +48,7 @@ void LoadParameters::encode(IPC::Encoder& encoder) const
     encoder << unreachableURLString;
     encoder << provisionalLoadErrorURLString;
     encoder << shouldOpenExternalURLsPolicy;
+    encoder << shouldCheckNavigationPolicy;
     encoder << userData;
 
     platformEncode(encoder);
@@ -102,6 +103,9 @@ bool LoadParameters::decode(IPC::Decoder& decoder, LoadParameters& data)
     if (!decoder.decode(data.shouldOpenExternalURLsPolicy))
         return false;
 
+    if (!decoder.decode(data.shouldCheckNavigationPolicy))
+        return false;
+
     if (!decoder.decode(data.userData))
         return false;
 
index d926175..8402ddc 100644 (file)
@@ -62,6 +62,7 @@ struct LoadParameters {
     String provisionalLoadErrorURLString;
 
     uint64_t shouldOpenExternalURLsPolicy;
+    bool shouldCheckNavigationPolicy { true };
     UserData userData;
 
 #if PLATFORM(COCOA)
index 6a1f492..6e03527 100644 (file)
@@ -31,7 +31,8 @@ enum class ProcessTerminationReason {
     ExceededMemoryLimit,
     ExceededCPULimit,
     RequestedByClient,
-    Crash
+    Crash,
+    NavigationSwap,
 };
 
 }
index f470c82..44ff50c 100644 (file)
@@ -36,6 +36,7 @@ class WebNavigationState;
 namespace API {
 
 class Navigation : public ObjectImpl<Object::Type::Navigation> {
+    WTF_MAKE_NONCOPYABLE(Navigation);
 public:
     static Ref<Navigation> create(WebKit::WebNavigationState& state)
     {
@@ -64,7 +65,7 @@ public:
 
 private:
     explicit Navigation(WebKit::WebNavigationState&);
-    explicit Navigation(WebKit::WebNavigationState&, WebCore::ResourceRequest&&);
+    Navigation(WebKit::WebNavigationState&, WebCore::ResourceRequest&&);
 
     uint64_t m_navigationID;
     WebCore::ResourceRequest m_request;
index e969406..0934ba3 100644 (file)
@@ -127,6 +127,7 @@ Ref<ProcessPoolConfiguration> ProcessPoolConfiguration::copy()
     copy->m_ctDataConnectionServiceType = this->m_ctDataConnectionServiceType;
 #endif
     copy->m_presentingApplicationPID = this->m_presentingApplicationPID;
+    copy->m_processSwapsOnNavigation = this->m_processSwapsOnNavigation;
 
     return copy;
 }
index 3b6bc30..796dd7c 100644 (file)
@@ -236,6 +236,10 @@ inline WKProcessTerminationReason toAPI(ProcessTerminationReason reason)
         return kWKProcessTerminationReasonExceededMemoryLimit;
     case ProcessTerminationReason::ExceededCPULimit:
         return kWKProcessTerminationReasonExceededCPULimit;
+    case ProcessTerminationReason::NavigationSwap:
+        // We probably shouldn't bother coming up with a new C-API type for process-swapping.
+        // "Requested by client" seems like the best match for existing types.
+        FALLTHROUGH;
     case ProcessTerminationReason::RequestedByClient:
         return kWKProcessTerminationReasonRequestedByClient;
     case ProcessTerminationReason::Crash:
index c5e3945..6e78e22 100644 (file)
@@ -962,6 +962,10 @@ static _WKProcessTerminationReason wkProcessTerminationReason(ProcessTermination
         return _WKProcessTerminationReasonExceededMemoryLimit;
     case ProcessTerminationReason::ExceededCPULimit:
         return _WKProcessTerminationReasonExceededCPULimit;
+    case ProcessTerminationReason::NavigationSwap:
+        // We probably shouldn't bother coming up with a new API type for process-swapping.
+        // "Requested by client" seems like the best match for existing types.
+        FALLTHROUGH;
     case ProcessTerminationReason::RequestedByClient:
         return _WKProcessTerminationReasonRequestedByClient;
     case ProcessTerminationReason::Crash:
index aa8fbcd..a836712 100644 (file)
@@ -32,8 +32,9 @@
 
 namespace WebKit {
 
-WebFramePolicyListenerProxy::WebFramePolicyListenerProxy(WebFrameProxy* frame, uint64_t listenerID)
+WebFramePolicyListenerProxy::WebFramePolicyListenerProxy(WebFrameProxy* frame, uint64_t listenerID, PolicyListenerType policyType)
     : WebFrameListenerProxy(frame, listenerID)
+    , m_policyType(policyType)
 {
 }
 
index 03c83f2..8990718 100644 (file)
 
 namespace WebKit {
 
+enum class PolicyListenerType {
+    NavigationAction,
+    NewWindowAction,
+    Response,
+};
+
 class WebFramePolicyListenerProxy : public WebFrameListenerProxy {
 public:
     static const Type APIType = Type::FramePolicyListener;
 
-    static Ref<WebFramePolicyListenerProxy> create(WebFrameProxy* frame, uint64_t listenerID)
+    static Ref<WebFramePolicyListenerProxy> create(WebFrameProxy* frame, uint64_t listenerID, PolicyListenerType policyType)
     {
-        return adoptRef(*new WebFramePolicyListenerProxy(frame, listenerID));
+        return adoptRef(*new WebFramePolicyListenerProxy(frame, listenerID, policyType));
     }
 
     void use(std::optional<WebsitePoliciesData>&&);
     void download();
     void ignore();
 
+    PolicyListenerType policyListenerType() const { return m_policyType; }
+
 private:
-    WebFramePolicyListenerProxy(WebFrameProxy*, uint64_t listenerID);
+    WebFramePolicyListenerProxy(WebFrameProxy*, uint64_t listenerID, PolicyListenerType);
 
     Type type() const override { return APIType; }
 
 #if DELEGATE_REF_COUNTING_TO_COCOA
     void* operator new(size_t size) { return newObject(size, APIType); }
 #endif
+
+    PolicyListenerType m_policyType;
 };
 
 } // namespace WebKit
index bc3a878..6d42aab 100644 (file)
@@ -187,14 +187,22 @@ void WebFrameProxy::receivedPolicyDecision(PolicyAction action, uint64_t listene
     m_page->receivedPolicyDecision(action, *this, listenerID, navigation, WTFMove(data));
 }
 
-WebFramePolicyListenerProxy& WebFrameProxy::setUpPolicyListenerProxy(uint64_t listenerID)
+WebFramePolicyListenerProxy& WebFrameProxy::setUpPolicyListenerProxy(uint64_t listenerID, PolicyListenerType policyListenerType)
 {
     if (m_activeListener)
         m_activeListener->invalidate();
-    m_activeListener = WebFramePolicyListenerProxy::create(this, listenerID);
+    m_activeListener = WebFramePolicyListenerProxy::create(this, listenerID, policyListenerType);
     return *static_cast<WebFramePolicyListenerProxy*>(m_activeListener.get());
 }
 
+WebFramePolicyListenerProxy* WebFrameProxy::activePolicyListenerProxy()
+{
+    if (!m_activeListener || m_activeListener->type() != WebFramePolicyListenerProxy::APIType)
+        return nullptr;
+
+    return static_cast<WebFramePolicyListenerProxy*>(m_activeListener.get());
+}
+
 void WebFrameProxy::changeWebsiteDataStore(WebsiteDataStore& websiteDataStore)
 {
     if (!m_page)
index f3a1b03..4be3300 100644 (file)
@@ -52,6 +52,7 @@ class WebCertificateInfo;
 class WebFramePolicyListenerProxy;
 class WebPageProxy;
 class WebsiteDataStore;
+enum class PolicyListenerType;
 struct WebsitePoliciesData;
 
 typedef GenericCallback<API::Data*> DataCallback;
@@ -116,7 +117,10 @@ public:
 
     // Policy operations.
     void receivedPolicyDecision(WebCore::PolicyAction, uint64_t listenerID, API::Navigation*, std::optional<WebsitePoliciesData>&&);
-    WebFramePolicyListenerProxy& setUpPolicyListenerProxy(uint64_t listenerID);
+
+    WebFramePolicyListenerProxy& setUpPolicyListenerProxy(uint64_t listenerID, PolicyListenerType);
+    WebFramePolicyListenerProxy* activePolicyListenerProxy();
+
     void changeWebsiteDataStore(WebsiteDataStore&);
 
 #if ENABLE(CONTENT_FILTERING)
index 9d97bdd..48f9345 100644 (file)
@@ -79,13 +79,15 @@ Ref<API::Navigation> WebNavigationState::createLoadDataNavigation()
 API::Navigation& WebNavigationState::navigation(uint64_t navigationID)
 {
     ASSERT(navigationID);
-    
+    ASSERT(m_navigations.contains(navigationID));
+
     return *m_navigations.get(navigationID);
 }
 
 Ref<API::Navigation> WebNavigationState::takeNavigation(uint64_t navigationID)
 {
     ASSERT(navigationID);
+    ASSERT(m_navigations.contains(navigationID));
     
     return m_navigations.take(navigationID).releaseNonNull();
 }
index 197b466..398470b 100644 (file)
@@ -639,15 +639,31 @@ void WebPageProxy::handleSynchronousMessage(IPC::Connection& connection, const S
 
 void WebPageProxy::reattachToWebProcess()
 {
+    auto process = makeRef(m_process->processPool().createNewWebProcessRespectingProcessCountLimit(m_websiteDataStore.get()));
+    reattachToWebProcess(WTFMove(process));
+}
+
+void WebPageProxy::attachToProcessForNavigation(Ref<WebProcessProxy>&& process)
+{
+    // FIXME: If this WebPageProxy is the only one hosted in its WebProcess, does this make it go away?
+    // We need to be prepared to reuse it later.
+    processDidTerminate(ProcessTerminationReason::NavigationSwap);
+
+    // FIXME: this is to fix the ASSERT(isValid()) inside reattachToWebProcess, some other way to fix this is needed.
+    m_isValid = false;
+    reattachToWebProcess(WTFMove(process));
+}
+
+void WebPageProxy::reattachToWebProcess(Ref<WebProcessProxy>&& process)
+{
     ASSERT(!m_isClosed);
     ASSERT(!isValid());
-    ASSERT(m_process->state() == WebProcessProxy::State::Terminated);
 
     m_isValid = true;
     m_process->removeWebPage(*this, m_pageID);
     m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID);
 
-    m_process = m_process->processPool().createNewWebProcessRespectingProcessCountLimit(m_websiteDataStore.get());
+    m_process = WTFMove(process);
 
     ASSERT(m_process->state() != ChildProcessProxy::State::Terminated);
     if (m_process->state() == ChildProcessProxy::State::Running)
@@ -874,6 +890,13 @@ RefPtr<API::Navigation> WebPageProxy::loadRequest(ResourceRequest&& request, Sho
         return nullptr;
 
     auto navigation = m_navigationState->createLoadRequestNavigation(ResourceRequest(request));
+    loadRequestWithNavigation(navigation.get(), WTFMove(request), shouldOpenExternalURLsPolicy, userData, NavigationPolicyCheck::Require);
+    return WTFMove(navigation);
+}
+
+void WebPageProxy::loadRequestWithNavigation(API::Navigation& navigation, ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData, NavigationPolicyCheck navigationPolicyCheck)
+{
+    ASSERT(!m_isClosed);
 
     auto transaction = m_pageLoadState.transaction();
 
@@ -884,10 +907,11 @@ RefPtr<API::Navigation> WebPageProxy::loadRequest(ResourceRequest&& request, Sho
         reattachToWebProcess();
 
     LoadParameters loadParameters;
-    loadParameters.navigationID = navigation->navigationID();
+    loadParameters.navigationID = navigation.navigationID();
     loadParameters.request = WTFMove(request);
     loadParameters.shouldOpenExternalURLsPolicy = (uint64_t)shouldOpenExternalURLsPolicy;
     loadParameters.userData = UserData(process().transformObjectsToHandles(userData).get());
+    loadParameters.shouldCheckNavigationPolicy = navigationPolicyCheck == NavigationPolicyCheck::Require;
     bool createdExtension = maybeInitializeSandboxExtensionHandle(url, loadParameters.sandboxExtensionHandle);
     if (createdExtension)
         m_process->willAcquireUniversalFileReadSandboxExtension();
@@ -895,8 +919,6 @@ RefPtr<API::Navigation> WebPageProxy::loadRequest(ResourceRequest&& request, Sho
 
     m_process->send(Messages::WebPage::LoadRequest(loadParameters), m_pageID);
     m_process->responsivenessTimer().start();
-
-    return WTFMove(navigation);
 }
 
 RefPtr<API::Navigation> WebPageProxy::loadFile(const String& fileURLString, const String& resourceDirectoryURLString, API::Object* userData)
@@ -2355,6 +2377,21 @@ void WebPageProxy::receivedPolicyDecision(PolicyAction action, WebFrameProxy& fr
         return;
     }
 
+    auto* activePolicyListener = frame.activePolicyListenerProxy();
+    if (activePolicyListener) {
+        ASSERT(activePolicyListener->listenerID() == listenerID);
+
+        if (action == PolicyAction::Use && navigation) {
+            auto proposedProcess = process().processPool().processForNavigation(*this, navigation->request().url());
+            if (proposedProcess.ptr() != &process()) {
+                action = PolicyAction::Suspend;
+                RunLoop::main().dispatch([this, protectedThis = makeRef(*this), navigation = makeRef(*navigation), proposedProcess = WTFMove(proposedProcess)]() mutable {
+                    continueNavigationInNewProcess(navigation.get(), WTFMove(proposedProcess));
+                });
+            }
+        }
+    }
+
     // If we received a policy decision while in decidePolicyForNavigationAction the decision will 
     // be sent back to the web process by decidePolicyForNavigationAction. 
     if (m_inDecidePolicyForNavigationAction) {
@@ -2368,6 +2405,16 @@ void WebPageProxy::receivedPolicyDecision(PolicyAction action, WebFrameProxy& fr
     m_process->send(Messages::WebPage::DidReceivePolicyDecision(frame.frameID(), listenerID, action, navigation ? navigation->navigationID() : 0, downloadID, websitePolicies), m_pageID);
 }
 
+void WebPageProxy::continueNavigationInNewProcess(API::Navigation& navigation, Ref<WebProcessProxy>&& process)
+{
+    LOG(Loading, "Continuing navigation %" PRIu64 " to URL %s in a new web process", navigation.navigationID(), navigation.request().url().string().utf8().data());
+
+    attachToProcessForNavigation(WTFMove(process));
+
+    // FIXME: Work out timing of responding with the last policy delegate, etc
+    loadRequestWithNavigation(navigation, ResourceRequest { navigation.request() }, WebCore::ShouldOpenExternalURLsPolicy::ShouldAllowExternalSchemes, nullptr, NavigationPolicyCheck::Bypass);
+}
+
 void WebPageProxy::setUserAgent(String&& userAgent)
 {
     if (m_userAgent == userAgent)
@@ -3768,6 +3815,8 @@ void WebPageProxy::frameDidBecomeFrameSet(uint64_t frameID, bool value)
 
 void WebPageProxy::decidePolicyForNavigationAction(uint64_t frameID, const SecurityOriginData& frameSecurityOrigin, uint64_t navigationID, NavigationActionData&& navigationActionData, const FrameInfoData& originatingFrameInfoData, uint64_t originatingPageID, const WebCore::ResourceRequest& originalRequest, ResourceRequest&& request, uint64_t listenerID, const UserData& userData, bool& receivedPolicyAction, uint64_t& newNavigationID, WebCore::PolicyAction& policyAction, DownloadID& downloadID, std::optional<WebsitePoliciesData>& websitePolicies)
 {
+    LOG(Loading, "WebPageProxy::didStartProvisionalLoadForFrame - Target url %s", originalRequest.url().string().utf8().data());
+
     PageClientProtector protector(m_pageClient);
 
     auto transaction = m_pageLoadState.transaction();
@@ -3781,7 +3830,7 @@ void WebPageProxy::decidePolicyForNavigationAction(uint64_t frameID, const Secur
     MESSAGE_CHECK_URL(request.url());
     MESSAGE_CHECK_URL(originalRequest.url());
     
-    Ref<WebFramePolicyListenerProxy> listener = frame->setUpPolicyListenerProxy(listenerID);
+    Ref<WebFramePolicyListenerProxy> listener = frame->setUpPolicyListenerProxy(listenerID, PolicyListenerType::NavigationAction);
     if (!navigationID) {
         auto navigation = m_navigationState->createLoadRequestNavigation(ResourceRequest(request));
         newNavigationID = navigation->navigationID();
@@ -3847,7 +3896,7 @@ void WebPageProxy::decidePolicyForNewWindowAction(uint64_t frameID, const Securi
     MESSAGE_CHECK(frame);
     MESSAGE_CHECK_URL(request.url());
 
-    Ref<WebFramePolicyListenerProxy> listener = frame->setUpPolicyListenerProxy(listenerID);
+    Ref<WebFramePolicyListenerProxy> listener = frame->setUpPolicyListenerProxy(listenerID, PolicyListenerType::NewWindowAction);
 
     if (m_navigationClient) {
         RefPtr<API::FrameInfo> sourceFrameInfo;
@@ -3873,9 +3922,11 @@ void WebPageProxy::decidePolicyForResponse(uint64_t frameID, const SecurityOrigi
     MESSAGE_CHECK_URL(request.url());
     MESSAGE_CHECK_URL(response.url());
 
-    Ref<WebFramePolicyListenerProxy> listener = frame->setUpPolicyListenerProxy(listenerID);
-    if (navigationID)
-        listener->setNavigation(m_navigationState->navigation(navigationID));
+    Ref<WebFramePolicyListenerProxy> listener = frame->setUpPolicyListenerProxy(listenerID, PolicyListenerType::Response);
+    if (navigationID) {
+        auto& navigation = m_navigationState->navigation(navigationID);
+        listener->setNavigation(navigation);
+    }
 
     if (m_navigationClient) {
         auto navigationResponse = API::NavigationResponse::create(API::FrameInfo::create(*frame, frameSecurityOrigin.securityOrigin()).get(), request, response, canShowMIMEType);
@@ -5590,14 +5641,19 @@ void WebPageProxy::processDidTerminate(ProcessTerminationReason reason)
     // There is a nested transaction in resetStateAfterProcessExited() that we don't want to commit before the client call.
     PageLoadState::Transaction transaction = m_pageLoadState.transaction();
 
-    resetStateAfterProcessExited();
+    resetStateAfterProcessExited(reason);
 
-    navigationState().clearAllNavigations();
+    // For bringup of process swapping, NavigationSwap termination will not go out to clients.
+    // If it does *during* process swapping, and the client triggers a reload, that causes bizarre WebKit re-entry.
+    // FIXME: This might have to change
+    if (reason != ProcessTerminationReason::NavigationSwap) {
+        navigationState().clearAllNavigations();
 
-    if (m_navigationClient)
-        m_navigationClient->processDidTerminate(*this, reason);
-    else if (reason != ProcessTerminationReason::RequestedByClient)
-        m_loaderClient->processDidCrash(*this);
+        if (m_navigationClient)
+            m_navigationClient->processDidTerminate(*this, reason);
+        else if (reason != ProcessTerminationReason::RequestedByClient)
+            m_loaderClient->processDidCrash(*this);
+    }
 
     if (m_controlledByAutomation) {
         if (auto* automationSession = process().processPool().automationSession())
@@ -5737,10 +5793,11 @@ void WebPageProxy::resetState(ResetStateReason resetStateReason)
 
     CallbackBase::Error error;
     switch (resetStateReason) {
+    case ResetStateReason::NavigationSwap:
+        FALLTHROUGH;
     case ResetStateReason::PageInvalidated:
         error = CallbackBase::Error::OwnerWasInvalidated;
         break;
-
     case ResetStateReason::WebProcessExited:
         error = CallbackBase::Error::ProcessExited;
         break;
@@ -5763,13 +5820,16 @@ void WebPageProxy::resetState(ResetStateReason resetStateReason)
 #endif
 }
 
-void WebPageProxy::resetStateAfterProcessExited()
+void WebPageProxy::resetStateAfterProcessExited(ProcessTerminationReason terminationReason)
 {
     if (!isValid())
         return;
 
+#if !ASSERT_DISABLED
     // FIXME: It's weird that resetStateAfterProcessExited() is called even though the process is launching.
-    ASSERT(m_process->state() == WebProcessProxy::State::Launching || m_process->state() == WebProcessProxy::State::Terminated);
+    if (terminationReason != ProcessTerminationReason::NavigationSwap)
+        ASSERT(m_process->state() == WebProcessProxy::State::Launching || m_process->state() == WebProcessProxy::State::Terminated);
+#endif
 
 #if PLATFORM(IOS)
     m_activityToken = nullptr;
@@ -5786,7 +5846,8 @@ void WebPageProxy::resetStateAfterProcessExited()
 
     m_pageClient.processDidExit();
 
-    resetState(ResetStateReason::WebProcessExited);
+    auto resetStateReason = terminationReason == ProcessTerminationReason::NavigationSwap ? ResetStateReason::NavigationSwap : ResetStateReason::WebProcessExited;
+    resetState(resetStateReason);
 
     m_pageClient.clearAllEditCommands();
     m_pendingLearnOrIgnoreWordMessageCount = 0;
index 0a9c99e..2d09435 100644 (file)
@@ -1305,9 +1305,10 @@ private:
     enum class ResetStateReason {
         PageInvalidated,
         WebProcessExited,
+        NavigationSwap,
     };
     void resetState(ResetStateReason);
-    void resetStateAfterProcessExited();
+    void resetStateAfterProcessExited(ProcessTerminationReason);
 
     void setUserAgent(String&&);
 
@@ -1441,9 +1442,18 @@ private:
     void setCanShortCircuitHorizontalWheelEvents(bool canShortCircuitHorizontalWheelEvents) { m_canShortCircuitHorizontalWheelEvents = canShortCircuitHorizontalWheelEvents; }
 
     void reattachToWebProcess();
+    void attachToProcessForNavigation(Ref<WebProcessProxy>&&);
+    void reattachToWebProcess(Ref<WebProcessProxy>&&);
+
     RefPtr<API::Navigation> reattachToWebProcessForReload();
     RefPtr<API::Navigation> reattachToWebProcessWithItem(WebBackForwardListItem*);
 
+    enum class NavigationPolicyCheck {
+        Require,
+        Bypass,
+    };
+    void loadRequestWithNavigation(API::Navigation&, WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy, API::Object* userData, NavigationPolicyCheck);
+
     void requestNotificationPermission(uint64_t notificationID, const String& originString);
     void showNotification(const String& title, const String& body, const String& iconURL, const String& tag, const String& lang, WebCore::NotificationDirection, const String& originString, uint64_t notificationID);
     void cancelNotification(uint64_t notificationID);
@@ -1685,7 +1695,6 @@ private:
     void contentFilterDidBlockLoadForFrame(const WebCore::ContentFilterUnblockHandler&, uint64_t frameID);
 #endif
 
-    uint64_t generateNavigationID();
     API::DiagnosticLoggingClient* effectiveDiagnosticLoggingClient(WebCore::ShouldSample);
 
     void dispatchActivityStateChange();
@@ -1732,6 +1741,8 @@ private:
 
     void reportPageLoadResult(const WebCore::ResourceError& = { });
 
+    void continueNavigationInNewProcess(API::Navigation&, Ref<WebProcessProxy>&&);
+
     PageClient& m_pageClient;
     Ref<API::PageConfiguration> m_configuration;
 
index 4cafeb9..1ae12a5 100644 (file)
@@ -1940,4 +1940,16 @@ ServiceWorkerProcessProxy* WebProcessPool::serviceWorkerProcessProxyFromPageID(u
 }
 #endif
 
+Ref<WebProcessProxy> WebProcessPool::processForNavigation(WebPageProxy& page, const URL& targetURL)
+{
+    if (!m_configuration->processSwapsOnNavigation())
+        return page.process();
+
+    auto url = URL { ParsedURLString, page.pageLoadState().url() };
+    if (protocolHostAndPortAreEqual(url, targetURL) || url.isBlankURL())
+        return page.process();
+
+    return createNewWebProcess(page.websiteDataStore());
+}
+
 } // namespace WebKit
index 762ae09..af1b40b 100644 (file)
@@ -442,6 +442,8 @@ public:
     BackgroundWebProcessToken backgroundWebProcessToken() const { return BackgroundWebProcessToken(m_backgroundWebProcessCounter.count()); }
 #endif
 
+    Ref<WebProcessProxy> processForNavigation(WebPageProxy&, const WebCore::URL&);
+
 private:
     void platformInitialize();
 
index f24bb9a..fd4804d 100644 (file)
@@ -425,8 +425,6 @@ StorageManager::SessionStorageNamespace::~SessionStorageNamespace()
 
 void StorageManager::SessionStorageNamespace::setAllowedConnection(IPC::Connection* allowedConnection)
 {
-    ASSERT(!allowedConnection || !m_allowedConnection);
-
     m_allowedConnection = allowedConnection;
 }
 
index b5a7822..dcaae55 100644 (file)
@@ -1261,6 +1261,7 @@ void WebPage::loadRequest(LoadParameters&& loadParameters)
     FrameLoadRequest frameLoadRequest { *m_mainFrame->coreFrame(), loadParameters.request, ShouldOpenExternalURLsPolicy::ShouldNotAllow };
     ShouldOpenExternalURLsPolicy externalURLsPolicy = static_cast<ShouldOpenExternalURLsPolicy>(loadParameters.shouldOpenExternalURLsPolicy);
     frameLoadRequest.setShouldOpenExternalURLsPolicy(externalURLsPolicy);
+    frameLoadRequest.setShouldCheckNavigationPolicy(loadParameters.shouldCheckNavigationPolicy);
 
     corePage()->userInputBridge().loadRequest(WTFMove(frameLoadRequest));
 
index b84ddcc..d17bc66 100644 (file)
@@ -1,3 +1,29 @@
+2018-03-20  Brady Eidson  <beidson@apple.com>
+
+        First piece of process swapping on navigation.
+        https://bugs.webkit.org/show_bug.cgi?id=183665
+
+        Reviewed by Andy Estes.
+
+        Expose the "swaps processes on navigation" setting in MiniBrowser UI for testing:
+
+        * MiniBrowser/mac/AppDelegate.m:
+        (defaultConfiguration):
+        * MiniBrowser/mac/SettingsController.h:
+        * MiniBrowser/mac/SettingsController.m:
+        (-[SettingsController _populateMenu]):
+        (-[SettingsController validateMenuItem:]):
+        (-[SettingsController processSwapOnNavigationEnabled]):
+        (-[SettingsController toggleProcessSwapOnNavigation:]):
+
+        Makes sure the current behavior is tested:
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm: Added.
+        (-[PSONNavigationDelegate webView:didFinishNavigation:]):
+        (-[PSONScheme webView:startURLSchemeTask:]):
+        (-[PSONScheme webView:stopURLSchemeTask:]):
+        (TEST):
+
 2018-03-20  Chris Dumez  <cdumez@apple.com>
 
         QuickLook.NavigationDelegate API test is failing on iOS with async policy delegates
index 9fb8608..61b9e8b 100644 (file)
@@ -98,6 +98,8 @@ static WKWebViewConfiguration *defaultConfiguration()
         processConfiguration.diskCacheSpeculativeValidationEnabled = ![SettingsController shared].networkCacheSpeculativeRevalidationDisabled;
         if ([SettingsController shared].perWindowWebProcessesDisabled)
             processConfiguration.maximumProcessCount = 1;
+        if ([SettingsController shared].processSwapOnNavigationEnabled)
+            processConfiguration.processSwapsOnNavigation = true;
         
         configuration.processPool = [[[WKProcessPool alloc] _initWithConfiguration:processConfiguration] autorelease];
 
index 61581b1..8dad064 100644 (file)
@@ -59,6 +59,7 @@
 @property (nonatomic, readonly) BOOL loadsAllSiteIcons;
 @property (nonatomic, readonly) BOOL usesGameControllerFramework;
 @property (nonatomic, readonly) BOOL networkCacheSpeculativeRevalidationDisabled;
+@property (nonatomic, readonly) BOOL processSwapOnNavigationEnabled;
 
 @property (nonatomic, readonly) NSString *defaultURL;
 
index 8cce180..d64ef41 100644 (file)
@@ -70,6 +70,7 @@ static NSString * const UseRemoteLayerTreeDrawingAreaPreferenceKey = @"WebKit2Us
 
 static NSString * const PerWindowWebProcessesDisabledKey = @"PerWindowWebProcessesDisabled";
 static NSString * const NetworkCacheSpeculativeRevalidationDisabledKey = @"NetworkCacheSpeculativeRevalidationDisabled";
+static NSString * const ProcessSwapOnNavigationKey = @"ProcessSwapOnNavigation";
 
 typedef NS_ENUM(NSInteger, DebugOverylayMenuItemTag) {
     NonFastScrollableRegionOverlayTag = 100,
@@ -179,6 +180,7 @@ typedef NS_ENUM(NSInteger, DebugOverylayMenuItemTag) {
     [self _addItemWithTitle:@"Load All Site Icons Per-Page" action:@selector(toggleLoadsAllSiteIcons:) indented:YES];
     [self _addItemWithTitle:@"Use GameController.framework on macOS (Restart required)" action:@selector(toggleUsesGameControllerFramework:) indented:YES];
     [self _addItemWithTitle:@"Disable network cache speculative revalidation" action:@selector(toggleNetworkCacheSpeculativeRevalidationDisabled:) indented:YES];
+    [self _addItemWithTitle:@"Enable Process Swap on Navigation" action:@selector(toggleProcessSwapOnNavigation:) indented:YES];
 
     NSMenuItem *debugOverlaysSubmenuItem = [[NSMenuItem alloc] initWithTitle:@"Debug Overlays" action:nil keyEquivalent:@""];
     NSMenu *debugOverlaysMenu = [[NSMenu alloc] initWithTitle:@"Debug Overlays"];
@@ -270,6 +272,8 @@ typedef NS_ENUM(NSInteger, DebugOverylayMenuItemTag) {
         [menuItem setState:[self usesGameControllerFramework] ? NSControlStateValueOn : NSControlStateValueOff];
     else if (action == @selector(toggleNetworkCacheSpeculativeRevalidationDisabled:))
         [menuItem setState:[self networkCacheSpeculativeRevalidationDisabled] ? NSControlStateValueOn : NSControlStateValueOff];
+    else if (action == @selector(toggleProcessSwapOnNavigation:))
+        [menuItem setState:[self processSwapOnNavigationEnabled] ? NSControlStateValueOn : NSControlStateValueOff];
     else if (action == @selector(toggleUseUISideCompositing:))
         [menuItem setState:[self useUISideCompositing] ? NSControlStateValueOn : NSControlStateValueOff];
     else if (action == @selector(togglePerWindowWebProcessesDisabled:))
@@ -485,6 +489,16 @@ typedef NS_ENUM(NSInteger, DebugOverylayMenuItemTag) {
     [self _toggleBooleanDefault:NetworkCacheSpeculativeRevalidationDisabledKey];
 }
 
+- (BOOL)processSwapOnNavigationEnabled
+{
+    return [[NSUserDefaults standardUserDefaults] boolForKey:ProcessSwapOnNavigationKey];
+}
+
+- (void)toggleProcessSwapOnNavigation:(id)sender
+{
+    [self _toggleBooleanDefault:ProcessSwapOnNavigationKey];
+}
+
 - (BOOL)isSpaceReservedForBanners
 {
     return [[NSUserDefaults standardUserDefaults] boolForKey:ReserveSpaceForBannersPreferenceKey];
index e557e8d..79f15be 100644 (file)
                51714EB81CF8CA17004723C4 /* WebProcessKillIDBCleanup.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51714EB61CF8C7A4004723C4 /* WebProcessKillIDBCleanup.mm */; };
                517E7E04151119C100D0B008 /* MemoryCachePruneWithinResourceLoadDelegate.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 517E7E031511187500D0B008 /* MemoryCachePruneWithinResourceLoadDelegate.html */; };
                5182C22E1F2BCE540059BA7C /* WKURLSchemeHandler-leaks.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5182C22D1F2BCB410059BA7C /* WKURLSchemeHandler-leaks.mm */; };
+               518C1153205B0504001FF4AE /* ProcessSwapOnNavigation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 518C1152205B04F9001FF4AE /* ProcessSwapOnNavigation.mm */; };
                5198A2401EA7E59F008910B7 /* InitialWarmedProcessUsed.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5198A23F1EA7E595008910B7 /* InitialWarmedProcessUsed.mm */; };
                51A5877D1D1B49CD004BA9AF /* IndexedDBMultiProcess-3.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51A5877C1D1B3D8D004BA9AF /* IndexedDBMultiProcess-3.html */; };
                51A587851D2739E3004BA9AF /* IndexedDBDatabaseProcessKill-1.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51A587821D272EB5004BA9AF /* IndexedDBDatabaseProcessKill-1.html */; };
                517E7DFB15110EA600D0B008 /* MemoryCachePruneWithinResourceLoadDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MemoryCachePruneWithinResourceLoadDelegate.mm; sourceTree = "<group>"; };
                517E7E031511187500D0B008 /* MemoryCachePruneWithinResourceLoadDelegate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = MemoryCachePruneWithinResourceLoadDelegate.html; sourceTree = "<group>"; };
                5182C22D1F2BCB410059BA7C /* WKURLSchemeHandler-leaks.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "WKURLSchemeHandler-leaks.mm"; sourceTree = "<group>"; };
+               518C1152205B04F9001FF4AE /* ProcessSwapOnNavigation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ProcessSwapOnNavigation.mm; sourceTree = "<group>"; };
                5198A23F1EA7E595008910B7 /* InitialWarmedProcessUsed.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = InitialWarmedProcessUsed.mm; sourceTree = "<group>"; };
                51A5877C1D1B3D8D004BA9AF /* IndexedDBMultiProcess-3.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "IndexedDBMultiProcess-3.html"; sourceTree = "<group>"; };
                51A587821D272EB5004BA9AF /* IndexedDBDatabaseProcessKill-1.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "IndexedDBDatabaseProcessKill-1.html"; sourceTree = "<group>"; };
                                83BAEE8C1EF4625500DDE894 /* PluginLoadClientPolicies.mm */,
                                C95501BE19AD2FAF0049BE3E /* Preferences.mm */,
                                7C1AF7931E8DCBAB002645B9 /* PrepareForMoveToWindow.mm */,
+                               518C1152205B04F9001FF4AE /* ProcessSwapOnNavigation.mm */,
                                5798E2AF1CAF5C2800C5CBA0 /* ProvisionalURLNotChange.mm */,
                                A1C4FB6C1BACCE50003742D0 /* QuickLook.mm */,
                                1A4F81D01BDFFDCF004E672E /* RemoteObjectRegistry.h */,
                                7CCE7F2F1A411B1000447C4C /* WKBrowsingContextLoadDelegateTest.mm in Sources */,
                                7C54A4BE1AA11CCA00380F78 /* WKBundleFileHandle.cpp in Sources */,
                                5CE354D91E70DA5C00BEFE3B /* WKContentExtensionStore.mm in Sources */,
+                               518C1153205B0504001FF4AE /* ProcessSwapOnNavigation.mm in Sources */,
                                2D838B1F1EEF3A5C009B980E /* WKContentViewEditingActions.mm in Sources */,
                                370CE22A1F57343400E7410B /* WKContentViewTargetForAction.mm in Sources */,
                                51D124981E763B02002B2820 /* WKHTTPCookieStore.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm
new file mode 100644 (file)
index 0000000..f4b1e07
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import <WebKit/WKNavigationDelegate.h>
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKProcessPoolPrivate.h>
+#import <WebKit/WKURLSchemeHandler.h>
+#import <WebKit/WKURLSchemeTaskPrivate.h>
+#import <WebKit/WKWebViewConfigurationPrivate.h>
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/WKWebsiteDataStorePrivate.h>
+#import <WebKit/WKWebsiteDataStoreRef.h>
+#import <WebKit/WebKit.h>
+#import <WebKit/_WKExperimentalFeature.h>
+#import <WebKit/_WKProcessPoolConfiguration.h>
+#import <WebKit/_WKWebsiteDataStoreConfiguration.h>
+#import <wtf/Deque.h>
+#import <wtf/HashMap.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/Vector.h>
+#import <wtf/text/StringHash.h>
+#import <wtf/text/WTFString.h>
+
+#if WK_API_ENABLED
+
+static bool done;
+static int numberOfDecidePolicyCalls;
+
+@interface PSONNavigationDelegate : NSObject <WKNavigationDelegate>
+@end
+
+@implementation PSONNavigationDelegate
+
+- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
+{
+    done = true;
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+    ++numberOfDecidePolicyCalls;
+    decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+@end
+
+@interface PSONScheme : NSObject <WKURLSchemeHandler> {
+}
+@end
+
+@implementation PSONScheme
+
+- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+    RetainPtr<NSURLResponse> response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:1 textEncodingName:nil]);
+    [task didReceiveResponse:response.get()];
+    [task didReceiveData:[@"Hello" dataUsingEncoding:NSUTF8StringEncoding]];
+    [task didFinish];
+}
+
+- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
+{
+}
+
+@end
+
+TEST(ProcessSwap, Basic)
+{
+    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] init]);
+    [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON1"];
+    [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON2"];
+
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
+    auto delegate = adoptNS([[PSONNavigationDelegate alloc] init]);
+    [webView setNavigationDelegate:delegate.get()];
+
+    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main1.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    auto pid1 = [webView _webProcessIdentifier];
+
+    request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson1://host/main2.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    auto pid2 = [webView _webProcessIdentifier];
+
+    request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson2://host/main2.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    auto pid3 = [webView _webProcessIdentifier];
+
+    EXPECT_EQ(pid1, pid2);
+    EXPECT_FALSE(pid2 == pid3);
+
+    // 3 loads, 3 decidePolicy calls (e.g. the load that did perform a process swap should not have generated an additional decidePolicy call)
+    EXPECT_EQ(numberOfDecidePolicyCalls, 3);
+}
+
+#endif // WK_API_ENABLED