Introduce SuspendedPageProxy to keep old web processes around after their WebPageProx...
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 Apr 2018 18:04:22 +0000 (18:04 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 Apr 2018 18:04:22 +0000 (18:04 +0000)
https://bugs.webkit.org/show_bug.cgi?id=184559

Reviewed by Alex Christensen.

Source/WebCore:

Covered by new API test.

WebCore changes rework the meaning of a "ForSuspension" policy to simply navigate the page to about:blank.

* loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::redirectReceived):
(WebCore::DocumentLoader::willSendRequest):
(WebCore::DocumentLoader::startLoadingMainResource):
* loader/DocumentLoader.h:

* loader/FrameLoader.cpp:
(WebCore::FrameLoader::init):
(WebCore::FrameLoader::continueLoadAfterNavigationPolicy):

Source/WebKit:

Before this patch, when a WebPageProxy navigates and is swapped to a new process, the old process almost always goes away.

This is not desirable for a few reasons:
1 - We can't keep the PageCache working for back/forward scenarios
2 - We throw away a "foo.com" web process, meaning the next time we need to host a "foo.com" web page we have to launch
    and initialize a new web process.

This patch adds a SuspendedPageProxy object to keep around the old web process and to manage communication with it.

For now, a WebPageProxy keeps exactly one "suspended page" representing the most recently visited page and its process.
Additionally, that process is never reused.

So no benefit is achieved with this patch, but it enables future benefits.

* Platform/Logging.h:

* Shared/WebBackForwardListItem.cpp:
(WebKit::WebBackForwardListItem::setSuspendedPage):
* Shared/WebBackForwardListItem.h:

New object to represent the state of a WebPageProxy in an old web process that is not currently hosting the view.
* UIProcess/SuspendedPageProxy.cpp: Added.
(WebKit::SuspendedPageProxy::SuspendedPageProxy):
(WebKit::SuspendedPageProxy::~SuspendedPageProxy):
(WebKit::SuspendedPageProxy::webProcessDidClose):
(WebKit::SuspendedPageProxy::didFinishLoad):
(WebKit::SuspendedPageProxy::didReceiveMessage):
(WebKit::SuspendedPageProxy::loggingString const):
* UIProcess/SuspendedPageProxy.h: Copied from Source/WebKit/Platform/Logging.h.
(WebKit::SuspendedPageProxy::create):
(WebKit::SuspendedPageProxy::page const):
(WebKit::SuspendedPageProxy::process const):
(WebKit::SuspendedPageProxy::item const):
(WebKit::SuspendedPageProxy::finishedSuspending const):

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::reattachToWebProcess):
(WebKit::WebPageProxy::attachToProcessForNavigation):
(WebKit::WebPageProxy::maybeCreateSuspendedPage):
(WebKit::WebPageProxy::suspendedPageProcessClosed):
(WebKit::WebPageProxy::receivedPolicyDecision):
(WebKit::WebPageProxy::didFinishLoadForFrame):
* UIProcess/WebPageProxy.h:

* UIProcess/WebProcessProxy.cpp:
(WebKit::WebProcessProxy::suspendWebPageProxy):
(WebKit::WebProcessProxy::suspendedPageWasDestroyed):
(WebKit::WebProcessProxy::removeWebPage):
(WebKit::WebProcessProxy::didReceiveMessage): Optionally pass WebPageProxy messages along to SuspendedPageProxy objects.
(WebKit::WebProcessProxy::didClose):
(WebKit::WebProcessProxy::maybeShutDown):
(WebKit::WebProcessProxy::canTerminateChildProcess): Don't terminate child processes if they still have suspended pages.
* UIProcess/WebProcessProxy.h:

* WebKit.xcodeproj/project.pbxproj:

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::setIsSuspended):
* WebProcess/WebPage/WebPage.h:
(WebKit::WebPage::isSuspended const): For now, used only by WebProcess::updateActivePages. Will have more uses soon.
* WebProcess/WebPage/WebPage.messages.in:

* WebProcess/WebProcess.messages.in:
* WebProcess/cocoa/WebProcessCocoa.mm:
(WebKit::WebProcess::updateActivePages): Allow the UIProcess to request an update of the web processes user visible name.

Source/WTF:

* wtf/DebugUtilities.h:
(WTF::debugString): Add a debug utility to easily construct a "const char*" that is released after a spin of the run loop.
  This greatly eases uses our String classes and functions inside of "%s" style environments like printf and LOG.

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:

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

24 files changed:
Source/WTF/ChangeLog
Source/WTF/wtf/DebugUtilities.h
Source/WebCore/ChangeLog
Source/WebCore/loader/DocumentLoader.cpp
Source/WebCore/loader/DocumentLoader.h
Source/WebCore/loader/FrameLoader.cpp
Source/WebKit/ChangeLog
Source/WebKit/Platform/Logging.h
Source/WebKit/Shared/WebBackForwardListItem.cpp
Source/WebKit/Shared/WebBackForwardListItem.h
Source/WebKit/UIProcess/SuspendedPageProxy.cpp [new file with mode: 0644]
Source/WebKit/UIProcess/SuspendedPageProxy.h [new file with mode: 0644]
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebProcessProxy.cpp
Source/WebKit/UIProcess/WebProcessProxy.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Source/WebKit/WebProcess/WebProcess.messages.in
Source/WebKit/WebProcess/cocoa/WebProcessCocoa.mm
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm

index 05c1025..52a4fdf 100644 (file)
@@ -1,3 +1,14 @@
+2018-04-13  Brady Eidson  <beidson@apple.com>
+
+        Introduce SuspendedPageProxy to keep old web processes around after their WebPageProxy has been swapped to a new one.
+        https://bugs.webkit.org/show_bug.cgi?id=184559
+
+        Reviewed by Alex Christensen.
+
+        * wtf/DebugUtilities.h:
+        (WTF::debugString): Add a debug utility to easily construct a "const char*" that is released after a spin of the run loop.
+          This greatly eases uses our String classes and functions inside of "%s" style environments like printf and LOG.
+
 2018-04-12  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         Remove unused crash hook functionality
index 122f768..a441b5c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef WTF_DebugUtilities_h
-#define WTF_DebugUtilities_h
+#pragma once
 
 #include <wtf/Assertions.h>
 #include <wtf/ProcessID.h>
+#include <wtf/text/StringConcatenate.h>
 
 #define SLEEP_THREAD_FOR_DEBUGGER() \
 do { \
@@ -40,4 +40,24 @@ do { \
     WTFBreakpointTrap(); \
 } while (0)
 
-#endif /* WTF_DebugUtilities_h */
+namespace WTF {
+
+template<typename... StringTypes>
+const char* debugString(StringTypes... strings)
+{
+    String result = tryMakeString(strings...);
+    if (!result)
+        CRASH();
+
+    auto cString = result.utf8();
+    const char* cStringData = cString.data();
+
+    callOnMainThread([cString = WTFMove(cString)] {
+    });
+
+    return cStringData;
+}
+
+} // namespace WTF
+
+using WTF::debugString;
index 7b8c3fb..a516f89 100644 (file)
@@ -1,3 +1,24 @@
+2018-04-13  Brady Eidson  <beidson@apple.com>
+
+        Introduce SuspendedPageProxy to keep old web processes around after their WebPageProxy has been swapped to a new one.
+        https://bugs.webkit.org/show_bug.cgi?id=184559
+
+        Reviewed by Alex Christensen.
+
+        Covered by new API test.
+
+        WebCore changes rework the meaning of a "ForSuspension" policy to simply navigate the page to about:blank.
+
+        * loader/DocumentLoader.cpp:
+        (WebCore::DocumentLoader::redirectReceived):
+        (WebCore::DocumentLoader::willSendRequest):
+        (WebCore::DocumentLoader::startLoadingMainResource):
+        * loader/DocumentLoader.h:
+
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::init):
+        (WebCore::FrameLoader::continueLoadAfterNavigationPolicy):
+
 2018-04-13  Chris Dumez  <cdumez@apple.com>
 
         input.webkitEntries does not work as expected when folder contains accented chars
index 80dbf13..fbc7434 100644 (file)
@@ -519,7 +519,7 @@ void DocumentLoader::redirectReceived(CachedResource& resource, ResourceRequest&
     ASSERT_UNUSED(resource, &resource == m_mainResource);
 #if ENABLE(SERVICE_WORKER)
     bool isRedirectionFromServiceWorker = redirectResponse.source() == ResourceResponse::Source::ServiceWorker;
-    willSendRequest(WTFMove(request), redirectResponse, [isRedirectionFromServiceWorker, completionHandler = WTFMove(completionHandler), protectedThis = makeRef(*this), this] (auto&& request) mutable {
+    willSendRequest(WTFMove(request), redirectResponse, ShouldContinue::Yes, [isRedirectionFromServiceWorker, completionHandler = WTFMove(completionHandler), protectedThis = makeRef(*this), this] (auto&& request) mutable {
         ASSERT(!m_substituteData.isValid());
         if (request.isNull() || !m_mainDocumentError.isNull() || !m_frame) {
             completionHandler({ });
@@ -552,11 +552,11 @@ void DocumentLoader::redirectReceived(CachedResource& resource, ResourceRequest&
         });
     });
 #else
-    willSendRequest(WTFMove(request), redirectResponse, WTFMove(completionHandler));
+    willSendRequest(WTFMove(request), redirectResponse, ShouldContinue::Yes, WTFMove(completionHandler));
 #endif
 }
 
-void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const ResourceResponse& redirectResponse, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
+void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const ResourceResponse& redirectResponse, ShouldContinue shouldContinue, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
 {
     // Note that there are no asserts here as there are for the other callbacks. This is due to the
     // fact that this "callback" is sent when starting every load, and the state of callback
@@ -564,6 +564,8 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc
     // callbacks is meant to prevent.
     ASSERT(!newRequest.isNull());
 
+    ASSERT(shouldContinue != ShouldContinue::No);
+
     bool didReceiveRedirectResponse = !redirectResponse.isNull();
     if (!frameLoader()->checkIfFormActionAllowedByCSP(newRequest.url(), didReceiveRedirectResponse)) {
         cancelMainResourceLoad(frameLoader()->cancelledError(newRequest));
@@ -630,21 +632,16 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc
 
     setRequest(newRequest);
 
-    // FIXME: Ideally we'd stop the I/O until we hear back from the navigation policy delegate
-    // listener. But there's no way to do that in practice. So instead we cancel later if the
-    // listener tells us to. In practice that means the navigation policy needs to be decided
-    // synchronously for these redirect cases.
-    if (!didReceiveRedirectResponse)
+    if (!didReceiveRedirectResponse && shouldContinue != ShouldContinue::ForSuspension)
         return completionHandler(WTFMove(newRequest));
 
-    ASSERT(!m_waitingForNavigationPolicy);
-    m_waitingForNavigationPolicy = true;
-    frameLoader()->policyChecker().checkNavigationPolicy(ResourceRequest(newRequest), didReceiveRedirectResponse, [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (ResourceRequest&& request, FormState*, ShouldContinue shouldContinue) mutable {
+    auto navigationPolicyCompletionHandler = [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (ResourceRequest&& request, FormState*, ShouldContinue shouldContinue) mutable {
         m_waitingForNavigationPolicy = false;
         switch (shouldContinue) {
         case ShouldContinue::ForSuspension:
-            FALLTHROUGH;
-            // FIXME: Setup this page for suspension (e.g. Page Cache) here.
+            // We handle suspension by navigating forward to about:blank, which leaves us setup to navigate back to resume.
+            request = { blankURL() };
+            break;
         case ShouldContinue::No:
             stopLoadingForPolicyChange();
             break;
@@ -653,7 +650,17 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc
         }
 
         completionHandler(WTFMove(request));
-    });
+    };
+
+    ASSERT(!m_waitingForNavigationPolicy);
+    m_waitingForNavigationPolicy = true;
+
+    if (shouldContinue == ShouldContinue::ForSuspension) {
+        navigationPolicyCompletionHandler(WTFMove(newRequest), nullptr, shouldContinue);
+        return;
+    }
+
+    frameLoader()->policyChecker().checkNavigationPolicy(ResourceRequest(newRequest), didReceiveRedirectResponse, WTFMove(navigationPolicyCompletionHandler));
 }
 
 bool DocumentLoader::tryLoadingRequestFromApplicationCache()
@@ -1655,8 +1662,10 @@ bool DocumentLoader::maybeLoadEmpty()
     return true;
 }
 
-void DocumentLoader::startLoadingMainResource()
+void DocumentLoader::startLoadingMainResource(ShouldContinue shouldContinue)
 {
+    ASSERT(shouldContinue != ShouldContinue::No);
+
     m_mainDocumentError = ResourceError();
     timing().markStartTimeAndFetchStart();
     ASSERT(!m_mainResource);
@@ -1681,7 +1690,7 @@ void DocumentLoader::startLoadingMainResource()
     ASSERT(timing().startTime());
     ASSERT(timing().fetchStart());
 
-    willSendRequest(ResourceRequest(m_request), ResourceResponse(), [this, protectedThis = makeRef(*this)] (ResourceRequest&& request) mutable {
+    willSendRequest(ResourceRequest(m_request), ResourceResponse(), shouldContinue, [this, protectedThis = makeRef(*this)] (ResourceRequest&& request) mutable {
         m_request = request;
 
         // willSendRequest() may lead to our Frame being detached or cancelling the load via nulling the ResourceRequest.
index 70a1a3e..18094ec 100644 (file)
@@ -84,6 +84,8 @@ class SharedBuffer;
 class SubresourceLoader;
 class SubstituteResource;
 
+enum class ShouldContinue;
+
 using ResourceLoaderMap = HashMap<unsigned long, RefPtr<ResourceLoader>>;
 
 enum class AutoplayPolicy {
@@ -237,7 +239,7 @@ public:
     void setDefersLoading(bool);
     void setMainResourceDataBufferingPolicy(DataBufferingPolicy);
 
-    void startLoadingMainResource();
+    void startLoadingMainResource(ShouldContinue);
     WEBCORE_EXPORT void cancelMainResourceLoad(const ResourceError&);
     void willContinueMainResourceLoadAfterRedirect(const ResourceRequest&);
 
@@ -351,7 +353,7 @@ private:
     void clearArchiveResources();
 #endif
 
-    void willSendRequest(ResourceRequest&&, const ResourceResponse&, CompletionHandler<void(ResourceRequest&&)>&&);
+    void willSendRequest(ResourceRequest&&, const ResourceResponse&, ShouldContinue, CompletionHandler<void(ResourceRequest&&)>&&);
     void finishedLoading();
     void mainReceivedError(const ResourceError&);
     WEBCORE_EXPORT void redirectReceived(CachedResource&, ResourceRequest&&, const ResourceResponse&, CompletionHandler<void(ResourceRequest&&)>&&) override;
index fe0558d..bd63d0b 100644 (file)
@@ -306,7 +306,7 @@ void FrameLoader::init()
     // This somewhat odd set of steps gives the frame an initial empty document.
     setPolicyDocumentLoader(m_client.createDocumentLoader(ResourceRequest(URL(ParsedURLString, emptyString())), SubstituteData()).ptr());
     setProvisionalDocumentLoader(m_policyDocumentLoader.get());
-    m_provisionalDocumentLoader->startLoadingMainResource();
+    m_provisionalDocumentLoader->startLoadingMainResource(ShouldContinue::Yes);
 
     Ref<Frame> protect(m_frame);
     m_frame.document()->cancelParsing();
@@ -3231,7 +3231,7 @@ void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest& reque
         diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::retrievalKey(), DiagnosticLoggingResultFail, ShouldSample::Yes);
     }
 
-    WTF::Function<void(void)> completionHandler = [this] {
+    CompletionHandler<void(void)> completionHandler = [this, shouldContinue] {
         if (!m_provisionalDocumentLoader)
             return;
         
@@ -3251,7 +3251,12 @@ void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest& reque
         }
         
         m_loadingFromCachedPage = false;
-        m_provisionalDocumentLoader->startLoadingMainResource();
+
+        // We handle suspension by navigating forward to about:blank, which leaves us setup to navigate back to resume.
+        if (shouldContinue == ShouldContinue::ForSuspension)
+            m_provisionalDocumentLoader->willContinueMainResourceLoadAfterRedirect({ blankURL() });
+
+        m_provisionalDocumentLoader->startLoadingMainResource(shouldContinue);
     };
     
     if (!formState) {
index e43a8d2..8d80f43 100644 (file)
@@ -1,3 +1,76 @@
+2018-04-13  Brady Eidson  <beidson@apple.com>
+
+        Introduce SuspendedPageProxy to keep old web processes around after their WebPageProxy has been swapped to a new one.
+        https://bugs.webkit.org/show_bug.cgi?id=184559
+
+        Reviewed by Alex Christensen.
+
+        Before this patch, when a WebPageProxy navigates and is swapped to a new process, the old process almost always goes away.
+
+        This is not desirable for a few reasons:
+        1 - We can't keep the PageCache working for back/forward scenarios
+        2 - We throw away a "foo.com" web process, meaning the next time we need to host a "foo.com" web page we have to launch
+            and initialize a new web process.
+
+        This patch adds a SuspendedPageProxy object to keep around the old web process and to manage communication with it.
+
+        For now, a WebPageProxy keeps exactly one "suspended page" representing the most recently visited page and its process.
+        Additionally, that process is never reused.
+
+        So no benefit is achieved with this patch, but it enables future benefits.
+
+        * Platform/Logging.h:
+
+        * Shared/WebBackForwardListItem.cpp:
+        (WebKit::WebBackForwardListItem::setSuspendedPage):
+        * Shared/WebBackForwardListItem.h:
+
+        New object to represent the state of a WebPageProxy in an old web process that is not currently hosting the view.
+        * UIProcess/SuspendedPageProxy.cpp: Added.
+        (WebKit::SuspendedPageProxy::SuspendedPageProxy):
+        (WebKit::SuspendedPageProxy::~SuspendedPageProxy):
+        (WebKit::SuspendedPageProxy::webProcessDidClose):
+        (WebKit::SuspendedPageProxy::didFinishLoad):
+        (WebKit::SuspendedPageProxy::didReceiveMessage):
+        (WebKit::SuspendedPageProxy::loggingString const):
+        * UIProcess/SuspendedPageProxy.h: Copied from Source/WebKit/Platform/Logging.h.
+        (WebKit::SuspendedPageProxy::create):
+        (WebKit::SuspendedPageProxy::page const):
+        (WebKit::SuspendedPageProxy::process const):
+        (WebKit::SuspendedPageProxy::item const):
+        (WebKit::SuspendedPageProxy::finishedSuspending const):
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::reattachToWebProcess):
+        (WebKit::WebPageProxy::attachToProcessForNavigation):
+        (WebKit::WebPageProxy::maybeCreateSuspendedPage):
+        (WebKit::WebPageProxy::suspendedPageProcessClosed):
+        (WebKit::WebPageProxy::receivedPolicyDecision):
+        (WebKit::WebPageProxy::didFinishLoadForFrame):
+        * UIProcess/WebPageProxy.h:
+
+        * UIProcess/WebProcessProxy.cpp:
+        (WebKit::WebProcessProxy::suspendWebPageProxy):
+        (WebKit::WebProcessProxy::suspendedPageWasDestroyed):
+        (WebKit::WebProcessProxy::removeWebPage):
+        (WebKit::WebProcessProxy::didReceiveMessage): Optionally pass WebPageProxy messages along to SuspendedPageProxy objects.
+        (WebKit::WebProcessProxy::didClose):
+        (WebKit::WebProcessProxy::maybeShutDown):
+        (WebKit::WebProcessProxy::canTerminateChildProcess): Don't terminate child processes if they still have suspended pages.
+        * UIProcess/WebProcessProxy.h:
+
+        * WebKit.xcodeproj/project.pbxproj:
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::setIsSuspended):
+        * WebProcess/WebPage/WebPage.h:
+        (WebKit::WebPage::isSuspended const): For now, used only by WebProcess::updateActivePages. Will have more uses soon.
+        * WebProcess/WebPage/WebPage.messages.in:
+
+        * WebProcess/WebProcess.messages.in:
+        * WebProcess/cocoa/WebProcessCocoa.mm:
+        (WebKit::WebProcess::updateActivePages): Allow the UIProcess to request an update of the web processes user visible name.
+
 2018-04-13  Daniel Bates  <dabates@apple.com>
 
         Inline NetworkLoad::sharedWillSendRedirectedRequest() into NetworkLoad::willPerformHTTPRedirection()
index 63e4c8e..45566ac 100644 (file)
@@ -62,6 +62,7 @@ extern "C" {
     M(Printing) \
     M(Process) \
     M(ProcessSuspension) \
+    M(ProcessSwapping) \
     M(RemoteLayerTree) \
     M(Resize) \
     M(ResourceLoadStatistics) \
index 431e3c1..1acd5dd 100644 (file)
@@ -103,4 +103,9 @@ uint64_t WebBackForwardListItem::highestUsedItemID()
     return highestItemID;
 }
 
+void WebBackForwardListItem::setSuspendedPage(SuspendedPageProxy& page)
+{
+    m_suspendedPage = &page;
+}
+
 } // namespace WebKit
index e4bd507..4ea2ca7 100644 (file)
@@ -41,6 +41,8 @@ class Encoder;
 
 namespace WebKit {
 
+class SuspendedPageProxy;
+
 class WebBackForwardListItem : public API::ObjectImpl<API::Object::Type::BackForwardListItem> {
 public:
     static Ref<WebBackForwardListItem> create(BackForwardListItemState&&, uint64_t pageID);
@@ -63,6 +65,7 @@ public:
     ViewSnapshot* snapshot() const { return m_itemState.snapshot.get(); }
     void setSnapshot(RefPtr<ViewSnapshot>&& snapshot) { m_itemState.snapshot = WTFMove(snapshot); }
 #endif
+    void setSuspendedPage(SuspendedPageProxy&);
 
     static uint64_t highestUsedItemID();
 
@@ -71,6 +74,7 @@ private:
 
     BackForwardListItemState m_itemState;
     uint64_t m_pageID;
+    SuspendedPageProxy* m_suspendedPage { nullptr };
 };
 
 typedef Vector<Ref<WebBackForwardListItem>> BackForwardListItemVector;
diff --git a/Source/WebKit/UIProcess/SuspendedPageProxy.cpp b/Source/WebKit/UIProcess/SuspendedPageProxy.cpp
new file mode 100644 (file)
index 0000000..4460f44
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "config.h"
+#include "SuspendedPageProxy.h"
+
+#include "Logging.h"
+#include "WebPageMessages.h"
+#include "WebPageProxy.h"
+#include "WebPageProxyMessages.h"
+#include "WebProcessMessages.h"
+#include "WebProcessProxy.h"
+#include <wtf/DebugUtilities.h>
+
+namespace WebKit {
+
+#if !LOG_DISABLED
+static const HashSet<IPC::StringReference>& messageNamesToIgnoreWhileSuspended()
+{
+    static NeverDestroyed<HashSet<IPC::StringReference>> messageNames;
+    static std::once_flag onceFlag;
+    std::call_once(onceFlag, [] {
+        messageNames.get().add("BackForwardAddItem");
+        messageNames.get().add("ClearAllEditCommands");
+        messageNames.get().add("DidChangeContentSize");
+        messageNames.get().add("DidChangeMainDocument");
+        messageNames.get().add("DidChangeProgress");
+        messageNames.get().add("DidCommitLoadForFrame");
+        messageNames.get().add("DidDestroyNavigation");
+        messageNames.get().add("DidFinishDocumentLoadForFrame");
+        messageNames.get().add("DidFinishProgress");
+        messageNames.get().add("DidFirstLayoutForFrame");
+        messageNames.get().add("DidFirstVisuallyNonEmptyLayoutForFrame");
+        messageNames.get().add("DidNavigateWithNavigationData");
+        messageNames.get().add("DidReachLayoutMilestone");
+        messageNames.get().add("DidSaveToPageCache");
+        messageNames.get().add("DidStartProgress");
+        messageNames.get().add("DidStartProvisionalLoadForFrame");
+        messageNames.get().add("EditorStateChanged");
+        messageNames.get().add("PageExtendedBackgroundColorDidChange");
+        messageNames.get().add("SetRenderTreeSize");
+        messageNames.get().add("SetStatusText");
+    });
+
+    return messageNames;
+}
+#endif
+
+SuspendedPageProxy::SuspendedPageProxy(WebPageProxy& page, WebProcessProxy& process, WebBackForwardListItem& item)
+    : m_page(page)
+    , m_process(&process)
+    , m_backForwardListItem(item)
+{
+    m_backForwardListItem->setSuspendedPage(*this);
+    m_process->send(Messages::WebPage::SetIsSuspended(true), m_page.pageID());
+}
+
+SuspendedPageProxy::~SuspendedPageProxy()
+{
+    if (m_process)
+        m_process->suspendedPageWasDestroyed(*this);
+}
+
+void SuspendedPageProxy::webProcessDidClose(WebProcessProxy& process)
+{
+    ASSERT_UNUSED(process, &process == m_process);
+    m_process = nullptr;
+
+    m_page.suspendedPageProcessClosed(*this);
+}
+
+void SuspendedPageProxy::didFinishLoad()
+{
+    ASSERT(m_process);
+    LOG(ProcessSwapping, "SuspendedPageProxy %s from process %i finished transition to suspended", loggingString(), m_process->processIdentifier());
+
+    m_finishedSuspending = true;
+
+    m_process->send(Messages::WebProcess::UpdateActivePages(), 0);
+}
+
+void SuspendedPageProxy::didReceiveMessage(IPC::Connection&, IPC::Decoder& decoder)
+{
+    ASSERT(decoder.messageReceiverName() == Messages::WebPageProxy::messageReceiverName());
+
+    if (decoder.messageName() == Messages::WebPageProxy::DidFinishLoadForFrame::name()) {
+        didFinishLoad();
+        return;
+    }
+#if !LOG_DISABLED
+    if (messageNamesToIgnoreWhileSuspended().contains(decoder.messageName()))
+        LOG(ProcessSwapping, "SuspendedPageProxy received unexpected WebPageProxy message '%s'", decoder.messageName().toString().data());
+#endif
+}
+
+#if !LOG_DISABLED
+const char* SuspendedPageProxy::loggingString() const
+{
+    return debugString("(", String::format("%p", this), "page ID ", String::number(m_page.pageID()), ", m_finishedSuspending ", String::number(m_finishedSuspending), ")");
+}
+#endif
+
+} // namespace WebKit
diff --git a/Source/WebKit/UIProcess/SuspendedPageProxy.h b/Source/WebKit/UIProcess/SuspendedPageProxy.h
new file mode 100644 (file)
index 0000000..cd9cbc0
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include "Connection.h"
+#include "WebBackForwardListItem.h"
+#include <wtf/RefCounted.h>
+
+namespace WebKit {
+
+class WebPageProxy;
+class WebProcessProxy;
+
+class SuspendedPageProxy : public RefCounted<SuspendedPageProxy> {
+public:
+    static Ref<SuspendedPageProxy> create(WebPageProxy& page, WebProcessProxy& process, WebBackForwardListItem& item)
+    {
+        return adoptRef(*new SuspendedPageProxy(page, process, item));
+    }
+
+    virtual ~SuspendedPageProxy();
+
+    void didReceiveMessage(IPC::Connection&, IPC::Decoder&);
+
+    WebPageProxy& page() const { return m_page; }
+    WebProcessProxy* process() const { return m_process; }
+    WebBackForwardListItem& item() const { return m_backForwardListItem; }
+
+    bool finishedSuspending() const { return m_finishedSuspending; }
+
+    void webProcessDidClose(WebProcessProxy&);
+
+#if !LOG_DISABLED
+    const char* loggingString() const;
+#endif
+
+private:
+    SuspendedPageProxy(WebPageProxy&, WebProcessProxy&, WebBackForwardListItem&);
+
+    void didFinishLoad();
+
+    WebPageProxy& m_page;
+    WebProcessProxy* m_process;
+    Ref<WebBackForwardListItem> m_backForwardListItem;
+
+    bool m_finishedSuspending { false };
+};
+
+} // namespace WebKit
index 5ca1bd2..8f3740e 100644 (file)
@@ -642,7 +642,7 @@ void WebPageProxy::handleSynchronousMessage(IPC::Connection& connection, const S
 void WebPageProxy::reattachToWebProcess()
 {
     auto process = makeRef(m_process->processPool().createNewWebProcessRespectingProcessCountLimit(m_websiteDataStore.get()));
-    reattachToWebProcess(WTFMove(process));
+    reattachToWebProcess(WTFMove(process), false);
 }
 
 void WebPageProxy::attachToProcessForNavigation(Ref<WebProcessProxy>&& process)
@@ -653,17 +653,44 @@ void WebPageProxy::attachToProcessForNavigation(Ref<WebProcessProxy>&& process)
 
     // FIXME: this is to fix the ASSERT(isValid()) inside reattachToWebProcess, some other way to fix this is needed.
     m_isValid = false;
-    reattachToWebProcess(WTFMove(process));
+    reattachToWebProcess(WTFMove(process), true);
 }
 
-void WebPageProxy::reattachToWebProcess(Ref<WebProcessProxy>&& process)
+SuspendedPageProxy* WebPageProxy::maybeCreateSuspendedPage(WebProcessProxy& process)
+{
+    ASSERT(!m_suspendedPage || m_suspendedPage->process() != &process);
+
+    auto* currentItem = m_backForwardList->currentItem();
+    if (!currentItem) {
+        LOG(ProcessSwapping, "WebPageProxy %" PRIu64 " unable to create suspended page for process pid %i - No current back/forward item", pageID(), process.processIdentifier());
+        return nullptr;
+    }
+
+    m_suspendedPage = SuspendedPageProxy::create(*this, process, *currentItem);
+
+    LOG(ProcessSwapping, "WebPageProxy %" PRIu64 " created suspended page %s for process pid %i, back/forward item %" PRIu64, pageID(), m_suspendedPage->loggingString(), process.processIdentifier(), currentItem->itemID());
+
+    return m_suspendedPage.get();
+}
+
+void WebPageProxy::suspendedPageProcessClosed(SuspendedPageProxy& page)
+{
+    ASSERT_UNUSED(page, &page == m_suspendedPage.get());
+    m_suspendedPage = nullptr;
+}
+
+void WebPageProxy::reattachToWebProcess(Ref<WebProcessProxy>&& process, bool suspendInOldProcess)
 {
     ASSERT(!m_isClosed);
     ASSERT(!isValid());
 
     m_isValid = true;
-    m_process->removeWebPage(*this, m_pageID);
-    m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID);
+
+    if (!suspendInOldProcess) {
+        m_process->removeWebPage(*this, m_pageID);
+        m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_pageID);
+    } else
+        m_process->suspendWebPageProxy(*this);
 
     m_process = WTFMove(process);
 
@@ -2354,7 +2381,7 @@ void WebPageProxy::receivedPolicyDecision(PolicyAction action, WebFrameProxy& fr
             auto proposedProcess = process().processPool().processForNavigation(*this, *navigation, action);
 
             if (proposedProcess.ptr() != &process()) {
-                LOG(Loading, "Switching from process %i to new process for navigation %" PRIu64 " '%s'", processIdentifier(), navigation->navigationID(), navigation->loggingString().utf8().data());
+                LOG(ProcessSwapping, "Switching from process %i to new process for navigation %" PRIu64 " '%s'", processIdentifier(), navigation->navigationID(), navigation->loggingString().utf8().data());
 
                 RunLoop::main().dispatch([this, protectedThis = makeRef(*this), navigation = makeRef(*navigation), proposedProcess = WTFMove(proposedProcess)]() mutable {
                     continueNavigationInNewProcess(navigation.get(), WTFMove(proposedProcess));
@@ -3571,6 +3598,8 @@ void WebPageProxy::didFinishDocumentLoadForFrame(uint64_t frameID, uint64_t navi
 
 void WebPageProxy::didFinishLoadForFrame(uint64_t frameID, uint64_t navigationID, const UserData& userData)
 {
+    LOG(Loading, "WebPageProxy::didFinishLoadForFrame - WebPageProxy %p with navigationID %llu didFinishLoad", this, navigationID);
+
     PageClientProtector protector(m_pageClient);
 
     WebFrameProxy* frame = m_process->webFrame(frameID);
index 94b98bc..151c576 100644 (file)
@@ -44,6 +44,7 @@
 #include "ProcessThrottler.h"
 #include "SandboxExtension.h"
 #include "ShareableBitmap.h"
+#include "SuspendedPageProxy.h"
 #include "SystemPreviewController.h"
 #include "UserMediaPermissionRequestManagerProxy.h"
 #include "VisibleContentRectUpdateInfo.h"
@@ -1286,6 +1287,9 @@ public:
 
     WebPreferencesStore preferencesStore() const;
 
+    SuspendedPageProxy* maybeCreateSuspendedPage(WebProcessProxy&);
+    void suspendedPageProcessClosed(SuspendedPageProxy&);
+
 private:
     WebPageProxy(PageClient&, WebProcessProxy&, uint64_t pageID, Ref<API::PageConfiguration>&&);
     void platformInitialize();
@@ -1436,7 +1440,7 @@ private:
 
     void reattachToWebProcess();
     void attachToProcessForNavigation(Ref<WebProcessProxy>&&);
-    void reattachToWebProcess(Ref<WebProcessProxy>&&);
+    void reattachToWebProcess(Ref<WebProcessProxy>&&, bool suspendInOldProcess);
 
     RefPtr<API::Navigation> reattachToWebProcessForReload();
     RefPtr<API::Navigation> reattachToWebProcessWithItem(WebBackForwardListItem&);
@@ -2129,6 +2133,11 @@ private:
 #endif
 
     std::optional<MonotonicTime> m_pageLoadStart;
+
+    // FIXME: Support more than one suspended page per WebPageProxy,
+    // and have a global collection of them per process pool
+    // (e.g. for that process pool's page cache)
+    RefPtr<SuspendedPageProxy> m_suspendedPage;
 };
 
 } // namespace WebKit
index ec6a410..bf5fe4a 100644 (file)
@@ -34,6 +34,7 @@
 #include "Logging.h"
 #include "PluginInfoStore.h"
 #include "PluginProcessManager.h"
+#include "SuspendedPageProxy.h"
 #include "TextChecker.h"
 #include "TextCheckerState.h"
 #include "UIMessagePortChannelProvider.h"
@@ -395,6 +396,27 @@ void WebProcessProxy::addExistingWebPage(WebPageProxy& webPage, uint64_t pageID)
     updateBackgroundResponsivenessTimer();
 }
 
+void WebProcessProxy::suspendWebPageProxy(WebPageProxy& webPage)
+{
+    if (auto* suspendedPage = webPage.maybeCreateSuspendedPage(*this)) {
+        LOG(ProcessSwapping, "WebProcessProxy pid %i added suspended page %s", processIdentifier(), suspendedPage->loggingString());
+        m_suspendedPageMap.set(webPage.pageID(), suspendedPage);
+    }
+
+    removeWebPage(webPage, webPage.pageID());
+    removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), webPage.pageID());
+}
+
+void WebProcessProxy::suspendedPageWasDestroyed(SuspendedPageProxy& suspendedPage)
+{
+    LOG(ProcessSwapping, "WebProcessProxy pid %i suspended page %s was destroyed", processIdentifier(), suspendedPage.loggingString());
+
+    ASSERT(m_suspendedPageMap.contains(suspendedPage.page().pageID()));
+    m_suspendedPageMap.remove(suspendedPage.page().pageID());
+
+    maybeShutDown();
+}
+
 void WebProcessProxy::removeWebPage(WebPageProxy& webPage, uint64_t pageID)
 {
     auto* removedPage = m_pageMap.take(pageID);
@@ -414,12 +436,7 @@ void WebProcessProxy::removeWebPage(WebPageProxy& webPage, uint64_t pageID)
     for (auto itemID : itemIDsToRemove)
         m_backForwardListItemMap.remove(itemID);
 
-    // If this was the last WebPage open in that web process, and we have no other reason to keep it alive, let it go.
-    // We only allow this when using a network process, as otherwise the WebProcess needs to preserve its session state.
-    if (state() == State::Terminated || !canTerminateChildProcess())
-        return;
-
-    shutDown();
+    maybeShutDown();
 }
 
 void WebProcessProxy::addVisitedLinkStore(VisitedLinkStore& store)
@@ -637,6 +654,15 @@ void WebProcessProxy::didReceiveMessage(IPC::Connection& connection, IPC::Decode
         return;
     }
 
+    // WebPageProxy messages are normally handled by the normal "dispatchMessage" up above.
+    // If they were not handled there, then they may potentially be handled by SuspendedPageProxy objects.
+    if (decoder.messageReceiverName() == Messages::WebPageProxy::messageReceiverName()) {
+        if (auto* suspendedPage = m_suspendedPageMap.get(decoder.destinationID())) {
+            suspendedPage->didReceiveMessage(connection, decoder);
+            return;
+        }
+    }
+
     // FIXME: Add unhandled message logging.
 }
 
@@ -680,6 +706,10 @@ void WebProcessProxy::didClose(IPC::Connection&)
     for (auto& page : pages)
         page->processDidTerminate(ProcessTerminationReason::Crash);
 
+    for (auto* suspendedPage : copyToVectorOf<SuspendedPageProxy*>(m_suspendedPageMap.values()))
+        suspendedPage->webProcessDidClose(*this);
+
+    m_suspendedPageMap.clear();
 }
 
 void WebProcessProxy::didReceiveInvalidMessage(IPC::Connection& connection, IPC::StringReference messageReceiverName, IPC::StringReference messageName)
@@ -827,9 +857,17 @@ void WebProcessProxy::didDestroyUserGestureToken(uint64_t identifier)
     m_userInitiatedActionMap.remove(identifier);
 }
 
+void WebProcessProxy::maybeShutDown()
+{
+    if (state() == State::Terminated || !canTerminateChildProcess())
+        return;
+
+    shutDown();
+}
+
 bool WebProcessProxy::canTerminateChildProcess()
 {
-    if (!m_pageMap.isEmpty())
+    if (!m_pageMap.isEmpty() || !m_suspendedPageMap.isEmpty())
         return false;
 
     if (!m_processPool->shouldTerminate(this))
index 80ab8a6..c6770ea 100644 (file)
@@ -66,6 +66,7 @@ namespace WebKit {
 class NetworkProcessProxy;
 class ObjCObjectGraph;
 class PageClient;
+class SuspendedPageProxy;
 class UserMediaCaptureManagerProxy;
 class VisitedLinkStore;
 class WebBackForwardListItem;
@@ -208,6 +209,9 @@ public:
     void didCommitProvisionalLoad() { m_hasCommittedAnyProvisionalLoads = true; }
     bool hasCommittedAnyProvisionalLoads() const { return m_hasCommittedAnyProvisionalLoads; }
 
+    void suspendWebPageProxy(WebPageProxy&);
+    void suspendedPageWasDestroyed(SuspendedPageProxy&);
+
 protected:
     static uint64_t generatePageID();
     WebProcessProxy(WebProcessPool&, WebsiteDataStore&);
@@ -221,6 +225,7 @@ private:
     // Called when the web process has crashed or we know that it will terminate soon.
     // Will potentially cause the WebProcessProxy object to be freed.
     void shutDown();
+    void maybeShutDown();
 
     // IPC message handlers.
     void addOrUpdateBackForwardItem(uint64_t itemID, uint64_t pageID, const PageState&);
@@ -298,6 +303,7 @@ private:
     HashSet<String> m_localPathsWithAssumedReadAccess;
 
     WebPageProxyMap m_pageMap;
+    HashMap<uint64_t, SuspendedPageProxy*> m_suspendedPageMap;
     WebFrameProxyMap m_frameMap;
     WebBackForwardListItemMap m_backForwardListItemMap;
     UserInitiatedActionMap m_userInitiatedActionMap;
index 7c2d465..ad15475 100644 (file)
                515BE1B41D5917FF00DD7C68 /* UIGamepad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 515BE1AC1D555C5100DD7C68 /* UIGamepad.cpp */; };
                515BE1B51D5917FF00DD7C68 /* UIGamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = 515BE1AD1D555C5100DD7C68 /* UIGamepad.h */; };
                515BE1B71D5A94FD00DD7C68 /* UIGamepadProviderMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 515BE1B61D5A94F900DD7C68 /* UIGamepadProviderMac.mm */; };
+               515C415C207D7CAE00726E02 /* SuspendedPageProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 515C415A207D74E000726E02 /* SuspendedPageProxy.cpp */; };
                515E7727183DD6F60007203F /* AsyncRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 515E7725183DD6F60007203F /* AsyncRequest.cpp */; };
                515E7728183DD6F60007203F /* AsyncRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 515E7726183DD6F60007203F /* AsyncRequest.h */; };
                5160BFE113381DF900918999 /* LoggingFoundation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5160BFE013381DF900918999 /* LoggingFoundation.mm */; };
                515BE1B01D59006900DD7C68 /* GamepadData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GamepadData.h; sourceTree = "<group>"; };
                515BE1B11D5902B600DD7C68 /* GamepadData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GamepadData.cpp; sourceTree = "<group>"; };
                515BE1B61D5A94F900DD7C68 /* UIGamepadProviderMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = UIGamepadProviderMac.mm; path = UIProcess/Gamepad/mac/UIGamepadProviderMac.mm; sourceTree = SOURCE_ROOT; };
+               515C415A207D74E000726E02 /* SuspendedPageProxy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SuspendedPageProxy.cpp; sourceTree = "<group>"; };
+               515C415B207D74E100726E02 /* SuspendedPageProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SuspendedPageProxy.h; sourceTree = "<group>"; };
                515E7725183DD6F60007203F /* AsyncRequest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AsyncRequest.cpp; sourceTree = "<group>"; };
                515E7726183DD6F60007203F /* AsyncRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncRequest.h; sourceTree = "<group>"; };
                5160BFE013381DF900918999 /* LoggingFoundation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LoggingFoundation.mm; sourceTree = "<group>"; };
                                414DEDD51F9EDDDF0047C40D /* ServiceWorkerProcessProxy.h */,
                                51A4D5A816CAC4FF000E615E /* StatisticsRequest.cpp */,
                                514BDED216C98EDD00E4E25E /* StatisticsRequest.h */,
+                               515C415A207D74E000726E02 /* SuspendedPageProxy.cpp */,
+                               515C415B207D74E100726E02 /* SuspendedPageProxy.h */,
                                318A1F04204F4764003480BC /* SystemPreviewController.cpp */,
                                3157135D2040A9B20084F9CF /* SystemPreviewController.h */,
                                1AA417C912C00CCA002BE67B /* TextChecker.h */,
                                5118E9AC1F295977003EF9F5 /* StorageToWebProcessConnectionMessageReceiver.cpp in Sources */,
                                1AE00D6B18327C1200087DD7 /* StringReference.cpp in Sources */,
                                296BD85E15019BC30071F424 /* StringUtilities.mm in Sources */,
+                               515C415C207D7CAE00726E02 /* SuspendedPageProxy.cpp in Sources */,
                                318A1F05204F4764003480BC /* SystemPreviewController.cpp in Sources */,
                                3157135E2040A9B20084F9CF /* SystemPreviewControllerCocoa.mm in Sources */,
                                1ZZ417EF12C00D87002BE67B /* TextCheckerCompletion.cpp in Sources */,
index 1ae3d73..953da7d 100644 (file)
@@ -5866,6 +5866,11 @@ void WebPage::urlSchemeTaskDidComplete(uint64_t handlerIdentifier, uint64_t task
     handler->taskDidComplete(taskIdentifier, error);
 }
 
+void WebPage::setIsSuspended(bool suspended)
+{
+    m_isSuspended = suspended;
+}
+
 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
 static uint64_t nextRequestStorageAccessContextId()
 {
index 4c3b595..a291b81 100644 (file)
@@ -1072,6 +1072,8 @@ public:
 
     UserContentControllerIdentifier userContentControllerIdentifier() const { return m_userContentController->identifier(); }
 
+    bool isSuspended() const { return m_isSuspended; }
+
 private:
     WebPage(uint64_t pageID, WebPageCreationParameters&&);
 
@@ -1388,6 +1390,8 @@ private:
     void urlSchemeTaskDidReceiveData(uint64_t handlerIdentifier, uint64_t taskIdentifier, const IPC::DataReference&);
     void urlSchemeTaskDidComplete(uint64_t handlerIdentifier, uint64_t taskIdentifier, const WebCore::ResourceError&);
 
+    void setIsSuspended(bool);
+
     RefPtr<WebImage> snapshotAtSize(const WebCore::IntRect&, const WebCore::IntSize& bitmapSize, SnapshotOptions);
     RefPtr<WebImage> snapshotNode(WebCore::Node&, SnapshotOptions, unsigned maximumPixelCount = std::numeric_limits<unsigned>::max());
 #if USE(CF)
@@ -1705,6 +1709,7 @@ private:
     std::unique_ptr<WebCredentialsMessenger> m_credentialsMessenger;
 #endif
 
+    bool m_isSuspended { false };
 };
 
 } // namespace WebKit
index d8fe4e5..e0ff521 100644 (file)
@@ -491,6 +491,8 @@ messages -> WebPage LegacyReceiver {
     URLSchemeTaskDidReceiveData(uint64_t handlerIdentifier, uint64_t taskIdentifier, IPC::DataReference data)
     URLSchemeTaskDidComplete(uint64_t handlerIdentifier, uint64_t taskIdentifier, WebCore::ResourceError error)
 
+    SetIsSuspended(bool suspended)
+
 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
     StorageAccessResponse(bool wasGranted, uint64_t contextId)
 #endif
index ea695bb..efb07e3 100644 (file)
@@ -128,6 +128,8 @@ messages -> WebProcess LegacyReceiver {
     CheckProcessLocalPortForActivity(struct WebCore::MessagePortIdentifier port, uint64_t callbackIdentifier)
     MessagesAvailableForPort(struct WebCore::MessagePortIdentifier port)
 
+    UpdateActivePages()
+
 #if PLATFORM(MAC)
     SetScreenProperties(uint32_t primaryScreenID, HashMap<uint32_t, WebCore::ScreenProperties> screenProperties)
 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
index 6851839..a147207 100644 (file)
@@ -387,7 +387,7 @@ void WebProcess::updateActivePages()
     auto activePageURLs = adoptNS([[NSMutableArray alloc] init]);
 
     for (auto& page : m_pageMap.values()) {
-        if (page->usesEphemeralSession())
+        if (page->usesEphemeralSession() || page->isSuspended())
             continue;
 
         if (NSURL *originAsURL = origin(*page))
index 753c33a..e331e9f 100644 (file)
@@ -1,3 +1,12 @@
+2018-04-13  Brady Eidson  <beidson@apple.com>
+
+        Introduce SuspendedPageProxy to keep old web processes around after their WebPageProxy has been swapped to a new one.
+        https://bugs.webkit.org/show_bug.cgi?id=184559
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/ProcessSwapOnNavigation.mm:
+
 2018-04-13  Chris Dumez  <cdumez@apple.com>
 
         input.webkitEntries does not work as expected when folder contains accented chars
index 772bdb2..2299c05 100644 (file)
@@ -751,4 +751,44 @@ TEST(ProcessSwap, MainFramesOnly)
     EXPECT_EQ(1u, seenPIDs.size());
 }
 
+TEST(ProcessSwap, OnePreviousProcessRemains)
+{
+    auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
+    [processPoolConfiguration setProcessSwapsOnNavigation:YES];
+    auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
+
+    auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [webViewConfiguration setProcessPool:processPool.get()];
+    auto handler = adoptNS([[PSONScheme alloc] init]);
+    [webViewConfiguration setURLSchemeHandler:handler.get() forURLScheme:@"PSON"];
+
+    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:@"pson://host1/main.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson://host2/main.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"pson://host3/main.html"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&done);
+    done = false;
+
+    // Navigations to 3 different domains, we expect to have seen 3 different PIDs
+    EXPECT_EQ(3u, seenPIDs.size());
+
+    // But only 2 of those processes should still be alive
+    EXPECT_EQ(2u, [processPool _webProcessCount]);
+}
+
 #endif // WK_API_ENABLED