Implement DownloadMonitor to prevent long-running slow downloads from background...
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Mar 2019 22:38:55 +0000 (22:38 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Mar 2019 22:38:55 +0000 (22:38 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195785

Patch by Alex Christensen <achristensen@webkit.org> on 2019-03-18
Reviewed by Geoffrey Garen.

Source/WebKit:

This is similar to what was updated in <rdar://problem/42677685> but for WebKit Downloads.
To test it I implemented a throttled TCP server, SPI to speed up DownloadMonitor's timer,
and SPI to synthesize the application going into the background and foreground, which
NSNotificationCenter does on iOS.

* NetworkProcess/Downloads/Download.cpp:
(WebKit::Download::didReceiveData):
(WebKit::Download::didFinish):
(WebKit::Download::didFail):
(WebKit::Download::didCancel):
* NetworkProcess/Downloads/Download.h:
(WebKit::Download::applicationEnteredBackground):
(WebKit::Download::applicationEnteredForeground):
(WebKit::Download::manager const):
* NetworkProcess/Downloads/DownloadManager.cpp:
(WebKit::DownloadManager::downloadFinished):
(WebKit::DownloadManager::applicationDidEnterBackground):
(WebKit::DownloadManager::applicationWillEnterForeground):
* NetworkProcess/Downloads/DownloadManager.h:
* NetworkProcess/Downloads/DownloadMap.cpp:
(WebKit::DownloadMap::values):
* NetworkProcess/Downloads/DownloadMap.h:
* NetworkProcess/Downloads/DownloadMonitor.cpp: Added.
(WebKit::operator _kbps):
(WebKit::timeUntilNextInterval):
(WebKit::DownloadMonitor::DownloadMonitor):
(WebKit::DownloadMonitor::measuredThroughputRate const):
(WebKit::DownloadMonitor::downloadReceivedBytes):
(WebKit::DownloadMonitor::applicationEnteredForeground):
(WebKit::DownloadMonitor::applicationEnteredBackground):
(WebKit::DownloadMonitor::speedMultiplier const):
(WebKit::DownloadMonitor::timerFired):
* NetworkProcess/Downloads/DownloadMonitor.h: Added.
* NetworkProcess/NetworkProcess.cpp:
(WebKit::NetworkProcess::initializeNetworkProcess):
(WebKit::NetworkProcess::applicationDidEnterBackground):
(WebKit::NetworkProcess::applicationWillEnterForeground):
* NetworkProcess/NetworkProcess.h:
* NetworkProcess/NetworkProcess.messages.in:
* NetworkProcess/NetworkProcessCreationParameters.cpp:
(WebKit::NetworkProcessCreationParameters::encode const):
(WebKit::NetworkProcessCreationParameters::decode):
* NetworkProcess/NetworkProcessCreationParameters.h:
* Sources.txt:
* SourcesCocoa.txt:
* UIProcess/API/APIProcessPoolConfiguration.cpp:
(API::ProcessPoolConfiguration::copy):
* UIProcess/API/APIProcessPoolConfiguration.h:
* UIProcess/API/C/WKContext.cpp:
(WKContextDownloadURLRequest):
(WKContextResumeDownload):
* UIProcess/API/Cocoa/WKProcessPool.mm:
(-[WKProcessPool _synthesizeAppIsBackground:]):
(-[WKProcessPool _downloadURLRequest:originatingWebView:]):
* UIProcess/API/Cocoa/WKProcessPoolPrivate.h:
* UIProcess/API/Cocoa/_WKProcessPoolConfiguration.h:
* UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm:
(-[_WKProcessPoolConfiguration downloadMonitorSpeedMultiplier]):
(-[_WKProcessPoolConfiguration setDownloadMonitorSpeedMultiplier:]):
* UIProcess/Cocoa/DownloadProxyMapCocoa.mm: Added.
(WebKit::DownloadProxyMap::platformCreate):
(WebKit::DownloadProxyMap::platformDestroy):
* UIProcess/Cocoa/WebProcessPoolCocoa.mm:
(WebKit::WebProcessPool::platformInitializeNetworkProcess):
* UIProcess/Downloads/DownloadProxy.cpp:
(WebKit::DownloadProxy::didFinish):
(WebKit::DownloadProxy::didFail):
(WebKit::DownloadProxy::didCancel):
* UIProcess/Downloads/DownloadProxyMap.cpp:
(WebKit::DownloadProxyMap::DownloadProxyMap):
(WebKit::DownloadProxyMap::~DownloadProxyMap):
(WebKit::DownloadProxyMap::platformCreate):
(WebKit::DownloadProxyMap::platformDestroy):
(WebKit::DownloadProxyMap::applicationDidEnterBackground):
(WebKit::DownloadProxyMap::applicationWillEnterForeground):
(WebKit::DownloadProxyMap::createDownloadProxy):
(WebKit::DownloadProxyMap::downloadFinished):
* UIProcess/Downloads/DownloadProxyMap.h:
* UIProcess/Network/NetworkProcessProxy.cpp:
(WebKit::NetworkProcessProxy::synthesizeAppIsBackground):
(WebKit::NetworkProcessProxy::createDownloadProxy):
* UIProcess/Network/NetworkProcessProxy.h:
* UIProcess/PageClient.h:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::receivedPolicyDecision):
(WebKit::WebPageProxy::handleDownloadRequest):
* UIProcess/WebPageProxy.h:
* UIProcess/WebProcessPool.cpp:
(WebKit::WebProcessPool::download):
(WebKit::WebProcessPool::resumeDownload):
(WebKit::WebProcessPool::createDownloadProxy):
(WebKit::WebProcessPool::synthesizeAppIsBackground):
* UIProcess/WebProcessPool.h:
* UIProcess/ios/PageClientImplIOS.h:
* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::handleDownloadRequest):
* UIProcess/mac/PageClientImplMac.h:
* UIProcess/mac/PageClientImplMac.mm:
(WebKit::PageClientImpl::handleDownloadRequest):
* WebKit.xcodeproj/project.pbxproj:

Tools:

* TestWebKitAPI/TCPServer.cpp: Added.
(TestWebKitAPI::TCPServer::TCPServer):
(TestWebKitAPI::TCPServer::~TCPServer):
(TestWebKitAPI::TCPServer::socketBindListen):
(TestWebKitAPI::TCPServer::waitForAndReplyToRequests):
* TestWebKitAPI/TCPServer.h: Added.
(TestWebKitAPI::TCPServer::port const):
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/Download.mm:
(-[DownloadMonitorTestDelegate _downloadDidStart:]):
(-[DownloadMonitorTestDelegate _downloadDidCancel:]):
(-[DownloadMonitorTestDelegate _download:decideDestinationWithSuggestedFilename:completionHandler:]):
(-[DownloadMonitorTestDelegate _download:didReceiveData:]):
(TestWebKitAPI::respondSlowly):
(TestWebKitAPI::webViewWithDownloadMonitorSpeedMultiplier):
(TestWebKitAPI::downloadAtRate):
(TestWebKitAPI::TEST):

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

54 files changed:
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/Downloads/Download.cpp
Source/WebKit/NetworkProcess/Downloads/Download.h
Source/WebKit/NetworkProcess/Downloads/DownloadManager.cpp
Source/WebKit/NetworkProcess/Downloads/DownloadManager.h
Source/WebKit/NetworkProcess/Downloads/DownloadMap.cpp
Source/WebKit/NetworkProcess/Downloads/DownloadMap.h
Source/WebKit/NetworkProcess/Downloads/DownloadMonitor.cpp [new file with mode: 0644]
Source/WebKit/NetworkProcess/Downloads/DownloadMonitor.h [new file with mode: 0644]
Source/WebKit/NetworkProcess/NetworkProcess.cpp
Source/WebKit/NetworkProcess/NetworkProcess.h
Source/WebKit/NetworkProcess/NetworkProcess.messages.in
Source/WebKit/NetworkProcess/NetworkProcessCreationParameters.cpp
Source/WebKit/NetworkProcess/NetworkProcessCreationParameters.h
Source/WebKit/NetworkProcess/cache/CacheStorageEngineCaches.cpp
Source/WebKit/NetworkProcess/cache/CacheStorageEngineConnection.cpp
Source/WebKit/Sources.txt
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/APIProcessPoolConfiguration.cpp
Source/WebKit/UIProcess/API/APIProcessPoolConfiguration.h
Source/WebKit/UIProcess/API/C/WKContext.cpp
Source/WebKit/UIProcess/API/Cocoa/WKProcessPool.mm
Source/WebKit/UIProcess/API/Cocoa/WKProcessPoolPrivate.h
Source/WebKit/UIProcess/API/Cocoa/_WKProcessPoolConfiguration.h
Source/WebKit/UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm
Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp
Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp
Source/WebKit/UIProcess/API/gtk/PageClientImpl.h
Source/WebKit/UIProcess/API/wpe/PageClientImpl.cpp
Source/WebKit/UIProcess/API/wpe/PageClientImpl.h
Source/WebKit/UIProcess/Cocoa/DownloadProxyMapCocoa.mm [new file with mode: 0644]
Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm
Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp
Source/WebKit/UIProcess/Downloads/DownloadProxyMap.cpp
Source/WebKit/UIProcess/Downloads/DownloadProxyMap.h
Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp
Source/WebKit/UIProcess/Network/NetworkProcessProxy.h
Source/WebKit/UIProcess/PageClient.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebProcessPool.cpp
Source/WebKit/UIProcess/WebProcessPool.h
Source/WebKit/UIProcess/ios/PageClientImplIOS.h
Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
Source/WebKit/UIProcess/mac/PageClientImplMac.h
Source/WebKit/UIProcess/mac/PageClientImplMac.mm
Source/WebKit/UIProcess/win/PageClientImpl.cpp
Source/WebKit/UIProcess/win/PageClientImpl.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/TestWebKitAPI/TCPServer.cpp [new file with mode: 0644]
Tools/TestWebKitAPI/TCPServer.h [new file with mode: 0644]
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm

index 2a985e2..f87cd5f 100644 (file)
@@ -1,3 +1,111 @@
+2019-03-18  Alex Christensen  <achristensen@webkit.org>
+
+        Implement DownloadMonitor to prevent long-running slow downloads from background apps
+        https://bugs.webkit.org/show_bug.cgi?id=195785
+
+        Reviewed by Geoffrey Garen.
+
+        This is similar to what was updated in <rdar://problem/42677685> but for WebKit Downloads.
+        To test it I implemented a throttled TCP server, SPI to speed up DownloadMonitor's timer,
+        and SPI to synthesize the application going into the background and foreground, which
+        NSNotificationCenter does on iOS.
+
+        * NetworkProcess/Downloads/Download.cpp:
+        (WebKit::Download::didReceiveData):
+        (WebKit::Download::didFinish):
+        (WebKit::Download::didFail):
+        (WebKit::Download::didCancel):
+        * NetworkProcess/Downloads/Download.h:
+        (WebKit::Download::applicationEnteredBackground):
+        (WebKit::Download::applicationEnteredForeground):
+        (WebKit::Download::manager const):
+        * NetworkProcess/Downloads/DownloadManager.cpp:
+        (WebKit::DownloadManager::downloadFinished):
+        (WebKit::DownloadManager::applicationDidEnterBackground):
+        (WebKit::DownloadManager::applicationWillEnterForeground):
+        * NetworkProcess/Downloads/DownloadManager.h:
+        * NetworkProcess/Downloads/DownloadMap.cpp:
+        (WebKit::DownloadMap::values):
+        * NetworkProcess/Downloads/DownloadMap.h:
+        * NetworkProcess/Downloads/DownloadMonitor.cpp: Added.
+        (WebKit::operator _kbps):
+        (WebKit::timeUntilNextInterval):
+        (WebKit::DownloadMonitor::DownloadMonitor):
+        (WebKit::DownloadMonitor::measuredThroughputRate const):
+        (WebKit::DownloadMonitor::downloadReceivedBytes):
+        (WebKit::DownloadMonitor::applicationEnteredForeground):
+        (WebKit::DownloadMonitor::applicationEnteredBackground):
+        (WebKit::DownloadMonitor::speedMultiplier const):
+        (WebKit::DownloadMonitor::timerFired):
+        * NetworkProcess/Downloads/DownloadMonitor.h: Added.
+        * NetworkProcess/NetworkProcess.cpp:
+        (WebKit::NetworkProcess::initializeNetworkProcess):
+        (WebKit::NetworkProcess::applicationDidEnterBackground):
+        (WebKit::NetworkProcess::applicationWillEnterForeground):
+        * NetworkProcess/NetworkProcess.h:
+        * NetworkProcess/NetworkProcess.messages.in:
+        * NetworkProcess/NetworkProcessCreationParameters.cpp:
+        (WebKit::NetworkProcessCreationParameters::encode const):
+        (WebKit::NetworkProcessCreationParameters::decode):
+        * NetworkProcess/NetworkProcessCreationParameters.h:
+        * Sources.txt:
+        * SourcesCocoa.txt:
+        * UIProcess/API/APIProcessPoolConfiguration.cpp:
+        (API::ProcessPoolConfiguration::copy):
+        * UIProcess/API/APIProcessPoolConfiguration.h:
+        * UIProcess/API/C/WKContext.cpp:
+        (WKContextDownloadURLRequest):
+        (WKContextResumeDownload):
+        * UIProcess/API/Cocoa/WKProcessPool.mm:
+        (-[WKProcessPool _synthesizeAppIsBackground:]):
+        (-[WKProcessPool _downloadURLRequest:originatingWebView:]):
+        * UIProcess/API/Cocoa/WKProcessPoolPrivate.h:
+        * UIProcess/API/Cocoa/_WKProcessPoolConfiguration.h:
+        * UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm:
+        (-[_WKProcessPoolConfiguration downloadMonitorSpeedMultiplier]):
+        (-[_WKProcessPoolConfiguration setDownloadMonitorSpeedMultiplier:]):
+        * UIProcess/Cocoa/DownloadProxyMapCocoa.mm: Added.
+        (WebKit::DownloadProxyMap::platformCreate):
+        (WebKit::DownloadProxyMap::platformDestroy):
+        * UIProcess/Cocoa/WebProcessPoolCocoa.mm:
+        (WebKit::WebProcessPool::platformInitializeNetworkProcess):
+        * UIProcess/Downloads/DownloadProxy.cpp:
+        (WebKit::DownloadProxy::didFinish):
+        (WebKit::DownloadProxy::didFail):
+        (WebKit::DownloadProxy::didCancel):
+        * UIProcess/Downloads/DownloadProxyMap.cpp:
+        (WebKit::DownloadProxyMap::DownloadProxyMap):
+        (WebKit::DownloadProxyMap::~DownloadProxyMap):
+        (WebKit::DownloadProxyMap::platformCreate):
+        (WebKit::DownloadProxyMap::platformDestroy):
+        (WebKit::DownloadProxyMap::applicationDidEnterBackground):
+        (WebKit::DownloadProxyMap::applicationWillEnterForeground):
+        (WebKit::DownloadProxyMap::createDownloadProxy):
+        (WebKit::DownloadProxyMap::downloadFinished):
+        * UIProcess/Downloads/DownloadProxyMap.h:
+        * UIProcess/Network/NetworkProcessProxy.cpp:
+        (WebKit::NetworkProcessProxy::synthesizeAppIsBackground):
+        (WebKit::NetworkProcessProxy::createDownloadProxy):
+        * UIProcess/Network/NetworkProcessProxy.h:
+        * UIProcess/PageClient.h:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::receivedPolicyDecision):
+        (WebKit::WebPageProxy::handleDownloadRequest):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebProcessPool.cpp:
+        (WebKit::WebProcessPool::download):
+        (WebKit::WebProcessPool::resumeDownload):
+        (WebKit::WebProcessPool::createDownloadProxy):
+        (WebKit::WebProcessPool::synthesizeAppIsBackground):
+        * UIProcess/WebProcessPool.h:
+        * UIProcess/ios/PageClientImplIOS.h:
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::handleDownloadRequest):
+        * UIProcess/mac/PageClientImplMac.h:
+        * UIProcess/mac/PageClientImplMac.mm:
+        (WebKit::PageClientImpl::handleDownloadRequest):
+        * WebKit.xcodeproj/project.pbxproj:
+
 2019-03-18  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [iOS] Native selection views sometimes appear in hidden editable areas after losing focus
index 42d7f1e..7d8b4d4 100644 (file)
@@ -31,6 +31,7 @@
 #include "Connection.h"
 #include "DataReference.h"
 #include "DownloadManager.h"
+#include "DownloadMonitor.h"
 #include "DownloadProxyMessages.h"
 #include "Logging.h"
 #include "NetworkDataTask.h"
@@ -114,6 +115,8 @@ void Download::didReceiveData(uint64_t length)
         RELEASE_LOG_IF_ALLOWED("didReceiveData: Started receiving data (id = %" PRIu64 ")", downloadID().downloadID());
         m_hasReceivedData = true;
     }
+    
+    m_monitor.downloadReceivedBytes(length);
 
     send(Messages::DownloadProxy::DidReceiveData(length));
 }
@@ -129,7 +132,7 @@ void Download::didFinish()
         m_sandboxExtension = nullptr;
     }
 
-    m_downloadManager.downloadFinished(this);
+    m_downloadManager.downloadFinished(*this);
 }
 
 void Download::didFail(const ResourceError& error, const IPC::DataReference& resumeData)
@@ -143,7 +146,7 @@ void Download::didFail(const ResourceError& error, const IPC::DataReference& res
         m_sandboxExtension->revoke();
         m_sandboxExtension = nullptr;
     }
-    m_downloadManager.downloadFinished(this);
+    m_downloadManager.downloadFinished(*this);
 }
 
 void Download::didCancel(const IPC::DataReference& resumeData)
@@ -156,7 +159,7 @@ void Download::didCancel(const IPC::DataReference& resumeData)
         m_sandboxExtension->revoke();
         m_sandboxExtension = nullptr;
     }
-    m_downloadManager.downloadFinished(this);
+    m_downloadManager.downloadFinished(*this);
 }
 
 IPC::Connection* Download::messageSenderConnection() const
index bc07de0..772dd02 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "DownloadID.h"
 #include "DownloadManager.h"
+#include "DownloadMonitor.h"
 #include "MessageSender.h"
 #include "NetworkDataTask.h"
 #include "SandboxExtension.h"
@@ -58,6 +59,7 @@ class ResourceResponse;
 
 namespace WebKit {
 
+class DownloadMonitor;
 class NetworkDataTask;
 class NetworkSession;
 class WebPage;
@@ -89,6 +91,12 @@ public:
     void didFail(const WebCore::ResourceError&, const IPC::DataReference& resumeData);
     void didCancel(const IPC::DataReference& resumeData);
 
+    bool isAlwaysOnLoggingAllowed() const;
+
+    void applicationDidEnterBackground() { m_monitor.applicationDidEnterBackground(); }
+    void applicationWillEnterForeground() { m_monitor.applicationWillEnterForeground(); }
+    DownloadManager& manager() const { return m_downloadManager; }
+
 private:
     // IPC::MessageSender
     IPC::Connection* messageSenderConnection() const override;
@@ -97,8 +105,6 @@ private:
     void platformCancelNetworkLoad();
     void platformDestroyDownload();
 
-    bool isAlwaysOnLoggingAllowed() const;
-
     DownloadManager& m_downloadManager;
     DownloadID m_downloadID;
     Ref<DownloadManager::Client> m_client;
@@ -114,6 +120,7 @@ private:
     PAL::SessionID m_sessionID;
     String m_suggestedName;
     bool m_hasReceivedData { false };
+    DownloadMonitor m_monitor { *this };
 };
 
 } // namespace WebKit
index a50b0a7..80fe1a2 100644 (file)
@@ -169,11 +169,10 @@ void DownloadManager::publishDownloadProgress(DownloadID downloadID, const URL&
 }
 #endif // PLATFORM(COCOA)
 
-void DownloadManager::downloadFinished(Download* download)
+void DownloadManager::downloadFinished(Download& download)
 {
-    ASSERT(m_downloads.contains(download->downloadID()));
-    m_downloads.remove(download->downloadID());
-    
+    ASSERT(m_downloads.contains(download.downloadID()));
+    m_downloads.remove(download.downloadID());
 }
 
 void DownloadManager::didCreateDownload()
@@ -196,4 +195,16 @@ AuthenticationManager& DownloadManager::downloadsAuthenticationManager()
     return m_client.downloadsAuthenticationManager();
 }
 
+void DownloadManager::applicationDidEnterBackground()
+{
+    for (auto& download : m_downloads.values())
+        download->applicationDidEnterBackground();
+}
+
+void DownloadManager::applicationWillEnterForeground()
+{
+    for (auto& download : m_downloads.values())
+        download->applicationWillEnterForeground();
+}
+
 } // namespace WebKit
index 25cdbc3..386c9e7 100644 (file)
@@ -78,6 +78,7 @@ public:
         virtual NetworkBlobRegistry& networkBlobRegistry() = 0;
         virtual void ref() const = 0;
         virtual void deref() const = 0;
+        virtual uint32_t downloadMonitorSpeedMultiplier() const = 0;
     };
 
     explicit DownloadManager(Client&);
@@ -98,10 +99,13 @@ public:
     
     Download* download(DownloadID downloadID) { return m_downloads.get(downloadID); }
 
-    void downloadFinished(Download*);
+    void downloadFinished(Download&);
     bool isDownloading() const { return !m_downloads.isEmpty(); }
     uint64_t activeDownloadCount() const { return m_downloads.size(); }
 
+    void applicationDidEnterBackground();
+    void applicationWillEnterForeground();
+
     void didCreateDownload();
     void didDestroyDownload();
 
index ce4eb27..3299253 100644 (file)
@@ -75,6 +75,11 @@ bool DownloadMap::remove(DownloadID downloadID)
     return result;
 }
 
+auto DownloadMap::values() -> DownloadMapType::ValuesIteratorRange
+{
+    return m_downloads.values();
+}
+
 } // namespace WebKit
 
 #endif // ENABLE(TAKE_UNBOUNDED_NETWORKING_ASSERTION)
index 635a61a..b52567c 100644 (file)
@@ -49,6 +49,8 @@ public:
     DownloadMapType::AddResult add(DownloadID, std::unique_ptr<Download>&&);
     bool remove(DownloadID);
 
+    DownloadMapType::ValuesIteratorRange values();
+
 private:
     DownloadMapType m_downloads;
     std::unique_ptr<ProcessAssertion> m_downloadAssertion;
diff --git a/Source/WebKit/NetworkProcess/Downloads/DownloadMonitor.cpp b/Source/WebKit/NetworkProcess/Downloads/DownloadMonitor.cpp
new file mode 100644 (file)
index 0000000..5f50475
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 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 "DownloadMonitor.h"
+
+#include "Download.h"
+#include "Logging.h"
+
+#undef RELEASE_LOG_IF_ALLOWED
+#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(m_download.isAlwaysOnLoggingAllowed(), Network, "%p - DownloadMonitor::" fmt, this, ##__VA_ARGS__)
+
+namespace WebKit {
+
+constexpr uint64_t operator"" _kbps(unsigned long long kilobytesPerSecond)
+{
+    return kilobytesPerSecond * 1024;
+}
+
+struct ThroughputInterval {
+    Seconds time;
+    uint64_t bytesPerSecond;
+};
+
+static const ThroughputInterval throughputIntervals[] = {
+    { 1_min, 1_kbps },
+    { 5_min, 2_kbps },
+    { 10_min, 4_kbps },
+    { 15_min, 8_kbps },
+    { 20_min, 16_kbps },
+    { 25_min, 32_kbps },
+    { 30_min, 64_kbps },
+    { 45_min, 96_kbps },
+    { 60_min, 128_kbps }
+};
+
+static Seconds timeUntilNextInterval(size_t currentInterval)
+{
+    RELEASE_ASSERT(currentInterval + 1 < WTF_ARRAY_LENGTH(throughputIntervals));
+    return throughputIntervals[currentInterval + 1].time - throughputIntervals[currentInterval].time;
+}
+
+DownloadMonitor::DownloadMonitor(Download& download)
+    : m_download(download)
+{
+}
+
+double DownloadMonitor::measuredThroughputRate() const
+{
+    uint64_t bytes { 0 };
+    for (const auto& timestamp : m_timestamps)
+        bytes += timestamp.bytesReceived;
+    if (!bytes)
+        return 0;
+    ASSERT(!m_timestamps.isEmpty());
+    Seconds timeDifference = m_timestamps.last().time.secondsSinceEpoch() - m_timestamps.first().time.secondsSinceEpoch();
+    double seconds = timeDifference.seconds();
+    if (!seconds)
+        return std::numeric_limits<double>::max();
+    return bytes / seconds;
+}
+
+void DownloadMonitor::downloadReceivedBytes(uint64_t bytesReceived)
+{
+    if (m_timestamps.size() > timestampCapacity - 1) {
+        ASSERT(m_timestamps.size() == timestampCapacity);
+        m_timestamps.removeFirst();
+    }
+    m_timestamps.append({ MonotonicTime::now(), bytesReceived });
+}
+
+void DownloadMonitor::applicationWillEnterForeground()
+{
+    RELEASE_LOG_IF_ALLOWED("applicationWillEnterForeground (id = %" PRIu64 ")", m_download.downloadID().downloadID());
+    m_timer.stop();
+    m_interval = 0;
+}
+
+void DownloadMonitor::applicationDidEnterBackground()
+{
+    RELEASE_LOG_IF_ALLOWED("applicationDidEnterBackground (id = %" PRIu64 ")", m_download.downloadID().downloadID());
+    ASSERT(!m_timer.isActive());
+    ASSERT(!m_interval);
+    m_timer.startOneShot(throughputIntervals[0].time / speedMultiplier());
+}
+
+uint32_t DownloadMonitor::speedMultiplier() const
+{
+    return m_download.manager().client().downloadMonitorSpeedMultiplier();
+}
+
+void DownloadMonitor::timerFired()
+{
+    RELEASE_ASSERT(m_interval < WTF_ARRAY_LENGTH(throughputIntervals));
+    if (measuredThroughputRate() < throughputIntervals[m_interval].bytesPerSecond) {
+        RELEASE_LOG_IF_ALLOWED("timerFired: cancelling download (id = %" PRIu64 ")", m_download.downloadID().downloadID());
+        m_download.cancel();
+    } else if (m_interval + 1 < WTF_ARRAY_LENGTH(throughputIntervals)) {
+        RELEASE_LOG_IF_ALLOWED("timerFired: sufficient throughput rate (id = %" PRIu64 ")", m_download.downloadID().downloadID());
+        m_timer.startOneShot(timeUntilNextInterval(m_interval++) / speedMultiplier());
+    } else
+        RELEASE_LOG_IF_ALLOWED("timerFired: Download reached threshold to not be terminated (id = %" PRIu64 ")", m_download.downloadID().downloadID());
+}
+
+} // namespace WebKit
diff --git a/Source/WebKit/NetworkProcess/Downloads/DownloadMonitor.h b/Source/WebKit/NetworkProcess/Downloads/DownloadMonitor.h
new file mode 100644 (file)
index 0000000..bb753a3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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 <WebCore/Timer.h>
+#include <wtf/Deque.h>
+
+namespace WebKit {
+
+class Download;
+
+class DownloadMonitor {
+    WTF_MAKE_NONCOPYABLE(DownloadMonitor); WTF_MAKE_FAST_ALLOCATED;
+public:
+    DownloadMonitor(Download&);
+    
+    void applicationDidEnterBackground();
+    void applicationWillEnterForeground();
+    void downloadReceivedBytes(uint64_t);
+    void timerFired();
+
+private:
+    Download& m_download;
+
+    double measuredThroughputRate() const;
+    uint32_t speedMultiplier() const;
+    
+    struct Timestamp {
+        MonotonicTime time;
+        uint64_t bytesReceived;
+    };
+    static constexpr size_t timestampCapacity = 10;
+    Deque<Timestamp, timestampCapacity> m_timestamps;
+    WebCore::Timer m_timer { *this, &DownloadMonitor::timerFired };
+    size_t m_interval { 0 };
+};
+
+} // namespace WebKit
index 1c6b0f9..4f8a37f 100644 (file)
@@ -361,6 +361,8 @@ void NetworkProcess::initializeNetworkProcess(NetworkProcessCreationParameters&&
 
     for (auto& scheme : parameters.urlSchemesRegisteredAsCanDisplayOnlyIfCanRequest)
         registerURLSchemeAsCanDisplayOnlyIfCanRequest(scheme);
+    
+    m_downloadMonitorSpeedMultiplier = parameters.downloadMonitorSpeedMultiplier;
 
     RELEASE_LOG(Process, "%p - NetworkProcess::initializeNetworkProcess: Presenting process = %d", this, WebCore::presentingApplicationPID());
 }
@@ -1955,6 +1957,16 @@ void NetworkProcess::cancelPrepareToSuspend()
         connection->endSuspension();
 }
 
+void NetworkProcess::applicationDidEnterBackground()
+{
+    m_downloadManager.applicationDidEnterBackground();
+}
+
+void NetworkProcess::applicationWillEnterForeground()
+{
+    m_downloadManager.applicationWillEnterForeground();
+}
+
 void NetworkProcess::processDidResume()
 {
     RELEASE_LOG(ProcessSuspension, "%p - NetworkProcess::processDidResume()", this);
index 7957cae..1c6fb6c 100644 (file)
@@ -366,6 +366,7 @@ private:
     IPC::Connection* parentProcessConnectionForDownloads() override { return parentProcessConnection(); }
     AuthenticationManager& downloadsAuthenticationManager() override;
     void pendingDownloadCanceled(DownloadID) override;
+    uint32_t downloadMonitorSpeedMultiplier() const override { return m_downloadMonitorSpeedMultiplier; }
 
     // Message Handlers
     void didReceiveSyncNetworkProcessMessage(IPC::Connection&, IPC::Decoder&, std::unique_ptr<IPC::Encoder>&);
@@ -391,6 +392,8 @@ private:
 #endif
     void continueWillSendRequest(DownloadID, WebCore::ResourceRequest&&);
     void continueDecidePendingDownloadDestination(DownloadID, String destination, SandboxExtension::Handle&&, bool allowOverwrite);
+    void applicationDidEnterBackground();
+    void applicationWillEnterForeground();
 
     void setCacheModel(CacheModel);
     void allowSpecificHTTPSCertificateForHost(const WebCore::CertificateInfo&, const String& host);
@@ -532,6 +535,7 @@ private:
         HashMap<WebCore::ClientOrigin, std::unique_ptr<WebCore::StorageQuotaManager>> managersPerOrigin;
     };
     HashMap<PAL::SessionID, StorageQuotaManagers> m_storageQuotaManagers;
+    uint32_t m_downloadMonitorSpeedMultiplier { 1 };
 };
 
 } // namespace WebKit
index e61524c..30a5532 100644 (file)
@@ -52,6 +52,8 @@ messages -> NetworkProcess LegacyReceiver {
 #if PLATFORM(COCOA)
     PublishDownloadProgress(WebKit::DownloadID downloadID, URL url, WebKit::SandboxExtension::Handle sandboxExtensionHandle)
 #endif
+    ApplicationDidEnterBackground()
+    ApplicationWillEnterForeground()
 
     ContinueWillSendRequest(WebKit::DownloadID downloadID, WebCore::ResourceRequest request)
     ContinueDecidePendingDownloadDestination(WebKit::DownloadID downloadID, String destination, WebKit::SandboxExtension::Handle sandboxExtensionHandle, bool allowOverwrite)
index 087150a..a3dfe60 100644 (file)
@@ -94,6 +94,7 @@ void NetworkProcessCreationParameters::encode(IPC::Encoder& encoder) const
     encoder << serviceWorkerRegistrationDirectory << serviceWorkerRegistrationDirectoryExtensionHandle << urlSchemesServiceWorkersCanHandle << shouldDisableServiceWorkerProcessTerminationDelay;
 #endif
     encoder << shouldEnableITPDatabase;
+    encoder << downloadMonitorSpeedMultiplier;
 }
 
 bool NetworkProcessCreationParameters::decode(IPC::Decoder& decoder, NetworkProcessCreationParameters& result)
@@ -221,6 +222,12 @@ bool NetworkProcessCreationParameters::decode(IPC::Decoder& decoder, NetworkProc
     if (!decoder.decode(result.shouldEnableITPDatabase))
         return false;
 
+    Optional<uint32_t> downloadMonitorSpeedMultiplier;
+    decoder >> downloadMonitorSpeedMultiplier;
+    if (!downloadMonitorSpeedMultiplier)
+        return false;
+    result.downloadMonitorSpeedMultiplier = *downloadMonitorSpeedMultiplier;
+    
     return true;
 }
 
index fc900c2..b428740 100644 (file)
@@ -112,6 +112,7 @@ struct NetworkProcessCreationParameters {
     bool shouldDisableServiceWorkerProcessTerminationDelay { false };
 #endif
     bool shouldEnableITPDatabase { false };
+    uint32_t downloadMonitorSpeedMultiplier { 1 };
 };
 
 } // namespace WebKit
index 5c5da9b..9c2aa37 100644 (file)
@@ -51,7 +51,7 @@ static inline String cachesOriginFilename(const String& cachesRootPath)
     return FileSystem::pathByAppendingComponent(cachesRootPath, "origin"_s);
 }
 
-Caches::Caches(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, StorageQuotaManager& quotaManager)
+Caches::Caches(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, WebCore::StorageQuotaManager& quotaManager)
     : m_engine(&engine)
     , m_origin(WTFMove(origin))
     , m_rootPath(WTFMove(rootPath))
@@ -500,10 +500,10 @@ void Caches::requestSpace(uint64_t spaceRequired, WebCore::DOMCacheEngine::Compl
 
     m_quotaManager->requestSpace(spaceRequired, [callback = WTFMove(callback)](auto decision) {
         switch (decision) {
-        case StorageQuotaManager::Decision::Deny:
+        case WebCore::StorageQuotaManager::Decision::Deny:
             callback(Error::QuotaExceeded);
             return;
-        case StorageQuotaManager::Decision::Grant:
+        case WebCore::StorageQuotaManager::Decision::Grant:
             callback({ });
         };
     });
index a5e909a..6ca46ad 100644 (file)
@@ -37,6 +37,7 @@ namespace WebKit {
 using namespace WebCore::DOMCacheEngine;
 using namespace CacheStorage;
 
+#undef RELEASE_LOG_IF_ALLOWED
 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(sessionID.isAlwaysOnLoggingAllowed(), CacheStorage, "%p - CacheStorageEngineConnection::" fmt, &m_connection.connection(), ##__VA_ARGS__)
 #define RELEASE_LOG_FUNCTION_IF_ALLOWED_IN_CALLBACK(functionName, fmt, resultGetter) \
     if (!result.has_value())\
index e27583e..dde112b 100644 (file)
@@ -53,6 +53,7 @@ NetworkProcess/Cookies/WebCookieManager.cpp
 NetworkProcess/Downloads/Download.cpp
 NetworkProcess/Downloads/DownloadManager.cpp
 NetworkProcess/Downloads/DownloadMap.cpp
+NetworkProcess/Downloads/DownloadMonitor.cpp
 NetworkProcess/Downloads/PendingDownload.cpp
 
 NetworkProcess/FileAPI/NetworkBlobRegistry.cpp
index b560b4a..f96a4f5 100644 (file)
@@ -325,6 +325,7 @@ UIProcess/Cocoa/AutomationClient.mm
 UIProcess/Cocoa/AutomationSessionClient.mm
 UIProcess/Cocoa/DiagnosticLoggingClient.mm
 UIProcess/Cocoa/DownloadClient.mm
+UIProcess/Cocoa/DownloadProxyMapCocoa.mm
 UIProcess/Cocoa/FindClient.mm
 UIProcess/Cocoa/FullscreenClient.mm
 UIProcess/Cocoa/GlobalFindInPageState.mm
index 48c0505..332b0e5 100644 (file)
@@ -115,6 +115,7 @@ Ref<ProcessPoolConfiguration> ProcessPoolConfiguration::copy()
     copy->m_shouldCaptureAudioInUIProcess = this->m_shouldCaptureAudioInUIProcess;
     copy->m_shouldCaptureDisplayInUIProcess = this->m_shouldCaptureDisplayInUIProcess;
     copy->m_isJITEnabled = this->m_isJITEnabled;
+    copy->m_downloadMonitorSpeedMultiplier = this->m_downloadMonitorSpeedMultiplier;
 #if PLATFORM(IOS_FAMILY)
     copy->m_ctDataConnectionServiceType = this->m_ctDataConnectionServiceType;
 #endif
index 4fe2e41..d3b09a8 100644 (file)
@@ -59,6 +59,9 @@ public:
     bool usesSingleWebProcess() const { return m_usesSingleWebProcess; }
     void setUsesSingleWebProcess(bool enabled) { m_usesSingleWebProcess = enabled; }
 
+    uint32_t downloadMonitorSpeedMultiplier() const { return m_downloadMonitorSpeedMultiplier; }
+    void setDownloadMonitorSpeedMultiplier(uint32_t multiplier) { m_downloadMonitorSpeedMultiplier = multiplier; }
+    
     bool isAutomaticProcessWarmingEnabled() const
     {
         return m_isAutomaticProcessWarmingEnabledByClient.valueOr(m_clientWouldBenefitFromAutomaticProcessPrewarming);
@@ -228,6 +231,7 @@ private:
     WTF::String m_customWebContentServiceBundleIdentifier;
     bool m_isJITEnabled { true };
     bool m_usesSingleWebProcess { false };
+    uint32_t m_downloadMonitorSpeedMultiplier { 1 };
 
 #if PLATFORM(IOS_FAMILY)
     WTF::String m_ctDataConnectionServiceType;
index f9e3076..c49ebb8 100644 (file)
@@ -275,12 +275,12 @@ void WKContextSetConnectionClient(WKContextRef contextRef, const WKContextConnec
 
 WKDownloadRef WKContextDownloadURLRequest(WKContextRef contextRef, WKURLRequestRef requestRef)
 {
-    return WebKit::toAPI(WebKit::toImpl(contextRef)->download(0, WebKit::toImpl(requestRef)->resourceRequest()));
+    return WebKit::toAPI(&WebKit::toImpl(contextRef)->download(0, WebKit::toImpl(requestRef)->resourceRequest()));
 }
 
 WKDownloadRef WKContextResumeDownload(WKContextRef contextRef, WKDataRef resumeData, WKStringRef path)
 {
-    return WebKit::toAPI(WebKit::toImpl(contextRef)->resumeDownload(nullptr, WebKit::toImpl(resumeData), WebKit::toWTFString(path)));
+    return WebKit::toAPI(&WebKit::toImpl(contextRef)->resumeDownload(nullptr, WebKit::toImpl(resumeData), WebKit::toWTFString(path)));
 }
 
 void WKContextSetInitializationUserDataForInjectedBundle(WKContextRef contextRef,  WKTypeRef userDataRef)
index bb163b3..bc77bb2 100644 (file)
@@ -563,6 +563,11 @@ static NSDictionary *policiesHashMapToDictionary(const HashMap<String, HashMap<S
     _processPool->setStorageAccessAPIEnabled(enabled);
 }
 
+- (void)_synthesizeAppIsBackground:(BOOL)background
+{
+    _processPool->synthesizeAppIsBackground(background);
+}
+
 - (void)_setAllowsAnySSLCertificateForServiceWorker:(BOOL) allows
 {
 #if ENABLE(SERVICE_WORKER)
@@ -587,7 +592,7 @@ static NSDictionary *policiesHashMapToDictionary(const HashMap<String, HashMap<S
 
 - (_WKDownload *)_downloadURLRequest:(NSURLRequest *)request originatingWebView:(WKWebView *)webView
 {
-    return (_WKDownload *)_processPool->download([webView _page], request)->wrapper();
+    return (_WKDownload *)_processPool->download([webView _page], request).wrapper();
 }
 
 - (_WKDownload *)_resumeDownloadFromData:(NSData *)resumeData path:(NSString *)path originatingWebView:(WKWebView *)webView
index a5d9ed8..734c139 100644 (file)
 
 @property (nonatomic, getter=_isCookieStoragePartitioningEnabled, setter=_setCookieStoragePartitioningEnabled:) BOOL _cookieStoragePartitioningEnabled WK_API_DEPRECATED("Partitioned cookies are no longer supported", macosx(10.12.3, WK_MAC_TBA), ios(10.3, WK_IOS_TBA));
 @property (nonatomic, getter=_isStorageAccessAPIEnabled, setter=_setStorageAccessAPIEnabled:) BOOL _storageAccessAPIEnabled WK_API_AVAILABLE(macosx(10.13.4), ios(11.3));
+- (void)_synthesizeAppIsBackground:(BOOL)background WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @end
index c0bb3af..21b217c 100644 (file)
@@ -69,6 +69,7 @@ WK_CLASS_AVAILABLE(macosx(10.10), ios(8.0))
 @property (nonatomic) BOOL pageCacheEnabled WK_API_AVAILABLE(macosx(10.14), ios(12.0));
 @property (nonatomic) BOOL suppressesConnectionTerminationOnSystemChange WK_API_AVAILABLE(macosx(10.14), ios(12.0));
 @property (nonatomic, getter=isJITEnabled) BOOL JITEnabled WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+@property (nonatomic) NSUInteger downloadMonitorSpeedMultiplierForTesting WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @end
 
index 3ccb5b8..96b0a98 100644 (file)
     _processPoolConfiguration->setJITEnabled(enabled);
 }
 
+- (NSUInteger)downloadMonitorSpeedMultiplierForTesting
+{
+    return _processPoolConfiguration->downloadMonitorSpeedMultiplier();
+}
+
+- (void)setDownloadMonitorSpeedMultiplierForTesting:(NSUInteger)multiplier
+{
+    _processPoolConfiguration->setDownloadMonitorSpeedMultiplier(multiplier);
+}
+
 - (void)setSuppressesConnectionTerminationOnSystemChange:(BOOL)suppressesConnectionTerminationOnSystemChange
 {
     _processPoolConfiguration->setSuppressesConnectionTerminationOnSystemChange(suppressesConnectionTerminationOnSystemChange);
index 1ecbf38..ff3ff86 100644 (file)
@@ -1639,7 +1639,7 @@ WebKitDownload* webkitWebContextGetOrCreateDownload(DownloadProxy* downloadProxy
 WebKitDownload* webkitWebContextStartDownload(WebKitWebContext* context, const char* uri, WebPageProxy* initiatingPage)
 {
     WebCore::ResourceRequest request(String::fromUTF8(uri));
-    return webkitWebContextGetOrCreateDownload(context->priv->processPool->download(initiatingPage, request));
+    return webkitWebContextGetOrCreateDownload(&context->priv->processPool->download(initiatingPage, request));
 }
 
 void webkitWebContextRemoveDownload(DownloadProxy* downloadProxy)
index 83e7a4d..2a9fd7a 100644 (file)
@@ -299,10 +299,10 @@ void PageClientImpl::startDrag(Ref<SelectionData>&& selection, DragOperation dra
 }
 #endif
 
-void PageClientImpl::handleDownloadRequest(DownloadProxy* download)
+void PageClientImpl::handleDownloadRequest(DownloadProxy& download)
 {
     if (WEBKIT_IS_WEB_VIEW(m_viewWidget))
-        webkitWebViewHandleDownloadRequest(WEBKIT_WEB_VIEW(m_viewWidget), download);
+        webkitWebViewHandleDownloadRequest(WEBKIT_WEB_VIEW(m_viewWidget), &download);
 }
 
 void PageClientImpl::didCommitLoadForMainFrame(const String& /* mimeType */, bool /* useCustomContentProvider */ )
index d20dfb7..5ff9b6e 100644 (file)
@@ -103,7 +103,7 @@ private:
     void exitAcceleratedCompositingMode() override;
     void updateAcceleratedCompositingMode(const LayerTreeContext&) override;
 
-    void handleDownloadRequest(DownloadProxy*) override;
+    void handleDownloadRequest(DownloadProxy&) override;
     void didChangeContentSize(const WebCore::IntSize&) override;
     void didCommitLoadForMainFrame(const String& mimeType, bool useCustomContentProvider) override;
     void didFailLoadForMainFrame() override;
index 9b86d70..fcd81e4 100644 (file)
@@ -124,10 +124,9 @@ void PageClientImpl::didCommitLoadForMainFrame(const String&, bool)
 {
 }
 
-void PageClientImpl::handleDownloadRequest(DownloadProxy* download)
+void PageClientImpl::handleDownloadRequest(DownloadProxy& download)
 {
-    ASSERT(download);
-    m_view.handleDownloadRequest(*download);
+    m_view.handleDownloadRequest(download);
 }
 
 void PageClientImpl::didChangeContentSize(const WebCore::IntSize&)
index 3d8d88c..f084a66 100644 (file)
@@ -74,7 +74,7 @@ private:
     void toolTipChanged(const String&, const String&) override;
 
     void didCommitLoadForMainFrame(const String&, bool) override;
-    void handleDownloadRequest(DownloadProxy*) override;
+    void handleDownloadRequest(DownloadProxy&) override;
 
     void didChangeContentSize(const WebCore::IntSize&) override;
 
diff --git a/Source/WebKit/UIProcess/Cocoa/DownloadProxyMapCocoa.mm b/Source/WebKit/UIProcess/Cocoa/DownloadProxyMapCocoa.mm
new file mode 100644 (file)
index 0000000..ac91cd6
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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 "DownloadProxyMap.h"
+
+#if PLATFORM(IOS_FAMILY)
+#import <UIKit/UIKit.h>
+#endif
+
+namespace WebKit {
+
+void DownloadProxyMap::platformCreate()
+{
+#if PLATFORM(IOS_FAMILY)
+    m_backgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:makeBlockPtr([weakThis = makeWeakPtr(*this)](NSNotification *) {
+        if (!weakThis)
+            return;
+        weakThis->applicationDidEnterBackground();
+    }).get()];
+    m_foregroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:makeBlockPtr([weakThis = makeWeakPtr(*this)](NSNotification *) {
+        if (!weakThis)
+            return;
+        weakThis->applicationWillEnterForeground();
+    }).get()];
+#endif
+}
+
+void DownloadProxyMap::platformDestroy()
+{
+#if PLATFORM(IOS_FAMILY)
+    [[NSNotificationCenter defaultCenter] removeObserver:m_backgroundObserver.get()];
+    [[NSNotificationCenter defaultCenter] removeObserver:m_foregroundObserver.get()];
+#endif
+}
+
+} // namespace WebKit
index e959f65..3f8b459 100644 (file)
 #import <sys/param.h>
 #import <wtf/FileSystem.h>
 #import <wtf/ProcessPrivilege.h>
+#import <wtf/SoftLinking.h>
 #import <wtf/cocoa/Entitlements.h>
 #import <wtf/spi/darwin/dyldSPI.h>
 
 #if PLATFORM(MAC)
 #import <QuartzCore/CARemoteLayerServer.h>
+#else
+#import "UIKitSPI.h"
 #endif
 
 #if PLATFORM(IOS)
@@ -309,6 +312,7 @@ void WebProcessPool::platformInitializeNetworkProcess(NetworkProcessCreationPara
 #endif
 
     parameters.shouldEnableITPDatabase = [defaults boolForKey:[NSString stringWithFormat:@"InternalDebug%@", WebPreferencesKey::isITPDatabaseEnabledKey().createCFString().get()]];
+    parameters.downloadMonitorSpeedMultiplier = m_configuration->downloadMonitorSpeedMultiplier();
 }
 
 void WebProcessPool::platformInvalidateContext()
index e09254b..c68ade9 100644 (file)
@@ -209,7 +209,7 @@ void DownloadProxy::didFinish()
     m_processPool->downloadClient().didFinish(*m_processPool, *this);
 
     // This can cause the DownloadProxy object to be deleted.
-    m_downloadProxyMap.downloadFinished(this);
+    m_downloadProxyMap.downloadFinished(*this);
 }
 
 static RefPtr<API::Data> createData(const IPC::DataReference& data)
@@ -230,7 +230,7 @@ void DownloadProxy::didFail(const ResourceError& error, const IPC::DataReference
     m_processPool->downloadClient().didFail(*m_processPool, *this, error);
 
     // This can cause the DownloadProxy object to be deleted.
-    m_downloadProxyMap.downloadFinished(this);
+    m_downloadProxyMap.downloadFinished(*this);
 }
 
 void DownloadProxy::didCancel(const IPC::DataReference& resumeData)
@@ -240,7 +240,7 @@ void DownloadProxy::didCancel(const IPC::DataReference& resumeData)
     m_processPool->downloadClient().didCancel(*m_processPool, *this);
 
     // This can cause the DownloadProxy object to be deleted.
-    m_downloadProxyMap.downloadFinished(this);
+    m_downloadProxyMap.downloadFinished(*this);
 }
 
 } // namespace WebKit
index 5f37c2e..1a5b1ab 100644 (file)
 namespace WebKit {
 
 DownloadProxyMap::DownloadProxyMap(NetworkProcessProxy& process)
-    : m_process(&process)
+    : m_process(makeWeakPtr(process))
 #if PLATFORM(COCOA)
     , m_shouldTakeAssertion(WTF::processHasEntitlement("com.apple.multitasking.systemappassertions"))
 #endif
 {
+    platformCreate();
 }
 
 DownloadProxyMap::~DownloadProxyMap()
 {
     ASSERT(m_downloads.isEmpty());
+    platformDestroy();
 }
 
-DownloadProxy* DownloadProxyMap::createDownloadProxy(WebProcessPool& processPool, const WebCore::ResourceRequest& resourceRequest)
+#if !PLATFORM(COCOA)
+void DownloadProxyMap::platformCreate()
+{
+}
+
+void DownloadProxyMap::platformDestroy()
+{
+}
+#endif
+
+void DownloadProxyMap::applicationDidEnterBackground()
+{
+    if (m_process)
+        m_process->send(Messages::NetworkProcess::ApplicationDidEnterBackground(), 0);
+}
+
+void DownloadProxyMap::applicationWillEnterForeground()
+{
+    if (m_process)
+        m_process->send(Messages::NetworkProcess::ApplicationWillEnterForeground(), 0);
+}
+
+DownloadProxy& DownloadProxyMap::createDownloadProxy(WebProcessPool& processPool, const WebCore::ResourceRequest& resourceRequest)
 {
     auto downloadProxy = DownloadProxy::create(*this, processPool, resourceRequest);
     m_downloads.set(downloadProxy->downloadID(), downloadProxy.copyRef());
@@ -64,12 +88,12 @@ DownloadProxy* DownloadProxyMap::createDownloadProxy(WebProcessPool& processPool
 
     m_process->addMessageReceiver(Messages::DownloadProxy::messageReceiverName(), downloadProxy->downloadID().downloadID(), downloadProxy.get());
 
-    return downloadProxy.ptr();
+    return downloadProxy;
 }
 
-void DownloadProxyMap::downloadFinished(DownloadProxy* downloadProxy)
+void DownloadProxyMap::downloadFinished(DownloadProxy& downloadProxy)
 {
-    auto downloadID = downloadProxy->downloadID();
+    auto downloadID = downloadProxy.downloadID();
 
     // The DownloadProxy may be holding the last reference to the process pool.
     auto protectedProcessPool = makeRefPtr(m_process->processPool());
@@ -77,7 +101,7 @@ void DownloadProxyMap::downloadFinished(DownloadProxy* downloadProxy)
     ASSERT(m_downloads.contains(downloadID));
 
     m_process->removeMessageReceiver(Messages::DownloadProxy::messageReceiverName(), downloadID.downloadID());
-    downloadProxy->invalidate();
+    downloadProxy.invalidate();
     m_downloads.remove(downloadID);
 
     if (m_downloads.isEmpty() && m_shouldTakeAssertion) {
index 03c8cf0..7e30929 100644 (file)
 #include "DownloadID.h"
 #include <wtf/HashMap.h>
 #include <wtf/Noncopyable.h>
+#include <wtf/RetainPtr.h>
+#include <wtf/WeakPtr.h>
+
+#if PLATFORM(IOS_FAMILY)
+#include <objc/objc.h>
+#endif
 
 namespace WebCore {
 class ResourceRequest;
@@ -40,7 +46,7 @@ class NetworkProcessProxy;
 class ProcessAssertion;
 class WebProcessPool;
 
-class DownloadProxyMap {
+class DownloadProxyMap : public CanMakeWeakPtr<DownloadProxyMap> {
     WTF_MAKE_FAST_ALLOCATED;
     WTF_MAKE_NONCOPYABLE(DownloadProxyMap);
 
@@ -48,19 +54,29 @@ public:
     explicit DownloadProxyMap(NetworkProcessProxy&);
     ~DownloadProxyMap();
 
-    DownloadProxy* createDownloadProxy(WebProcessPool&, const WebCore::ResourceRequest&);
-    void downloadFinished(DownloadProxy*);
+    DownloadProxy& createDownloadProxy(WebProcessPool&, const WebCore::ResourceRequest&);
+    void downloadFinished(DownloadProxy&);
 
     bool isEmpty() const { return m_downloads.isEmpty(); }
 
     void processDidClose();
 
+    void applicationDidEnterBackground();
+    void applicationWillEnterForeground();
+
 private:
-    NetworkProcessProxy* m_process;
+    void platformCreate();
+    void platformDestroy();
+
+    WeakPtr<NetworkProcessProxy> m_process;
     HashMap<DownloadID, RefPtr<DownloadProxy>> m_downloads;
 
     bool m_shouldTakeAssertion { false };
     std::unique_ptr<ProcessAssertion> m_downloadAssertion;
+#if PLATFORM(IOS_FAMILY)
+    RetainPtr<id> m_backgroundObserver;
+    RetainPtr<id> m_foregroundObserver;
+#endif
 };
 
 } // namespace WebKit
index b47ae63..a39801b 100644 (file)
@@ -147,7 +147,17 @@ void NetworkProcessProxy::getNetworkProcessConnection(WebProcessProxy& webProces
     connection()->send(Messages::NetworkProcess::CreateNetworkConnectionToWebProcess(isServiceWorkerProcess, registrableDomain), 0, IPC::SendOption::DispatchMessageEvenWhenWaitingForSyncReply);
 }
 
-DownloadProxy* NetworkProcessProxy::createDownloadProxy(const ResourceRequest& resourceRequest)
+void NetworkProcessProxy::synthesizeAppIsBackground(bool background)
+{
+    if (m_downloadProxyMap) {
+        if (background)
+            m_downloadProxyMap->applicationDidEnterBackground();
+        else
+            m_downloadProxyMap->applicationWillEnterForeground();
+    }
+}
+
+DownloadProxy& NetworkProcessProxy::createDownloadProxy(const ResourceRequest& resourceRequest)
 {
     if (!m_downloadProxyMap)
         m_downloadProxyMap = std::make_unique<DownloadProxyMap>(*this);
index 46e4f65..1662bbe 100644 (file)
@@ -66,7 +66,7 @@ struct NetworkProcessCreationParameters;
 class WebUserContentControllerProxy;
 struct WebsiteData;
 
-class NetworkProcessProxy final : public AuxiliaryProcessProxy, private ProcessThrottlerClient {
+class NetworkProcessProxy final : public AuxiliaryProcessProxy, private ProcessThrottlerClient, public CanMakeWeakPtr<NetworkProcessProxy> {
 public:
     using RegistrableDomain = WebCore::RegistrableDomain;
     using TopFrameDomain = WebCore::RegistrableDomain;
@@ -88,7 +88,7 @@ public:
 
     void getNetworkProcessConnection(WebProcessProxy&, Messages::WebProcessProxy::GetNetworkProcessConnection::DelayedReply&&);
 
-    DownloadProxy* createDownloadProxy(const WebCore::ResourceRequest&);
+    DownloadProxy& createDownloadProxy(const WebCore::ResourceRequest&);
 
     void fetchWebsiteData(PAL::SessionID, OptionSet<WebsiteDataType>, OptionSet<WebsiteDataFetchOption>, CompletionHandler<void(WebsiteData)>&&);
     void deleteWebsiteData(PAL::SessionID, OptionSet<WebsiteDataType>, WallTime modifiedSince, CompletionHandler<void()>&& completionHandler);
@@ -154,6 +154,7 @@ public:
     
     void sendProcessDidTransitionToForeground();
     void sendProcessDidTransitionToBackground();
+    void synthesizeAppIsBackground(bool background);
 
     void setIsHoldingLockedFiles(bool);
     void setIsIDBDatabaseHoldingLockedFiles(bool);
index 01f0d16..e1ddd36 100644 (file)
@@ -215,7 +215,7 @@ public:
     virtual void didFailProvisionalLoadForMainFrame() { };
     virtual void didCommitLoadForMainFrame(const String& mimeType, bool useCustomContentProvider) = 0;
 
-    virtual void handleDownloadRequest(DownloadProxy*) = 0;
+    virtual void handleDownloadRequest(DownloadProxy&) = 0;
 
     virtual bool handleRunOpenPanel(WebPageProxy*, WebFrameProxy*, API::OpenPanelParameters*, WebOpenPanelResultListenerProxy*) { return false; }
     virtual bool showShareSheet(const WebCore::ShareDataWithParsedURL&, WTF::CompletionHandler<void (bool)>&&) { return false; }
index f93f9f5..4bb0141 100644 (file)
@@ -2835,13 +2835,13 @@ void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation*
     DownloadID downloadID = { };
     if (action == PolicyAction::Download) {
         // Create a download proxy.
-        auto* download = m_process->processPool().createDownloadProxy(m_decidePolicyForResponseRequest, this);
+        auto& download = m_process->processPool().createDownloadProxy(m_decidePolicyForResponseRequest, this);
         if (navigation) {
-            download->setWasUserInitiated(navigation->wasUserInitiated());
-            download->setRedirectChain(navigation->takeRedirectChain());
+            download.setWasUserInitiated(navigation->wasUserInitiated());
+            download.setRedirectChain(navigation->takeRedirectChain());
         }
 
-        downloadID = download->downloadID();
+        downloadID = download.downloadID();
         handleDownloadRequest(download);
         m_decidePolicyForResponseRequest = { };
     }
@@ -5452,7 +5452,7 @@ void WebPageProxy::setMayStartMediaWhenInWindow(bool mayStartMedia)
     process().send(Messages::WebPage::SetMayStartMediaWhenInWindow(mayStartMedia), m_pageID);
 }
 
-void WebPageProxy::handleDownloadRequest(DownloadProxy* download)
+void WebPageProxy::handleDownloadRequest(DownloadProxy& download)
 {
     pageClient().handleDownloadRequest(download);
 }
index 685f404..4c6cebf 100644 (file)
@@ -1102,7 +1102,7 @@ public:
 
     WebPageCreationParameters creationParameters(WebProcessProxy&, DrawingAreaProxy&);
 
-    void handleDownloadRequest(DownloadProxy*);
+    void handleDownloadRequest(DownloadProxy&);
 
     void advanceToNextMisspelling(bool startBeforeSelection);
     void changeSpellingToWord(const String& word);
index 7b9f4c6..a4c3466 100644 (file)
@@ -1332,9 +1332,9 @@ bool WebProcessPool::hasPagesUsingWebsiteDataStore(WebsiteDataStore& dataStore)
     return m_sessionToPageIDsMap.contains(dataStore.sessionID());
 }
 
-DownloadProxy* WebProcessPool::download(WebPageProxy* initiatingPage, const ResourceRequest& request, const String& suggestedFilename)
+DownloadProxy& WebProcessPool::download(WebPageProxy* initiatingPage, const ResourceRequest& request, const String& suggestedFilename)
 {
-    auto* downloadProxy = createDownloadProxy(request, initiatingPage);
+    auto& downloadProxy = createDownloadProxy(request, initiatingPage);
     PAL::SessionID sessionID = initiatingPage ? initiatingPage->sessionID() : PAL::SessionID::defaultSessionID();
 
     if (initiatingPage)
@@ -1357,16 +1357,16 @@ DownloadProxy* WebProcessPool::download(WebPageProxy* initiatingPage, const Reso
                 updatedRequest.setHTTPUserAgent(WebPageProxy::standardUserAgent());
         }
         updatedRequest.setIsTopSite(false);
-        networkProcess()->send(Messages::NetworkProcess::DownloadRequest(sessionID, downloadProxy->downloadID(), updatedRequest, suggestedFilename), 0);
+        networkProcess()->send(Messages::NetworkProcess::DownloadRequest(sessionID, downloadProxy.downloadID(), updatedRequest, suggestedFilename), 0);
         return downloadProxy;
     }
 
     return downloadProxy;
 }
 
-DownloadProxy* WebProcessPool::resumeDownload(WebPageProxy* initiatingPage, const API::Data* resumeData, const String& path)
+DownloadProxy& WebProcessPool::resumeDownload(WebPageProxy* initiatingPage, const API::Data* resumeData, const String& path)
 {
-    auto* downloadProxy = createDownloadProxy(ResourceRequest(), initiatingPage);
+    auto& downloadProxy = createDownloadProxy(ResourceRequest(), initiatingPage);
     PAL::SessionID sessionID = initiatingPage ? initiatingPage->sessionID() : PAL::SessionID::defaultSessionID();
 
     SandboxExtension::Handle sandboxExtensionHandle;
@@ -1374,7 +1374,7 @@ DownloadProxy* WebProcessPool::resumeDownload(WebPageProxy* initiatingPage, cons
         SandboxExtension::createHandle(path, SandboxExtension::Type::ReadWrite, sandboxExtensionHandle);
 
     if (networkProcess()) {
-        networkProcess()->send(Messages::NetworkProcess::ResumeDownload(sessionID, downloadProxy->downloadID(), resumeData->dataReference(), path, sandboxExtensionHandle), 0);
+        networkProcess()->send(Messages::NetworkProcess::ResumeDownload(sessionID, downloadProxy.downloadID(), resumeData->dataReference(), path, sandboxExtensionHandle), 0);
         return downloadProxy;
     }
 
@@ -1625,13 +1625,18 @@ void WebProcessPool::setDefaultRequestTimeoutInterval(double timeoutInterval)
     sendToAllProcesses(Messages::WebProcess::SetDefaultRequestTimeoutInterval(timeoutInterval));
 }
 
-DownloadProxy* WebProcessPool::createDownloadProxy(const ResourceRequest& request, WebPageProxy* originatingPage)
+DownloadProxy& WebProcessPool::createDownloadProxy(const ResourceRequest& request, WebPageProxy* originatingPage)
 {
-    auto downloadProxy = ensureNetworkProcess().createDownloadProxy(request);
-    downloadProxy->setOriginatingPage(originatingPage);
+    auto& downloadProxy = ensureNetworkProcess().createDownloadProxy(request);
+    downloadProxy.setOriginatingPage(originatingPage);
     return downloadProxy;
 }
 
+void WebProcessPool::synthesizeAppIsBackground(bool background)
+{
+    ensureNetworkProcess().synthesizeAppIsBackground(background);
+}
+
 void WebProcessPool::addMessageReceiver(IPC::StringReference messageReceiverName, IPC::MessageReceiver& messageReceiver)
 {
     m_messageReceiverMap.addMessageReceiver(messageReceiverName, messageReceiver);
index daf5b15..9774115 100644 (file)
@@ -198,8 +198,8 @@ public:
 
     const String& injectedBundlePath() const { return m_configuration->injectedBundlePath(); }
 
-    DownloadProxy* download(WebPageProxy* initiatingPage, const WebCore::ResourceRequest&, const String& suggestedFilename = { });
-    DownloadProxy* resumeDownload(WebPageProxy* initiatingPage, const API::Data* resumeData, const String& path);
+    DownloadProxy& download(WebPageProxy* initiatingPage, const WebCore::ResourceRequest&, const String& suggestedFilename = { });
+    DownloadProxy& resumeDownload(WebPageProxy* initiatingPage, const API::Data* resumeData, const String& path);
 
     void setInjectedBundleInitializationUserData(RefPtr<API::Object>&& userData) { m_injectedBundleInitializationUserData = WTFMove(userData); }
 
@@ -271,7 +271,7 @@ public:
     void setEnhancedAccessibility(bool);
     
     // Downloads.
-    DownloadProxy* createDownloadProxy(const WebCore::ResourceRequest&, WebPageProxy* originatingPage);
+    DownloadProxy& createDownloadProxy(const WebCore::ResourceRequest&, WebPageProxy* originatingPage);
     API::DownloadClient& downloadClient() { return *m_downloadClient; }
 
     API::LegacyContextHistoryClient& historyClient() { return *m_historyClient; }
@@ -434,6 +434,8 @@ public:
     bool alwaysRunsAtBackgroundPriority() const { return m_alwaysRunsAtBackgroundPriority; }
     bool shouldTakeUIBackgroundAssertion() const { return m_shouldTakeUIBackgroundAssertion; }
 
+    void synthesizeAppIsBackground(bool background);
+    
 #if ENABLE(GAMEPAD)
     void gamepadConnected(const UIGamepad&);
     void gamepadDisconnected(const UIGamepad&);
index 6025a22..b4786fc 100644 (file)
@@ -75,7 +75,7 @@ private:
     void didStartProvisionalLoadForMainFrame() override;
     void didFailProvisionalLoadForMainFrame() override;
     void didCommitLoadForMainFrame(const String& mimeType, bool useCustomContentProvider) override;
-    void handleDownloadRequest(DownloadProxy*) override;
+    void handleDownloadRequest(DownloadProxy&) override;
     void didChangeContentSize(const WebCore::IntSize&) override;
     void setCursor(const WebCore::Cursor&) override;
     void setCursorHiddenUntilMouseMoves(bool) override;
index db3f675..e656011 100644 (file)
@@ -224,7 +224,7 @@ void PageClientImpl::didCommitLoadForMainFrame(const String& mimeType, bool useC
     [m_contentView _didCommitLoadForMainFrame];
 }
 
-void PageClientImpl::handleDownloadRequest(DownloadProxy*)
+void PageClientImpl::handleDownloadRequest(DownloadProxy&)
 {
 }
 
index d6a1466..6a5c8fd 100644 (file)
@@ -88,7 +88,7 @@ private:
     void toolTipChanged(const String& oldToolTip, const String& newToolTip) override;
     void didCommitLoadForMainFrame(const String& mimeType, bool useCustomContentProvider) override;
     void didFinishLoadingDataForCustomContentProvider(const String& suggestedFilename, const IPC::DataReference&) override;
-    void handleDownloadRequest(DownloadProxy*) override;
+    void handleDownloadRequest(DownloadProxy&) override;
     void didChangeContentSize(const WebCore::IntSize&) override;
     void setCursor(const WebCore::Cursor&) override;
     void setCursorHiddenUntilMouseMoves(bool) override;
index 1a95ac3..8ab3456 100644 (file)
@@ -295,7 +295,7 @@ void PageClientImpl::didFinishLoadingDataForCustomContentProvider(const String&
 {
 }
 
-void PageClientImpl::handleDownloadRequest(DownloadProxy*)
+void PageClientImpl::handleDownloadRequest(DownloadProxy&)
 {
 }
 
index 066dd5b..b522b94 100644 (file)
@@ -229,7 +229,7 @@ void PageClientImpl::didChangeContentSize(const IntSize& size)
     notImplemented();
 }
 
-void PageClientImpl::handleDownloadRequest(DownloadProxy* download)
+void PageClientImpl::handleDownloadRequest(DownloadProxy& download)
 {
     notImplemented();
 }
index 1f9da84..5242af7 100644 (file)
@@ -96,7 +96,7 @@ private:
     void exitAcceleratedCompositingMode() override;
     void updateAcceleratedCompositingMode(const LayerTreeContext&) override;
 
-    void handleDownloadRequest(DownloadProxy*) override;
+    void handleDownloadRequest(DownloadProxy&) override;
     void didChangeContentSize(const WebCore::IntSize&) override;
     void didCommitLoadForMainFrame(const String& mimeType, bool useCustomContentProvider) override;
     void didFailLoadForMainFrame() override { }
index 7e3967f..9eb5a64 100644 (file)
                5C20CB9E1BB0DD1800895BB1 /* NetworkSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkSession.h; sourceTree = "<group>"; };
                5C26958420042F12005C439B /* WKOpenPanelParametersPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKOpenPanelParametersPrivate.h; sourceTree = "<group>"; };
                5C298D9E1C3DEF2900470AFE /* PendingDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PendingDownload.h; sourceTree = "<group>"; };
+               5C2B1AF3223AB72800B91CF7 /* DownloadMonitor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DownloadMonitor.cpp; sourceTree = "<group>"; };
+               5C2B1AF4223AB72800B91CF7 /* DownloadMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DownloadMonitor.h; sourceTree = "<group>"; };
+               5C2B1AF5223C1FD200B91CF7 /* DownloadProxyMapCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DownloadProxyMapCocoa.mm; sourceTree = "<group>"; };
                5C359C0C21547321009E7948 /* WKDeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDeprecated.h; sourceTree = "<group>"; };
                5C3AEA8E1FE1F1DF002318D3 /* WebsitePoliciesData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebsitePoliciesData.cpp; sourceTree = "<group>"; };
                5C46C0AC21B7198B00BC5991 /* WebsiteDataStoreConfiguration.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebsiteDataStoreConfiguration.cpp; sourceTree = "<group>"; };
                                83891B6B1A68C30B0030F386 /* DiagnosticLoggingClient.mm */,
                                A1DF631118E0B7C8003A3E2A /* DownloadClient.h */,
                                A1DF631018E0B7C8003A3E2A /* DownloadClient.mm */,
+                               5C2B1AF5223C1FD200B91CF7 /* DownloadProxyMapCocoa.mm */,
                                00B9661818E25AE100CE1F88 /* FindClient.h */,
                                00B9661718E25AE100CE1F88 /* FindClient.mm */,
                                CD78E1131DB7D7ED0014A2DE /* FullscreenClient.h */,
                                5C1426F91C23F84300D41183 /* DownloadManager.h */,
                                51240EB9220A08CA005CFC63 /* DownloadMap.cpp */,
                                51240EB8220A08CA005CFC63 /* DownloadMap.h */,
+                               5C2B1AF3223AB72800B91CF7 /* DownloadMonitor.cpp */,
+                               5C2B1AF4223AB72800B91CF7 /* DownloadMonitor.h */,
                                5C85C7861C3F23C50061A4FA /* PendingDownload.cpp */,
                                5C298D9E1C3DEF2900470AFE /* PendingDownload.h */,
                        );
                                                CreatedOnToolsVersion = 10.1;
                                                ProvisioningStyle = Automatic;
                                        };
-                                       A1798B59223464C4000764BD = {
-                                               CreatedOnToolsVersion = 10.2;
-                                       };
                                        E1AC2E2720F7B94C00B0897D = {
                                                CreatedOnToolsVersion = 9.3;
                                                ProvisioningStyle = Automatic;
index 84e4404..1f1f5e3 100644 (file)
@@ -1,3 +1,28 @@
+2019-03-18  Alex Christensen  <achristensen@webkit.org>
+
+        Implement DownloadMonitor to prevent long-running slow downloads from background apps
+        https://bugs.webkit.org/show_bug.cgi?id=195785
+
+        Reviewed by Geoffrey Garen.
+
+        * TestWebKitAPI/TCPServer.cpp: Added.
+        (TestWebKitAPI::TCPServer::TCPServer):
+        (TestWebKitAPI::TCPServer::~TCPServer):
+        (TestWebKitAPI::TCPServer::socketBindListen):
+        (TestWebKitAPI::TCPServer::waitForAndReplyToRequests):
+        * TestWebKitAPI/TCPServer.h: Added.
+        (TestWebKitAPI::TCPServer::port const):
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/Download.mm:
+        (-[DownloadMonitorTestDelegate _downloadDidStart:]):
+        (-[DownloadMonitorTestDelegate _downloadDidCancel:]):
+        (-[DownloadMonitorTestDelegate _download:decideDestinationWithSuggestedFilename:completionHandler:]):
+        (-[DownloadMonitorTestDelegate _download:didReceiveData:]):
+        (TestWebKitAPI::respondSlowly):
+        (TestWebKitAPI::webViewWithDownloadMonitorSpeedMultiplier):
+        (TestWebKitAPI::downloadAtRate):
+        (TestWebKitAPI::TEST):
+
 2019-03-18  Saam Barati  <sbarati@apple.com>
 
         Add a plan file for JetStream 2
diff --git a/Tools/TestWebKitAPI/TCPServer.cpp b/Tools/TestWebKitAPI/TCPServer.cpp
new file mode 100644 (file)
index 0000000..f6dedee
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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 "TCPServer.h"
+
+#include <netinet/in.h>
+#include <thread>
+#include <unistd.h>
+#include <vector>
+
+namespace TestWebKitAPI {
+
+TCPServer::TCPServer(Function<void(Socket)>&& socketHandler)
+    : m_socketHandler(WTFMove(socketHandler))
+{
+    socketBindListen();
+    m_thread = std::thread(&TCPServer::waitForAndReplyToRequests, this);
+}
+
+TCPServer::~TCPServer()
+{
+    m_thread.join();
+    if (m_listeningSocket != InvalidSocket) {
+        close(m_listeningSocket);
+        m_listeningSocket = InvalidSocket;
+    }
+    if (m_connectionSocket != InvalidSocket) {
+        close(m_connectionSocket);
+        m_connectionSocket = InvalidSocket;
+    }
+}
+
+void TCPServer::socketBindListen()
+{
+    m_listeningSocket = socket(PF_INET, SOCK_STREAM, 0);
+    if (m_listeningSocket == InvalidSocket)
+        return;
+    
+    // Ports 49152-65535 are unallocated ports. Try until we find one that's free.
+    for (Port port = 49152; port; port++) {
+        struct sockaddr_in name;
+        memset(&name, 0, sizeof(name));
+        name.sin_family = AF_INET;
+        name.sin_port = htons(port);
+        name.sin_addr.s_addr = htonl(INADDR_ANY);
+        if (bind(m_listeningSocket, reinterpret_cast<sockaddr*>(&name), sizeof(name)) < 0) {
+            // This port is busy. Try the next port.
+            continue;
+        }
+        const unsigned maxConnections = 1;
+        if (listen(m_listeningSocket, maxConnections) == -1) {
+            // Listening failed.
+            close(m_listeningSocket);
+            m_listeningSocket = InvalidSocket;
+            return;
+        }
+        m_port = port;
+        return; // Successfully set up listening port.
+    }
+    
+    // Couldn't find an available port.
+    close(m_listeningSocket);
+    m_listeningSocket = InvalidSocket;
+}
+
+void TCPServer::waitForAndReplyToRequests()
+{
+    if (m_listeningSocket == InvalidSocket)
+        return;
+    
+    m_connectionSocket = accept(m_listeningSocket, nullptr, nullptr);
+    m_socketHandler(m_connectionSocket);
+    shutdown(m_connectionSocket, SHUT_RDWR);
+}
+
+} // namespace TestWebKitAPI
diff --git a/Tools/TestWebKitAPI/TCPServer.h b/Tools/TestWebKitAPI/TCPServer.h
new file mode 100644 (file)
index 0000000..73abc10
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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 <thread>
+#include <wtf/Function.h>
+
+namespace TestWebKitAPI {
+
+class TCPServer {
+public:
+    using Socket = int;
+    static constexpr Socket InvalidSocket = -1;
+    using Port = uint16_t;
+    static constexpr Port InvalidPort = 0;
+    
+    TCPServer(Function<void(Socket)>&&);
+    ~TCPServer();
+    
+    Port port() const { return m_port; }
+    
+private:
+    void socketBindListen();
+    void waitForAndReplyToRequests();
+
+    Port m_port { InvalidPort };
+    Socket m_listeningSocket { InvalidSocket };
+    Socket m_connectionSocket { InvalidSocket };
+    std::thread m_thread;
+    Function<void(Socket)> m_socketHandler;
+};
+
+} // namespace TestWebKitAPI
index 33e056a..fd20522 100644 (file)
                5C838F7F1DB04F900082858F /* LoadInvalidURLRequest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 57901FAE1CAF137100ED64F9 /* LoadInvalidURLRequest.mm */; };
                5C8BC799218CF44700813886 /* NetworkProcess.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5C8BC798218CF3E900813886 /* NetworkProcess.mm */; };
                5C973F5C1F58EF8B00359C27 /* WebGLPolicy.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5C973F5B1F58EF0A00359C27 /* WebGLPolicy.mm */; };
+               5C9B548E223C4CBE00B150C4 /* TCPServer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5C9B548C223C4BF500B150C4 /* TCPServer.cpp */; };
                5C9E56851DF9145400C9EE33 /* WebsitePolicies.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5C9E56841DF9143D00C9EE33 /* WebsitePolicies.mm */; };
                5C9E56871DF914AE00C9EE33 /* contentBlockerCheck.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5C9E56861DF9148E00C9EE33 /* contentBlockerCheck.html */; };
                5C9E59411D3EB5AC00E3C62E /* ApplicationCache.db in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5C9E593E1D3EB1DE00E3C62E /* ApplicationCache.db */; };
                5C7C74CA1FB528D4002F9ABE /* WebViewScheduleInRunLoop.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebViewScheduleInRunLoop.mm; sourceTree = "<group>"; };
                5C8BC798218CF3E900813886 /* NetworkProcess.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NetworkProcess.mm; sourceTree = "<group>"; };
                5C973F5B1F58EF0A00359C27 /* WebGLPolicy.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebGLPolicy.mm; sourceTree = "<group>"; };
+               5C9B548C223C4BF500B150C4 /* TCPServer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TCPServer.cpp; sourceTree = "<group>"; };
+               5C9B548D223C4BF500B150C4 /* TCPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPServer.h; sourceTree = "<group>"; };
                5C9E56841DF9143D00C9EE33 /* WebsitePolicies.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebsitePolicies.mm; sourceTree = "<group>"; };
                5C9E56861DF9148E00C9EE33 /* contentBlockerCheck.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = contentBlockerCheck.html; sourceTree = "<group>"; };
                5C9E593E1D3EB1DE00E3C62E /* ApplicationCache.db */ = {isa = PBXFileReference; lastKnownFileType = file; path = ApplicationCache.db; sourceTree = "<group>"; };
                                BC575BBF126F5752006F0F12 /* PlatformUtilities.cpp */,
                                BC131883117114A800B69727 /* PlatformUtilities.h */,
                                BC90951B125533D700083756 /* PlatformWebView.h */,
+                               5C9B548C223C4BF500B150C4 /* TCPServer.cpp */,
+                               5C9B548D223C4BF500B150C4 /* TCPServer.h */,
                                BCB9E7FA112359A300A137E0 /* Test.h */,
                                BC131AA8117131FC00B69727 /* TestsController.cpp */,
                                BCB9E7C711234E3A00A137E0 /* TestsController.h */,
                                7CCE7EA81A411A1900447C4C /* SyntheticBackingScaleFactorWindow.m in Sources */,
                                1C734B5320788C4800F430EA /* SystemColors.mm in Sources */,
                                2D70059621EDA0C6003463CB /* TabOutOfWebView.mm in Sources */,
+                               5C9B548E223C4CBE00B150C4 /* TCPServer.cpp in Sources */,
                                7CCE7F161A411AE600447C4C /* TerminateTwice.cpp in Sources */,
                                7CCE7EA91A411A1D00447C4C /* TestBrowsingContextLoadDelegate.mm in Sources */,
                                F46128CB211D475100D9FADB /* TestDraggingInfo.mm in Sources */,
index 258ae1d..a5c9827 100644 (file)
 #if PLATFORM(MAC) || PLATFORM(IOS)
 
 #import "PlatformUtilities.h"
+#import "TCPServer.h"
 #import "Test.h"
 #import "TestProtocol.h"
 #import "TestWKWebView.h"
-#import <WebKit/_WKDownload.h>
-#import <WebKit/_WKDownloadDelegate.h>
 #import <WebKit/WKNavigationDelegatePrivate.h>
 #import <WebKit/WKProcessPoolPrivate.h>
 #import <WebKit/WKUIDelegatePrivate.h>
 #import <WebKit/WKWebView.h>
 #import <WebKit/WKWebViewConfiguration.h>
+#import <WebKit/_WKDownload.h>
+#import <WebKit/_WKDownloadDelegate.h>
+#import <WebKit/_WKProcessPoolConfiguration.h>
 #import <wtf/FileSystem.h>
 #import <wtf/MainThread.h>
+#import <wtf/MonotonicTime.h>
 #import <wtf/RetainPtr.h>
 #import <wtf/WeakObjCPtr.h>
 #import <wtf/text/WTFString.h>
@@ -776,4 +779,139 @@ TEST(_WKDownload, CrashAfterDownloadDidFinishWhenDownloadProxyHoldsTheLastRefOnW
     EXPECT_NULL(processPool.get());
 }
 
-#endif
+static bool receivedData;
+static bool didCancel;
+static RetainPtr<NSString> destination;
+
+@interface DownloadMonitorTestDelegate : NSObject <_WKDownloadDelegate>
+@end
+
+@implementation DownloadMonitorTestDelegate
+
+- (void)_downloadDidStart:(_WKDownload *)download
+{
+    didDownloadStart = true;
+}
+
+- (void)_downloadDidCancel:(_WKDownload *)download
+{
+    didCancel = true;
+}
+
+- (void)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename completionHandler:(void (^)(BOOL allowOverwrite, NSString *destination))completionHandler
+{
+    EXPECT_TRUE([filename isEqualToString:@"filename.dat"]);
+    destination = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
+    completionHandler(YES, destination.get());
+}
+
+- (void)_download:(_WKDownload *)download didReceiveData:(uint64_t)length
+{
+    receivedData = true;
+}
+
+@end
+
+namespace TestWebKitAPI {
+
+void respondSlowly(int socket, double kbps, bool& terminateServer)
+{
+    EXPECT_FALSE(isMainThread());
+    char readBuffer[1000];
+    auto bytesRead = ::read(socket, readBuffer, sizeof(readBuffer));
+    EXPECT_GT(bytesRead, 0);
+    EXPECT_TRUE(static_cast<size_t>(bytesRead) < sizeof(readBuffer));
+    
+    const char* responseHeader =
+    "HTTP/1.1 200 OK\r\n"
+    "Content-Disposition: attachment; filename=\"filename.dat\"\r\n"
+    "Content-Length: 100000000\r\n\r\n";
+    auto bytesWritten = ::write(socket, responseHeader, strlen(responseHeader));
+    EXPECT_EQ(static_cast<size_t>(bytesWritten), strlen(responseHeader));
+    
+    const double writesPerSecond = 100;
+    Vector<char> writeBuffer(static_cast<size_t>(1024 * kbps / writesPerSecond));
+    while (!terminateServer) {
+        auto before = MonotonicTime::now();
+        ::write(socket, writeBuffer.data(), writeBuffer.size());
+        double writeDuration = (MonotonicTime::now() - before).seconds();
+        double desiredSleep = 1.0 / writesPerSecond;
+        if (writeDuration < desiredSleep)
+            usleep(USEC_PER_SEC * (desiredSleep - writeDuration));
+    }
+}
+
+RetainPtr<WKWebView> webViewWithDownloadMonitorSpeedMultiplier(size_t multiplier)
+{
+    static auto navigationDelegate = adoptNS([DownloadNavigationDelegate new]);
+    static auto downloadDelegate = adoptNS([DownloadMonitorTestDelegate new]);
+    auto processPoolConfiguration = adoptNS([_WKProcessPoolConfiguration new]);
+    [processPoolConfiguration setDownloadMonitorSpeedMultiplierForTesting:multiplier];
+    auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
+    auto webViewConfiguration = adoptNS([WKWebViewConfiguration new]);
+    [webViewConfiguration setProcessPool:processPool.get()];
+    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
+    [webView setNavigationDelegate:navigationDelegate.get()];
+    [webView configuration].processPool._downloadDelegate = downloadDelegate.get();
+    return webView;
+}
+
+enum class AppReturnsToForeground { No, Yes };
+    
+void downloadAtRate(double desiredKbps, unsigned speedMultiplier, AppReturnsToForeground returnToForeground = AppReturnsToForeground::No)
+{
+    bool terminateServer = false;
+    TCPServer server([&](auto socket) {
+        respondSlowly(socket, desiredKbps, terminateServer);
+    });
+    
+    auto webView = webViewWithDownloadMonitorSpeedMultiplier(speedMultiplier);
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/", server.port()]]]];
+    receivedData = false;
+    Util::run(&receivedData);
+    // Start the DownloadMonitor's timer.
+    [[webView configuration].processPool _synthesizeAppIsBackground:YES];
+    if (returnToForeground == AppReturnsToForeground::Yes)
+        [[webView configuration].processPool _synthesizeAppIsBackground:NO];
+    didCancel = false;
+    Util::run(&didCancel);
+    terminateServer = true;
+    [[NSFileManager defaultManager] removeItemAtURL:[NSURL fileURLWithPath:destination.get() isDirectory:NO] error:nil];
+}
+
+TEST(_WKDownload, DownloadMonitorCancel)
+{
+    downloadAtRate(0.5, 120); // Should cancel in ~0.5 seconds
+    downloadAtRate(1.5, 120); // Should cancel in ~2.5 seconds
+}
+
+TEST(_WKDownload, DownloadMonitorSurvive)
+{
+    __block BOOL timeoutReached = NO;
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+        EXPECT_FALSE(didCancel);
+        didCancel = true;
+        timeoutReached = YES;
+    });
+
+    // Simulates an hour of downloading 150kb/s in 1 second.
+    // Timeout should be reached before this is cancelled because the download rate is high enough.
+    downloadAtRate(150.0, 3600);
+    EXPECT_TRUE(timeoutReached);
+}
+
+TEST(_WKDownload, DownloadMonitorReturnToForeground)
+{
+    __block BOOL timeoutReached = NO;
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+        EXPECT_FALSE(didCancel);
+        didCancel = true;
+        timeoutReached = YES;
+    });
+    downloadAtRate(0.5, 120, AppReturnsToForeground::Yes);
+    EXPECT_TRUE(timeoutReached);
+}
+
+} // namespace TestWebKitAPI
+
+#endif // PLATFORM(MAC) || PLATFORM(IOS)