Add SPI to publish NSProgress on active downloads
authordavid_quesada@apple.com <david_quesada@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 30 Nov 2018 00:38:29 +0000 (00:38 +0000)
committerdavid_quesada@apple.com <david_quesada@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 30 Nov 2018 00:38:29 +0000 (00:38 +0000)
https://bugs.webkit.org/show_bug.cgi?id=192021
rdar://problem/44405661

Reviewed by Alex Christensen.

Source/WebCore/PAL:

Add an SPI header for NSProgress's publishing and unpublishing methods. In older
SDKs, these methods are explicitly unavailable on platforms other than macOS,
with underscore-prefixed versions available as SPI. In newer SDKs, the unprefixed
versions are SPI and the prefixed versions are deprecated.

* PAL.xcodeproj/project.pbxproj:
* pal/spi/cocoa/NSProgressSPI.h:

Source/WebKit:

Make it possible for clients to allow other processes to monitor the state of active
downloads. On Cocoa platforms, this can be done by creating an NSProgress, publishing
it on an appropriate file URL (potentially a different file URL than where the download
data is being written), updating properties on it as the download makes progress, and
wiring up a cancellation handler that allows it to be remotely canceled. Interested
clients can then subscribe to progress on that URL and receive a proxy to the progress
that WebKit publishes.

* NetworkProcess/Downloads/Download.cpp:
(WebKit::Download::~Download):
(WebKit::Download::platformDestroyDownload):
    Add a platform-customizable hook for destructing the Download. DownloadCocoa.mm
    will interact with its Objective-C NSProgress instance at this point.

* NetworkProcess/Downloads/Download.h:
* NetworkProcess/Downloads/DownloadManager.cpp:
(WebKit::DownloadManager::dataTaskBecameDownloadTask):
    See comments for publishDownloadProgress().
(WebKit::DownloadManager::publishDownloadProgress):
    If the provided downloadID corresponds to a non-Pending Download, hand the URL
    and a matching sandbox extension to the Download so it can create its progress.
    Otherwise, store the URL and sandbox extension on the PendingDownload to be used
    later when the full Download is created. When this happens, dataTaskBecameDownloadTask()
    will tell the PendingDownload about the Download it has become. The PendingDownload
    will then relay the progress URL and sandbox extension to the Download.

* NetworkProcess/Downloads/DownloadManager.h:
* NetworkProcess/Downloads/PendingDownload.cpp:
(WebKit::PendingDownload::publishProgress):
    Store the progress info for later use, when the proper Download is created.
(WebKit::PendingDownload::didBecomeDownload):
    If there was a progress URL provided earlier, tell the Download corresponding to this
    PendingDownload to publish its progress using that URL.

* NetworkProcess/Downloads/PendingDownload.h:
* NetworkProcess/Downloads/cocoa/DownloadCocoa.mm:
(WebKit::Download::platformDestroyDownload):
    When the Download is destroyed (i.e. when the download succeeds, fails, or is canceled),
    unpublish the progress, since there is no longer any activity to report.
(WebKit::Download::publishProgress):
    Resolve the sandbox extension, create a progress configured to reflect the progress of
    this Download's NSURLSessionDownloadTask, and publish it at the given file URL.

* NetworkProcess/Downloads/cocoa/WKDownloadProgress.h: Copied from Source/WebKit/UIProcess/API/Cocoa/_WKDownload.h.
* NetworkProcess/Downloads/cocoa/WKDownloadProgress.mm: Added.
(-[WKDownloadProgress initWithDownloadTask:download:URL:sandboxExtension:]):
    Configure this progress, start observing properties on the download task that this
    progress will reflect, and connect the cancellation handler of the progress to cancel
    the corresponding Download.
(-[WKDownloadProgress publish]):
(-[WKDownloadProgress unpublish]):
    Override these methods to consume and revoke the sandbox extension to make sure the
    process has access to the progress file while the progress will be published.
(-[WKDownloadProgress dealloc]):
(-[WKDownloadProgress observeValueForKeyPath:ofObject:change:context:]):
    When either byte count (number received, or number expected to receive) of the download
    task changes, update this progress to reflect that.

* NetworkProcess/NetworkProcess.cpp:
(WebKit::NetworkProcess::publishDownloadProgress):
* NetworkProcess/NetworkProcess.h:
* NetworkProcess/NetworkProcess.messages.in:

* NetworkProcess/mac/com.apple.WebKit.NetworkProcess.sb.in:
* Resources/SandboxProfiles/ios/com.apple.WebKit.Networking.sb:
    Allow looking up the services that manages published NSProgresses.

* UIProcess/API/Cocoa/_WKDownload.h:
* UIProcess/API/Cocoa/_WKDownload.mm:
(-[_WKDownload publishProgressAtURL:]):
* UIProcess/Downloads/DownloadProxy.cpp:
(WebKit::DownloadProxy::publishProgress):
* UIProcess/Downloads/DownloadProxy.h:
* WebKit.xcodeproj/project.pbxproj:

Tools:

Add API tests to exercise the progress-publishing functionality in a variety of scenarios.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/DownloadProgress.mm: Added.
(+[DownloadProgressTestProtocol registerProtocolForTestRunner:]):
(+[DownloadProgressTestProtocol unregisterProtocol]):
(+[DownloadProgressTestProtocol canInitWithRequest:]):
(+[DownloadProgressTestProtocol canonicalRequestForRequest:]):
(+[DownloadProgressTestProtocol requestIsCacheEquivalent:toRequest:]):
(-[DownloadProgressTestProtocol startLoading]):
(-[DownloadProgressTestProtocol stopLoading]):
(-[DownloadProgressTestRunner init]):
(-[DownloadProgressTestRunner startLoadingWithProtocol:]):
(-[DownloadProgressTestRunner tearDown]):
(-[DownloadProgressTestRunner _didGetProgress:]):
(-[DownloadProgressTestRunner _didLoseProgress]):
(-[DownloadProgressTestRunner subscribeAndWaitForProgress]):
(-[DownloadProgressTestRunner waitToLoseProgress]):
(-[DownloadProgressTestRunner startDownload:expectedLength:]):
(-[DownloadProgressTestRunner publishProgress]):
(-[DownloadProgressTestRunner receiveData:]):
(-[DownloadProgressTestRunner finishDownloadTask]):
(-[DownloadProgressTestRunner failDownloadTask]):
(-[DownloadProgressTestRunner waitForDownloadDidCreateDestination]):
(-[DownloadProgressTestRunner waitForDownloadFinished]):
(-[DownloadProgressTestRunner waitForDownloadCanceled]):
(-[DownloadProgressTestRunner waitForDownloadFailed]):
(-[DownloadProgressTestRunner waitForUpdatedCompletedUnitCount]):
(-[DownloadProgressTestRunner observeValueForKeyPath:ofObject:change:context:]):
(-[DownloadProgressTestRunner webView:decidePolicyForNavigationResponse:decisionHandler:]):
(-[DownloadProgressTestRunner webView:decidePolicyForNavigationAction:decisionHandler:]):
(-[DownloadProgressTestRunner _downloadDidStart:]):
(-[DownloadProgressTestRunner _download:didCreateDestination:]):
(-[DownloadProgressTestRunner _downloadDidFinish:]):
(-[DownloadProgressTestRunner _downloadDidCancel:]):
(-[DownloadProgressTestRunner _download:didFailWithError:]):
(-[DownloadProgressTestRunner _download:decideDestinationWithSuggestedFilename:completionHandler:]):
(TEST):

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

26 files changed:
Source/WebCore/PAL/ChangeLog
Source/WebCore/PAL/PAL.xcodeproj/project.pbxproj
Source/WebCore/PAL/pal/spi/cocoa/NSProgressSPI.h [new file with mode: 0644]
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/PendingDownload.cpp
Source/WebKit/NetworkProcess/Downloads/PendingDownload.h
Source/WebKit/NetworkProcess/Downloads/cocoa/DownloadCocoa.mm
Source/WebKit/NetworkProcess/Downloads/cocoa/WKDownloadProgress.h [new file with mode: 0644]
Source/WebKit/NetworkProcess/Downloads/cocoa/WKDownloadProgress.mm [new file with mode: 0644]
Source/WebKit/NetworkProcess/NetworkProcess.cpp
Source/WebKit/NetworkProcess/NetworkProcess.h
Source/WebKit/NetworkProcess/NetworkProcess.messages.in
Source/WebKit/NetworkProcess/mac/com.apple.WebKit.NetworkProcess.sb.in
Source/WebKit/Resources/SandboxProfiles/ios/com.apple.WebKit.Networking.sb
Source/WebKit/UIProcess/API/Cocoa/_WKDownload.h
Source/WebKit/UIProcess/API/Cocoa/_WKDownload.mm
Source/WebKit/UIProcess/Downloads/DownloadProxy.cpp
Source/WebKit/UIProcess/Downloads/DownloadProxy.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/DownloadProgress.mm [new file with mode: 0644]

index 505c241..8dea400 100644 (file)
@@ -1,3 +1,19 @@
+2018-11-29  David Quesada  <david_quesada@apple.com>
+
+        Add SPI to publish NSProgress on active downloads
+        https://bugs.webkit.org/show_bug.cgi?id=192021
+        rdar://problem/44405661
+
+        Reviewed by Alex Christensen.
+
+        Add an SPI header for NSProgress's publishing and unpublishing methods. In older
+        SDKs, these methods are explicitly unavailable on platforms other than macOS,
+        with underscore-prefixed versions available as SPI. In newer SDKs, the unprefixed
+        versions are SPI and the prefixed versions are deprecated.
+
+        * PAL.xcodeproj/project.pbxproj:
+        * pal/spi/cocoa/NSProgressSPI.h:
+
 2018-11-29  Megan Gardner  <megan_gardner@apple.com>
 
         Move Lookup Code for better cross platform usage
index b8cc358..10df3ee 100644 (file)
                442956CD218A72DF0080DB54 /* RevealSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 442956CC218A72DE0080DB54 /* RevealSPI.h */; };
                570AB8F120AE2E8D00B8BE87 /* SecKeyProxySPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 570AB8F020AE2E8D00B8BE87 /* SecKeyProxySPI.h */; };
                570AB8F920AF6E3D00B8BE87 /* NSXPCConnectionSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 570AB8F820AF6E3D00B8BE87 /* NSXPCConnectionSPI.h */; };
+               63C7EDC721AFAE04006A7B99 /* NSProgressSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 63E369F921AFA83F001C14BC /* NSProgressSPI.h */; };
                7A1656441F97B2B900BA3CE4 /* NSKeyedArchiverSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A1656431F97B2B800BA3CE4 /* NSKeyedArchiverSPI.h */; };
                7A3A6A8020CADB4700317AAE /* NSImageSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A3A6A7F20CADB4600317AAE /* NSImageSPI.h */; };
                A10265891F56747A00B4C844 /* HIToolboxSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = A10265881F56747A00B4C844 /* HIToolboxSPI.h */; };
                442956CC218A72DE0080DB54 /* RevealSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RevealSPI.h; sourceTree = "<group>"; };
                570AB8F020AE2E8D00B8BE87 /* SecKeyProxySPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecKeyProxySPI.h; sourceTree = "<group>"; };
                570AB8F820AF6E3D00B8BE87 /* NSXPCConnectionSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSXPCConnectionSPI.h; sourceTree = "<group>"; };
+               63E369F921AFA83F001C14BC /* NSProgressSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSProgressSPI.h; sourceTree = "<group>"; };
                7A1656431F97B2B800BA3CE4 /* NSKeyedArchiverSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSKeyedArchiverSPI.h; sourceTree = "<group>"; };
                7A3A6A7F20CADB4600317AAE /* NSImageSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSImageSPI.h; sourceTree = "<group>"; };
                93E5909C1F93BF1E0067F8CF /* UnencodableHandling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UnencodableHandling.h; sourceTree = "<group>"; };
                                0C2DA1331F3BEB4900DBC317 /* NSFileManagerSPI.h */,
                                F442915D1FA52473002CC93E /* NSFileSizeFormatterSPI.h */,
                                7A1656431F97B2B800BA3CE4 /* NSKeyedArchiverSPI.h */,
+                               63E369F921AFA83F001C14BC /* NSProgressSPI.h */,
                                0C2DA1341F3BEB4900DBC317 /* NSStringSPI.h */,
                                0C2DA1351F3BEB4900DBC317 /* NSTouchBarSPI.h */,
                                0C2DA1361F3BEB4900DBC317 /* NSURLConnectionSPI.h */,
                                0C77859F1F45130F00F4EBB6 /* QTKitSPI.h in Headers */,
                                0C2DA1571F3BEB4900DBC317 /* QuartzCoreSPI.h in Headers */,
                                A102658B1F56748C00B4C844 /* QuickDrawSPI.h in Headers */,
+                               63C7EDC721AFAE04006A7B99 /* NSProgressSPI.h in Headers */,
                                0C7785A01F45130F00F4EBB6 /* QuickLookMacSPI.h in Headers */,
                                0C5AF9201F43A4C7002EAC02 /* QuickLookSPI.h in Headers */,
                                442956CD218A72DF0080DB54 /* RevealSPI.h in Headers */,
diff --git a/Source/WebCore/PAL/pal/spi/cocoa/NSProgressSPI.h b/Source/WebCore/PAL/pal/spi/cocoa/NSProgressSPI.h
new file mode 100644 (file)
index 0000000..79370a1
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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
+
+#define USE_NSPROGRESS_PUBLISHING_SPI ((PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000) || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED < 60000) || (PLATFORM(TVOS) && __TV_OS_VERSION_MIN_REQUIRED < 130000))
+
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+
+#if USE(APPLE_INTERNAL_SDK)
+
+#import <Foundation/NSProgress_Private.h>
+
+#else
+
+@interface NSProgress ()
+
+- (void)_publish;
+- (void)_unpublish;
++ (id)_addSubscriberForFileURL:(NSURL *)inURL withPublishingHandler:(NSProgressPublishingHandler)inPublishingHandler;
++ (void)_removeSubscriber:(id)inSubscriber;
+
+@end
+
+#endif // not USE(APPLE_INTERNAL_SDK)
+
+#endif // USE(NSPROGRESS_PUBLISHING_SPI)
index 59d05d4..a4ec09f 100644 (file)
@@ -1,3 +1,86 @@
+2018-11-29  David Quesada  <david_quesada@apple.com>
+
+        Add SPI to publish NSProgress on active downloads
+        https://bugs.webkit.org/show_bug.cgi?id=192021
+        rdar://problem/44405661
+
+        Reviewed by Alex Christensen.
+
+        Make it possible for clients to allow other processes to monitor the state of active
+        downloads. On Cocoa platforms, this can be done by creating an NSProgress, publishing
+        it on an appropriate file URL (potentially a different file URL than where the download
+        data is being written), updating properties on it as the download makes progress, and
+        wiring up a cancellation handler that allows it to be remotely canceled. Interested
+        clients can then subscribe to progress on that URL and receive a proxy to the progress
+        that WebKit publishes.
+
+        * NetworkProcess/Downloads/Download.cpp:
+        (WebKit::Download::~Download):
+        (WebKit::Download::platformDestroyDownload):
+            Add a platform-customizable hook for destructing the Download. DownloadCocoa.mm
+            will interact with its Objective-C NSProgress instance at this point.
+
+        * NetworkProcess/Downloads/Download.h:
+        * NetworkProcess/Downloads/DownloadManager.cpp:
+        (WebKit::DownloadManager::dataTaskBecameDownloadTask):
+            See comments for publishDownloadProgress().
+        (WebKit::DownloadManager::publishDownloadProgress):
+            If the provided downloadID corresponds to a non-Pending Download, hand the URL
+            and a matching sandbox extension to the Download so it can create its progress.
+            Otherwise, store the URL and sandbox extension on the PendingDownload to be used
+            later when the full Download is created. When this happens, dataTaskBecameDownloadTask()
+            will tell the PendingDownload about the Download it has become. The PendingDownload
+            will then relay the progress URL and sandbox extension to the Download.
+
+        * NetworkProcess/Downloads/DownloadManager.h:
+        * NetworkProcess/Downloads/PendingDownload.cpp:
+        (WebKit::PendingDownload::publishProgress):
+            Store the progress info for later use, when the proper Download is created.
+        (WebKit::PendingDownload::didBecomeDownload):
+            If there was a progress URL provided earlier, tell the Download corresponding to this
+            PendingDownload to publish its progress using that URL.
+
+        * NetworkProcess/Downloads/PendingDownload.h:
+        * NetworkProcess/Downloads/cocoa/DownloadCocoa.mm:
+        (WebKit::Download::platformDestroyDownload):
+            When the Download is destroyed (i.e. when the download succeeds, fails, or is canceled),
+            unpublish the progress, since there is no longer any activity to report.
+        (WebKit::Download::publishProgress):
+            Resolve the sandbox extension, create a progress configured to reflect the progress of
+            this Download's NSURLSessionDownloadTask, and publish it at the given file URL.
+
+        * NetworkProcess/Downloads/cocoa/WKDownloadProgress.h: Copied from Source/WebKit/UIProcess/API/Cocoa/_WKDownload.h.
+        * NetworkProcess/Downloads/cocoa/WKDownloadProgress.mm: Added.
+        (-[WKDownloadProgress initWithDownloadTask:download:URL:sandboxExtension:]):
+            Configure this progress, start observing properties on the download task that this
+            progress will reflect, and connect the cancellation handler of the progress to cancel
+            the corresponding Download.
+        (-[WKDownloadProgress publish]):
+        (-[WKDownloadProgress unpublish]):
+            Override these methods to consume and revoke the sandbox extension to make sure the
+            process has access to the progress file while the progress will be published.
+        (-[WKDownloadProgress dealloc]):
+        (-[WKDownloadProgress observeValueForKeyPath:ofObject:change:context:]):
+            When either byte count (number received, or number expected to receive) of the download
+            task changes, update this progress to reflect that.
+
+        * NetworkProcess/NetworkProcess.cpp:
+        (WebKit::NetworkProcess::publishDownloadProgress):
+        * NetworkProcess/NetworkProcess.h:
+        * NetworkProcess/NetworkProcess.messages.in:
+
+        * NetworkProcess/mac/com.apple.WebKit.NetworkProcess.sb.in:
+        * Resources/SandboxProfiles/ios/com.apple.WebKit.Networking.sb:
+            Allow looking up the services that manages published NSProgresses.
+
+        * UIProcess/API/Cocoa/_WKDownload.h:
+        * UIProcess/API/Cocoa/_WKDownload.mm:
+        (-[_WKDownload publishProgressAtURL:]):
+        * UIProcess/Downloads/DownloadProxy.cpp:
+        (WebKit::DownloadProxy::publishProgress):
+        * UIProcess/Downloads/DownloadProxy.h:
+        * WebKit.xcodeproj/project.pbxproj:
+
 2018-11-29  Tim Horton  <timothy_horton@apple.com>
 
         Make drawing tools available when an editable image is focused
index 494d99f..177f9ba 100644 (file)
@@ -77,6 +77,7 @@ Download::Download(DownloadManager& downloadManager, DownloadID downloadID, NSUR
 
 Download::~Download()
 {
+    platformDestroyDownload();
     m_downloadManager.didDestroyDownload();
 }
 
@@ -179,6 +180,10 @@ bool Download::isAlwaysOnLoggingAllowed() const
 void Download::platformCancelNetworkLoad()
 {
 }
+
+void Download::platformDestroyDownload()
+{
+}
 #endif
 
 } // namespace WebKit
index 590f9eb..bdbc8e1 100644 (file)
@@ -37,6 +37,7 @@
 #include <wtf/RetainPtr.h>
 
 #if PLATFORM(COCOA)
+OBJC_CLASS NSProgress;
 OBJC_CLASS NSURLSessionDownloadTask;
 #endif
 
@@ -73,6 +74,9 @@ public:
 
     void resume(const IPC::DataReference& resumeData, const String& path, SandboxExtension::Handle&&);
     void cancel();
+#if PLATFORM(COCOA)
+    void publishProgress(const WebCore::URL&, SandboxExtension::Handle&&);
+#endif
 
     DownloadID downloadID() const { return m_downloadID; }
     const String& suggestedName() const { return m_suggestedName; }
@@ -91,6 +95,7 @@ private:
     uint64_t messageSenderDestinationID() override;
 
     void platformCancelNetworkLoad();
+    void platformDestroyDownload();
 
     bool isAlwaysOnLoggingAllowed() const;
 
@@ -103,6 +108,7 @@ private:
     RefPtr<NetworkDataTask> m_download;
 #if PLATFORM(COCOA)
     RetainPtr<NSURLSessionDownloadTask> m_downloadTask;
+    RetainPtr<NSProgress> m_progress;
 #endif
     PAL::SessionID m_sessionID;
     String m_suggestedName;
index 1e28fc9..7d3a1d7 100644 (file)
@@ -64,7 +64,11 @@ void DownloadManager::startDownload(NetworkConnectionToWebProcess* connection, P
 void DownloadManager::dataTaskBecameDownloadTask(DownloadID downloadID, std::unique_ptr<Download>&& download)
 {
     ASSERT(m_pendingDownloads.contains(downloadID));
-    m_pendingDownloads.remove(downloadID);
+    if (auto pendingDownload = m_pendingDownloads.take(downloadID)) {
+#if PLATFORM(COCOA)
+        pendingDownload->didBecomeDownload(download);
+#endif
+    }
     ASSERT(!m_downloads.contains(downloadID));
     m_downloadsAfterDestinationDecided.remove(downloadID);
     m_downloads.add(downloadID, WTFMove(download));
@@ -155,6 +159,16 @@ void DownloadManager::cancelDownload(DownloadID downloadID)
         pendingDownload->cancel();
 }
 
+#if PLATFORM(COCOA)
+void DownloadManager::publishDownloadProgress(DownloadID downloadID, const WebCore::URL& url, SandboxExtension::Handle&& sandboxExtensionHandle)
+{
+    if (auto* download = m_downloads.get(downloadID))
+        download->publishProgress(url, WTFMove(sandboxExtensionHandle));
+    else if (auto* pendingDownload = m_pendingDownloads.get(downloadID))
+        pendingDownload->publishProgress(url, WTFMove(sandboxExtensionHandle));
+}
+#endif // PLATFORM(COCOA)
+
 void DownloadManager::downloadFinished(Download* download)
 {
     ASSERT(m_downloads.contains(download->downloadID()));
index 51d0ef1..8078410 100644 (file)
@@ -85,6 +85,9 @@ public:
     void resumeDownload(PAL::SessionID, DownloadID, const IPC::DataReference& resumeData, const String& path, SandboxExtension::Handle&&);
 
     void cancelDownload(DownloadID);
+#if PLATFORM(COCOA)
+    void publishDownloadProgress(DownloadID, const WebCore::URL&, SandboxExtension::Handle&&);
+#endif
     
     Download* download(DownloadID downloadID) { return m_downloads.get(downloadID); }
 
index a099ab3..aec4da2 100644 (file)
@@ -75,6 +75,21 @@ void PendingDownload::cancel()
     send(Messages::DownloadProxy::DidCancel({ }));
 }
 
+#if PLATFORM(COCOA)
+void PendingDownload::publishProgress(const WebCore::URL& url, SandboxExtension::Handle&& sandboxExtension)
+{
+    ASSERT(!m_progressURL.isValid());
+    m_progressURL = url;
+    m_progressSandboxExtension = WTFMove(sandboxExtension);
+}
+
+void PendingDownload::didBecomeDownload(const std::unique_ptr<Download>& download)
+{
+    if (m_progressURL.isValid())
+        download->publishProgress(m_progressURL, WTFMove(m_progressSandboxExtension));
+}
+#endif // PLATFORM(COCOA)
+
 void PendingDownload::didFailLoading(const WebCore::ResourceError& error)
 {
     send(Messages::DownloadProxy::DidFail(error, { }));
index a383749..9442edb 100644 (file)
@@ -34,6 +34,7 @@ class ResourceResponse;
 
 namespace WebKit {
 
+class Download;
 class DownloadID;
 class NetworkLoad;
 class NetworkLoadParameters;
@@ -48,6 +49,11 @@ public:
     void continueWillSendRequest(WebCore::ResourceRequest&&);
     void cancel();
 
+#if PLATFORM(COCOA)
+    void publishProgress(const WebCore::URL&, SandboxExtension::Handle&&);
+    void didBecomeDownload(const std::unique_ptr<Download>&);
+#endif
+
 private:    
     // NetworkLoadClient.
     void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) override { }
@@ -66,6 +72,11 @@ private:
 private:
     std::unique_ptr<NetworkLoad> m_networkLoad;
     bool m_isAllowedToAskUserForCredentials;
+
+#if PLATFORM(COCOA)
+    WebCore::URL m_progressURL;
+    SandboxExtension::Handle m_progressSandboxExtension;
+#endif
 };
 
 }
index efd71b2..2517104 100644 (file)
@@ -29,7 +29,9 @@
 #import "DataReference.h"
 #import "NetworkSessionCocoa.h"
 #import "SessionTracker.h"
+#import "WKDownloadProgress.h"
 #import <pal/spi/cf/CFNetworkSPI.h>
+#import <pal/spi/cocoa/NSProgressSPI.h>
 
 namespace WebKit {
 
@@ -87,4 +89,36 @@ void Download::platformCancelNetworkLoad()
     }];
 }
 
+void Download::platformDestroyDownload()
+{
+    if (m_progress)
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+        [m_progress _unpublish];
+#else
+        [m_progress unpublish];
+#endif
+}
+
+void Download::publishProgress(const WebCore::URL& url, SandboxExtension::Handle&& sandboxExtensionHandle)
+{
+#if WK_API_ENABLED
+    ASSERT(!m_progress);
+    ASSERT(url.isValid());
+
+    auto sandboxExtension = SandboxExtension::create(WTFMove(sandboxExtensionHandle));
+
+    ASSERT(sandboxExtension);
+
+    m_progress = adoptNS([[WKDownloadProgress alloc] initWithDownloadTask:m_downloadTask.get() download:this URL:(NSURL *)url sandboxExtension:sandboxExtension]);
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+    [m_progress _publish];
+#else
+    [m_progress publish];
+#endif
+#else
+    UNUSED_PARAM(url);
+    UNUSED_PARAM(sandboxExtensionHandle);
+#endif // not WK_API_ENABLED
+}
+
 }
diff --git a/Source/WebKit/NetworkProcess/Downloads/cocoa/WKDownloadProgress.h b/Source/WebKit/NetworkProcess/Downloads/cocoa/WKDownloadProgress.h
new file mode 100644 (file)
index 0000000..2b763af
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#if WK_API_ENABLED
+
+#import <Foundation/NSProgress.h>
+#import <wtf/RefPtr.h>
+
+namespace WebKit {
+
+class Download;
+class SandboxExtension;
+
+}
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface WKDownloadProgress : NSProgress
+
+- (instancetype)initWithDownloadTask:(NSURLSessionDownloadTask *)task download:(WebKit::Download*)download URL:(NSURL *)fileURL sandboxExtension:(RefPtr<WebKit::SandboxExtension>)sandboxExtension;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // WK_API_ENABLED
diff --git a/Source/WebKit/NetworkProcess/Downloads/cocoa/WKDownloadProgress.mm b/Source/WebKit/NetworkProcess/Downloads/cocoa/WKDownloadProgress.mm
new file mode 100644 (file)
index 0000000..feba0c9
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#import "config.h"
+#import "WKDownloadProgress.h"
+
+#if WK_API_ENABLED
+
+#import "Download.h"
+#import <pal/spi/cocoa/NSProgressSPI.h>
+#import <wtf/BlockPtr.h>
+#import <wtf/WeakObjCPtr.h>
+
+static void* WKDownloadProgressBytesExpectedToReceiveCountContext = &WKDownloadProgressBytesExpectedToReceiveCountContext;
+static void* WKDownloadProgressBytesReceivedContext = &WKDownloadProgressBytesReceivedContext;
+
+static NSString * const countOfBytesExpectedToReceiveKeyPath = @"countOfBytesExpectedToReceive";
+static NSString * const countOfBytesReceivedKeyPath = @"countOfBytesReceived";
+
+@implementation WKDownloadProgress {
+    RetainPtr<NSURLSessionDownloadTask> m_task;
+    WebKit::Download* m_download;
+    RefPtr<WebKit::SandboxExtension> m_sandboxExtension;
+}
+
+- (instancetype)initWithDownloadTask:(NSURLSessionDownloadTask *)task download:(WebKit::Download*)download URL:(NSURL *)fileURL sandboxExtension:(RefPtr<WebKit::SandboxExtension>)sandboxExtension
+{
+    if (!(self = [self initWithParent:nil userInfo:nil]))
+        return nil;
+
+    m_task = task;
+    m_download = download;
+
+    [task addObserver:self forKeyPath:countOfBytesExpectedToReceiveKeyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:WKDownloadProgressBytesExpectedToReceiveCountContext];
+    [task addObserver:self forKeyPath:countOfBytesReceivedKeyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:WKDownloadProgressBytesReceivedContext];
+
+    self.kind = NSProgressKindFile;
+#if !PLATFORM(MAC) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
+    self.fileOperationKind = NSProgressFileOperationKindDownloading;
+    self.fileURL = fileURL;
+#else
+    [self setUserInfoObject:NSProgressFileOperationKindDownloading forKey:NSProgressFileOperationKindKey];
+    [self setUserInfoObject:fileURL forKey:NSProgressFileURLKey];
+#endif
+    m_sandboxExtension = sandboxExtension;
+
+    self.cancellable = YES;
+    self.cancellationHandler = BlockPtr<void()>::fromCallable([weakSelf = WeakObjCPtr<WKDownloadProgress> { self }] {
+        auto strongSelf = weakSelf.get();
+        if (!strongSelf)
+            return;
+
+        if (auto* download = strongSelf.get()->m_download)
+            download->cancel();
+    }).get();
+
+    return self;
+}
+
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+- (void)_publish
+#else
+- (void)publish
+#endif
+{
+    BOOL consumedExtension = m_sandboxExtension->consume();
+    ASSERT_UNUSED(consumedExtension, consumedExtension);
+
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+    [super _publish];
+#else
+    [super publish];
+#endif
+}
+
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+- (void)_unpublish
+#else
+- (void)unpublish
+#endif
+{
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+    [super _unpublish];
+#else
+    [super unpublish];
+#endif
+
+    m_sandboxExtension->revoke();
+    m_sandboxExtension = nullptr;
+}
+
+- (void)dealloc
+{
+    [m_task.get() removeObserver:self forKeyPath:countOfBytesExpectedToReceiveKeyPath];
+    [m_task.get() removeObserver:self forKeyPath:countOfBytesReceivedKeyPath];
+
+    ASSERT(!m_sandboxExtension);
+
+    [super dealloc];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
+{
+    if (context == WKDownloadProgressBytesExpectedToReceiveCountContext) {
+        RetainPtr<NSNumber> value = static_cast<NSNumber *>(change[NSKeyValueChangeNewKey]);
+        ASSERT([value isKindOfClass:[NSNumber class]]);
+        int64_t expectedByteCount = value.get().longLongValue;
+        self.totalUnitCount = (expectedByteCount <= 0) ? -1 : expectedByteCount;
+    } else if (context == WKDownloadProgressBytesReceivedContext) {
+        RetainPtr<NSNumber> value = static_cast<NSNumber *>(change[NSKeyValueChangeNewKey]);
+        ASSERT([value isKindOfClass:[NSNumber class]]);
+        self.completedUnitCount = value.get().longLongValue;
+    } else
+        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+}
+
+@end
+
+#endif // WK_API_ENABLED
index 7488c9b..f740f4c 100644 (file)
@@ -819,6 +819,13 @@ void NetworkProcess::cancelDownload(DownloadID downloadID)
     downloadManager().cancelDownload(downloadID);
 }
 
+#if PLATFORM(COCOA)
+void NetworkProcess::publishDownloadProgress(DownloadID downloadID, const WebCore::URL& url, SandboxExtension::Handle&& sandboxExtensionHandle)
+{
+    downloadManager().publishDownloadProgress(downloadID, url, WTFMove(sandboxExtensionHandle));
+}
+#endif
+
 void NetworkProcess::continueWillSendRequest(DownloadID downloadID, WebCore::ResourceRequest&& request)
 {
     downloadManager().continueWillSendRequest(downloadID, WTFMove(request));
index 1798668..5d799b4 100644 (file)
@@ -274,6 +274,9 @@ private:
     void downloadRequest(PAL::SessionID, DownloadID, const WebCore::ResourceRequest&, const String& suggestedFilename);
     void resumeDownload(PAL::SessionID, DownloadID, const IPC::DataReference& resumeData, const String& path, SandboxExtension::Handle&&);
     void cancelDownload(DownloadID);
+#if PLATFORM(COCOA)
+    void publishDownloadProgress(DownloadID, const WebCore::URL&, SandboxExtension::Handle&&);
+#endif
     void continueWillSendRequest(DownloadID, WebCore::ResourceRequest&&);
     void continueDecidePendingDownloadDestination(DownloadID, String destination, SandboxExtension::Handle&&, bool allowOverwrite);
 
index 94d9c03..f1e0437 100644 (file)
@@ -49,6 +49,9 @@ messages -> NetworkProcess LegacyReceiver {
     DownloadRequest(PAL::SessionID sessionID, WebKit::DownloadID downloadID, WebCore::ResourceRequest request, String suggestedFilename)
     ResumeDownload(PAL::SessionID sessionID, WebKit::DownloadID downloadID, IPC::DataReference resumeData, String path, WebKit::SandboxExtension::Handle sandboxExtensionHandle)
     CancelDownload(WebKit::DownloadID downloadID)
+#if PLATFORM(COCOA)
+    PublishDownloadProgress(WebKit::DownloadID downloadID, WebCore::URL url, WebKit::SandboxExtension::Handle sandboxExtensionHandle)
+#endif
 
     ContinueWillSendRequest(WebKit::DownloadID downloadID, WebCore::ResourceRequest request)
     ContinueDecidePendingDownloadDestination(WebKit::DownloadID downloadID, String destination, WebKit::SandboxExtension::Handle sandboxExtensionHandle, bool allowOverwrite)
index cab796d..0c1816f 100644 (file)
     (local tcp)
     (remote tcp))
 
+;; For reporting progress for active downloads <rdar://problem/44405661>
+(allow mach-lookup
+    (global-name "com.apple.ProgressReporting"))
+
index ee39105..0a391da 100644 (file)
@@ -94,3 +94,7 @@
     (global-name "com.apple.WirelessCoexManager")
     (global-name "com.apple.wifi.manager")
     (global-name "com.apple.identityservicesd.embedded.auth"))
+
+;; For reporting progress for active downloads <rdar://problem/44405661>
+(allow mach-lookup
+    (global-name "com.apple.ProgressReporting"))
index acfd834..718692d 100644 (file)
@@ -36,6 +36,8 @@ WK_CLASS_AVAILABLE(macosx(10.10), ios(8.0))
 
 - (void)cancel;
 
+- (void)publishProgressAtURL:(NSURL *)URL WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 @property (nonatomic, readonly) NSURLRequest *request;
 @property (nonatomic, readonly, weak) WKWebView *originatingWebView;
 @property (nonatomic, readonly, copy) NSArray<NSURL *> *redirectChain WK_API_AVAILABLE(macosx(10.13.4), ios(11.3));
index 16ad810..87721dc 100644 (file)
     _download->cancel();
 }
 
+- (void)publishProgressAtURL:(NSURL *)URL
+{
+    _download->publishProgress(URL);
+}
+
 - (NSURLRequest *)request
 {
     return _download->request().nsURLRequest(WebCore::HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody);
index efb1149..b22e4f3 100644 (file)
@@ -101,6 +101,21 @@ void DownloadProxy::setOriginatingPage(WebPageProxy* page)
     m_originatingPage = makeWeakPtr(page);
 }
 
+#if PLATFORM(COCOA)
+void DownloadProxy::publishProgress(const WebCore::URL& URL)
+{
+    if (!m_processPool)
+        return;
+
+    if (auto* networkProcess = m_processPool->networkProcess()) {
+        SandboxExtension::Handle handle;
+        bool createdSandboxExtension = SandboxExtension::createHandle(URL.fileSystemPath(), SandboxExtension::Type::ReadWrite, handle);
+        ASSERT_UNUSED(createdSandboxExtension, createdSandboxExtension);
+        networkProcess->send(Messages::NetworkProcess::PublishDownloadProgress(m_downloadID, URL, handle), 0);
+    }
+}
+#endif // PLATFORM(COCOA)
+
 void DownloadProxy::didStart(const ResourceRequest& request, const String& suggestedFilename)
 {
     m_request = request;
index e07aac8..f9b8243 100644 (file)
@@ -95,6 +95,10 @@ public:
     const WebCore::IntRect& systemPreviewDownloadRect() const { return request().systemPreviewRect(); }
 #endif
 
+#if PLATFORM(COCOA)
+    void publishProgress(const WebCore::URL&);
+#endif
+
 private:
     explicit DownloadProxy(DownloadProxyMap&, WebProcessPool&, const WebCore::ResourceRequest&);
 
index e8ddf7f..d288e3b 100644 (file)
                63108F991F9671F700A0DB84 /* _WKApplicationManifestInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 63108F981F9671F700A0DB84 /* _WKApplicationManifestInternal.h */; };
                634842511FB26E7100946E3C /* APIApplicationManifest.h in Headers */ = {isa = PBXBuildFile; fileRef = 6348424F1FB26E7100946E3C /* APIApplicationManifest.h */; };
                636353A51E9858DF0009F8AF /* _WKGeolocationCoreLocationProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 63FABE191E970D65003011D5 /* _WKGeolocationCoreLocationProvider.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               637281A221ADC744009E0DE6 /* WKDownloadProgress.h in Headers */ = {isa = PBXBuildFile; fileRef = 637281A021ADC744009E0DE6 /* WKDownloadProgress.h */; };
+               637281A321ADC744009E0DE6 /* WKDownloadProgress.mm in Sources */ = {isa = PBXBuildFile; fileRef = 637281A121ADC744009E0DE6 /* WKDownloadProgress.mm */; };
                63C32C261E9810D900699BD0 /* _WKGeolocationPosition.h in Headers */ = {isa = PBXBuildFile; fileRef = 63C32C241E9810D900699BD0 /* _WKGeolocationPosition.h */; settings = {ATTRIBUTES = (Private, ); }; };
                63C32C281E98119000699BD0 /* _WKGeolocationPositionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 63C32C271E98119000699BD0 /* _WKGeolocationPositionInternal.h */; };
                65B86F1E12F11DE300B7DD8A /* WKBundleInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 65B86F1812F11D7B00B7DD8A /* WKBundleInspector.h */; settings = {ATTRIBUTES = (Private, ); }; };
                63108F951F96719C00A0DB84 /* _WKApplicationManifest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKApplicationManifest.mm; sourceTree = "<group>"; };
                63108F981F9671F700A0DB84 /* _WKApplicationManifestInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKApplicationManifestInternal.h; sourceTree = "<group>"; };
                6348424F1FB26E7100946E3C /* APIApplicationManifest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = APIApplicationManifest.h; sourceTree = "<group>"; };
+               637281A021ADC744009E0DE6 /* WKDownloadProgress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKDownloadProgress.h; sourceTree = "<group>"; };
+               637281A121ADC744009E0DE6 /* WKDownloadProgress.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WKDownloadProgress.mm; sourceTree = "<group>"; };
                63C32C231E9810D900699BD0 /* _WKGeolocationPosition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKGeolocationPosition.mm; sourceTree = "<group>"; };
                63C32C241E9810D900699BD0 /* _WKGeolocationPosition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKGeolocationPosition.h; sourceTree = "<group>"; };
                63C32C271E98119000699BD0 /* _WKGeolocationPositionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKGeolocationPositionInternal.h; sourceTree = "<group>"; };
                        isa = PBXGroup;
                        children = (
                                5C1427091C23F86900D41183 /* DownloadCocoa.mm */,
+                               637281A021ADC744009E0DE6 /* WKDownloadProgress.h */,
+                               637281A121ADC744009E0DE6 /* WKDownloadProgress.mm */,
                        );
                        path = cocoa;
                        sourceTree = "<group>";
                                C0E3AA7C1209E83C00A49D01 /* Module.h in Headers */,
                                2D50366B1BCDE17900E20BB3 /* NativeWebGestureEvent.h in Headers */,
                                263172CF18B469490065B9C3 /* NativeWebTouchEvent.h in Headers */,
+                               637281A221ADC744009E0DE6 /* WKDownloadProgress.h in Headers */,
                                1ADCB86B189831B30022EE5A /* NavigationActionData.h in Headers */,
                                1ABC3DF61899E437004F0626 /* NavigationState.h in Headers */,
                                1A6FBA2A11E6862700DB1371 /* NetscapeBrowserFuncs.h in Headers */,
                                2D92A794212B6AD400F493FD /* NPVariantData.cpp in Sources */,
                                CDA29A281CBEB67A00901CCF /* PlaybackSessionManagerMessageReceiver.cpp in Sources */,
                                CDA29A2A1CBEB67A00901CCF /* PlaybackSessionManagerProxyMessageReceiver.cpp in Sources */,
+                               637281A321ADC744009E0DE6 /* WKDownloadProgress.mm in Sources */,
                                2D913449212CF9F000128AFD /* Plugin.cpp in Sources */,
                                1A8EF4CC1252403700F7067F /* PluginControllerProxy.cpp in Sources */,
                                1A2D91A61281D739001EB962 /* PluginControllerProxyMac.mm in Sources */,
index b72a1f6..7da1dc6 100644 (file)
@@ -1,3 +1,50 @@
+2018-11-29  David Quesada  <david_quesada@apple.com>
+
+        Add SPI to publish NSProgress on active downloads
+        https://bugs.webkit.org/show_bug.cgi?id=192021
+        rdar://problem/44405661
+
+        Reviewed by Alex Christensen.
+
+        Add API tests to exercise the progress-publishing functionality in a variety of scenarios.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/DownloadProgress.mm: Added.
+        (+[DownloadProgressTestProtocol registerProtocolForTestRunner:]):
+        (+[DownloadProgressTestProtocol unregisterProtocol]):
+        (+[DownloadProgressTestProtocol canInitWithRequest:]):
+        (+[DownloadProgressTestProtocol canonicalRequestForRequest:]):
+        (+[DownloadProgressTestProtocol requestIsCacheEquivalent:toRequest:]):
+        (-[DownloadProgressTestProtocol startLoading]):
+        (-[DownloadProgressTestProtocol stopLoading]):
+        (-[DownloadProgressTestRunner init]):
+        (-[DownloadProgressTestRunner startLoadingWithProtocol:]):
+        (-[DownloadProgressTestRunner tearDown]):
+        (-[DownloadProgressTestRunner _didGetProgress:]):
+        (-[DownloadProgressTestRunner _didLoseProgress]):
+        (-[DownloadProgressTestRunner subscribeAndWaitForProgress]):
+        (-[DownloadProgressTestRunner waitToLoseProgress]):
+        (-[DownloadProgressTestRunner startDownload:expectedLength:]):
+        (-[DownloadProgressTestRunner publishProgress]):
+        (-[DownloadProgressTestRunner receiveData:]):
+        (-[DownloadProgressTestRunner finishDownloadTask]):
+        (-[DownloadProgressTestRunner failDownloadTask]):
+        (-[DownloadProgressTestRunner waitForDownloadDidCreateDestination]):
+        (-[DownloadProgressTestRunner waitForDownloadFinished]):
+        (-[DownloadProgressTestRunner waitForDownloadCanceled]):
+        (-[DownloadProgressTestRunner waitForDownloadFailed]):
+        (-[DownloadProgressTestRunner waitForUpdatedCompletedUnitCount]):
+        (-[DownloadProgressTestRunner observeValueForKeyPath:ofObject:change:context:]):
+        (-[DownloadProgressTestRunner webView:decidePolicyForNavigationResponse:decisionHandler:]):
+        (-[DownloadProgressTestRunner webView:decidePolicyForNavigationAction:decisionHandler:]):
+        (-[DownloadProgressTestRunner _downloadDidStart:]):
+        (-[DownloadProgressTestRunner _download:didCreateDestination:]):
+        (-[DownloadProgressTestRunner _downloadDidFinish:]):
+        (-[DownloadProgressTestRunner _downloadDidCancel:]):
+        (-[DownloadProgressTestRunner _download:didFailWithError:]):
+        (-[DownloadProgressTestRunner _download:decideDestinationWithSuggestedFilename:completionHandler:]):
+        (TEST):
+
 2018-11-29  Alexey Proskuryakov  <ap@apple.com>
 
         Exception in bot watcher's dashboard if a hidden platform in no longer configured for display
index ff4293f..11d6478 100644 (file)
                6354F4D11F7C3AB500D89DF3 /* ApplicationManifestParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6354F4D01F7C3AB500D89DF3 /* ApplicationManifestParser.cpp */; };
                6356FB221EC4E0BA0044BF18 /* VisibleContentRect.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6356FB211EC4E0BA0044BF18 /* VisibleContentRect.mm */; };
                636353A71E98665D0009F8AF /* GeolocationGetCurrentPositionResult.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 636353A61E9861940009F8AF /* GeolocationGetCurrentPositionResult.html */; };
+               637281A721AE1386009E0DE6 /* DownloadProgress.mm in Sources */ = {isa = PBXBuildFile; fileRef = 637281A621AE1386009E0DE6 /* DownloadProgress.mm */; };
                63A61B8B1FAD251100F06885 /* display-mode.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 63A61B8A1FAD204D00F06885 /* display-mode.html */; };
                63F668221F97F7F90032EE51 /* ApplicationManifest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 63F668201F97C3AA0032EE51 /* ApplicationManifest.mm */; };
                6B306106218A372900F5A802 /* ClosingWebView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6B306105218A372900F5A802 /* ClosingWebView.mm */; };
                6354F4D01F7C3AB500D89DF3 /* ApplicationManifestParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ApplicationManifestParser.cpp; sourceTree = "<group>"; };
                6356FB211EC4E0BA0044BF18 /* VisibleContentRect.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VisibleContentRect.mm; sourceTree = "<group>"; };
                636353A61E9861940009F8AF /* GeolocationGetCurrentPositionResult.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = GeolocationGetCurrentPositionResult.html; sourceTree = "<group>"; };
+               637281A621AE1386009E0DE6 /* DownloadProgress.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DownloadProgress.mm; sourceTree = "<group>"; };
                63A61B8A1FAD204D00F06885 /* display-mode.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "display-mode.html"; sourceTree = "<group>"; };
                63F668201F97C3AA0032EE51 /* ApplicationManifest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ApplicationManifest.mm; sourceTree = "<group>"; };
                6B306105218A372900F5A802 /* ClosingWebView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ClosingWebView.mm; sourceTree = "<group>"; };
                                518EE51620A78CDF00E024F3 /* DoubleDefersLoading.mm */,
                                518EE51720A78CDF00E024F3 /* DoubleDefersLoadingPlugin.mm */,
                                A1A4FE5D18DD3DB700B5EA8A /* Download.mm */,
+                               637281A621AE1386009E0DE6 /* DownloadProgress.mm */,
                                F46128D6211E489C00D9FADB /* DragAndDropTests.mm */,
                                A15502281E05020B00A24C57 /* DuplicateCompletionHandlerCalls.mm */,
                                F44D06461F395C4D001A0E29 /* EditorStateTests.mm */,
                                F45B63FE1F19D410009D38B9 /* ActionSheetTests.mm in Sources */,
                                37E7DD641EA06FF2009B396D /* AdditionalReadAccessAllowedURLs.mm in Sources */,
                                55A817FC218100E00004A39A /* AdditionalSupportedImageTypes.mm in Sources */,
+                               637281A721AE1386009E0DE6 /* DownloadProgress.mm in Sources */,
                                7A909A7D1D877480007E10F8 /* AffineTransform.cpp in Sources */,
                                A1DF74321C41B65800A2F4D0 /* AlwaysRevalidatedURLSchemes.mm in Sources */,
                                2DE71AFE1D49C0BD00904094 /* AnimatedResize.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/DownloadProgress.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/DownloadProgress.mm
new file mode 100644 (file)
index 0000000..da15e37
--- /dev/null
@@ -0,0 +1,580 @@
+/*
+ * 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.
+ */
+
+#import "config.h"
+
+#import "TestWKWebView.h"
+#import "Utilities.h"
+#import <Foundation/NSProgress.h>
+#import <WebCore/FileSystem.h>
+#import <WebKit/WKBrowsingContextController.h>
+#import <WebKit/WKNavigationDelegatePrivate.h>
+#import <WebKit/WKProcessPoolPrivate.h>
+#import <WebKit/WKWebViewConfiguration.h>
+#import <WebKit/_WKDownload.h>
+#import <WebKit/_WKDownloadDelegate.h>
+#import <pal/spi/cocoa/NSProgressSPI.h>
+#import <wtf/BlockPtr.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/WeakObjCPtr.h>
+
+#if WK_API_ENABLED
+
+@class DownloadProgressTestProtocol;
+
+enum class DownloadStartType {
+    ConvertLoadToDownload,
+    StartFromNavigationAction,
+    StartInProcessPool,
+};
+
+@interface DownloadProgressTestRunner : NSObject <WKNavigationDelegate, _WKDownloadDelegate>
+
+@property (nonatomic, readonly) _WKDownload *download;
+@property (nonatomic, readonly) NSProgress *progress;
+
+- (void)startLoadingWithProtocol:(DownloadProgressTestProtocol *)protocol;
+
+@end
+
+@interface DownloadProgressTestProtocol : NSURLProtocol
+@end
+
+@implementation DownloadProgressTestProtocol
+
+static DownloadProgressTestRunner *currentTestRunner;
+
++ (void)registerProtocolForTestRunner:(DownloadProgressTestRunner *)testRunner
+{
+    currentTestRunner = testRunner;
+    [NSURLProtocol registerClass:self];
+    [WKBrowsingContextController registerSchemeForCustomProtocol:@"http"];
+}
+
++ (void)unregisterProtocol
+{
+    currentTestRunner = nullptr;
+    [WKBrowsingContextController unregisterSchemeForCustomProtocol:@"http"];
+    [NSURLProtocol unregisterClass:self];
+}
+
+// MARK: NSURLProtocol Methods
+
++ (BOOL)canInitWithRequest:(NSURLRequest *)request
+{
+    return [request.URL.scheme caseInsensitiveCompare:@"http"] == NSOrderedSame;
+}
+
++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
+{
+    return request;
+}
+
++ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
+{
+    return NO;
+}
+
+- (void)startLoading
+{
+    [currentTestRunner startLoadingWithProtocol:self];
+}
+
+- (void)stopLoading
+{
+}
+
+@end
+
+static void* progressObservingContext = &progressObservingContext;
+
+@implementation DownloadProgressTestRunner {
+    RetainPtr<NSURL> m_progressURL;
+    RetainPtr<TestWKWebView> m_webView;
+    RetainPtr<id> m_progressSubscriber;
+    RetainPtr<NSProgress> m_progress;
+    RetainPtr<_WKDownload> m_download;
+    RetainPtr<DownloadProgressTestProtocol> m_protocol;
+    BlockPtr<void(void)> m_unpublishingBlock;
+    DownloadStartType m_startType;
+    NSInteger m_expectedLength;
+    bool m_hasProgress;
+    bool m_lostProgress;
+    bool m_downloadStarted;
+    bool m_downloadDidCreateDestination;
+    bool m_downloadFinished;
+    bool m_downloadCanceled;
+    bool m_downloadFailed;
+    bool m_hasUpdatedCompletedUnitCount;
+}
+
+- (instancetype)init
+{
+    self = [super init];
+
+    [DownloadProgressTestProtocol registerProtocolForTestRunner:self];
+
+    NSString *fileName = [NSString stringWithFormat:@"download-progress-%@", [NSUUID UUID].UUIDString];
+    m_progressURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName] isDirectory:NO];
+
+    currentTestRunner = self;
+
+    m_unpublishingBlock = BlockPtr<void(void)>::fromCallable([self] {
+        [self _didLoseProgress];
+    }).get();
+
+    return self;
+}
+
+- (_WKDownload *)download
+{
+    return m_download.get();
+}
+
+- (NSProgress *)progress
+{
+    return m_progress.get();
+}
+
+- (void)startLoadingWithProtocol:(DownloadProgressTestProtocol *)protocol
+{
+    m_protocol = protocol;
+
+    auto response = adoptNS([[NSURLResponse alloc] initWithURL:protocol.request.URL MIMEType:@"application/x-test-file" expectedContentLength:m_expectedLength textEncodingName:nullptr]);
+    [m_protocol.get().client URLProtocol:m_protocol.get() didReceiveResponse:response.get() cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+}
+
+- (void)tearDown
+{
+    if (m_webView) {
+        m_webView.get().configuration.processPool._downloadDelegate = nullptr;
+        [m_webView.get() removeFromSuperview];
+        m_webView = nullptr;
+    }
+
+    if (m_progressSubscriber) {
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+        [NSProgress _removeSubscriber:m_progressSubscriber.get()];
+#else
+        [NSProgress removeSubscriber:m_progressSubscriber.get()];
+#endif
+        m_progressSubscriber = nullptr;
+    }
+
+    m_progress = nullptr;
+    m_download = nullptr;
+    m_protocol = nullptr;
+    m_unpublishingBlock = nullptr;
+
+    [DownloadProgressTestProtocol unregisterProtocol];
+}
+
+- (void)_didGetProgress:(NSProgress *)progress
+{
+    ASSERT(!m_progress);
+    m_progress = progress;
+    [progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:progressObservingContext];
+    m_hasProgress = true;
+}
+
+- (void)_didLoseProgress
+{
+    ASSERT(m_progress);
+    [m_progress.get() removeObserver:self forKeyPath:@"completedUnitCount"];
+    m_progress = nullptr;
+    m_lostProgress = true;
+}
+
+- (void)subscribeAndWaitForProgress
+{
+    if (!m_progressSubscriber) {
+        auto publishingHandler = BlockPtr<NSProgressUnpublishingHandler(NSProgress *)>::fromCallable([weakSelf = WeakObjCPtr<DownloadProgressTestRunner> { self }](NSProgress *progress) {
+            if (auto strongSelf = weakSelf.get()) {
+                [strongSelf.get() _didGetProgress:progress];
+                return strongSelf->m_unpublishingBlock.get();
+            }
+            return static_cast<NSProgressUnpublishingHandler>(nil);
+        });
+
+#if USE(NSPROGRESS_PUBLISHING_SPI)
+        m_progressSubscriber = [NSProgress _addSubscriberForFileURL:m_progressURL.get() withPublishingHandler:publishingHandler.get()];
+#else
+        m_progressSubscriber = [NSProgress addSubscriberForFileURL:m_progressURL.get() withPublishingHandler:publishingHandler.get()];
+#endif
+    }
+    TestWebKitAPI::Util::run(&m_hasProgress);
+}
+
+- (void)waitToLoseProgress
+{
+    TestWebKitAPI::Util::run(&m_lostProgress);
+}
+
+- (void)startDownload:(DownloadStartType)startType expectedLength:(NSInteger)expectedLength
+{
+    m_startType = startType;
+    m_expectedLength = expectedLength;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    m_webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
+    m_webView.get().navigationDelegate = self;
+    m_webView.get().configuration.processPool._downloadDelegate = self;
+
+    auto request = adoptNS([[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://file"]]);
+
+    switch (startType) {
+    case DownloadStartType::ConvertLoadToDownload:
+    case DownloadStartType::StartFromNavigationAction:
+        [m_webView loadRequest:request.get()];
+        break;
+    case DownloadStartType::StartInProcessPool:
+        [m_webView.get().configuration.processPool _downloadURLRequest:request.get()];
+        break;
+    }
+
+    TestWebKitAPI::Util::run(&m_downloadStarted);
+}
+
+- (void)publishProgress
+{
+    ASSERT(m_download);
+
+    [m_download.get() publishProgressAtURL:m_progressURL.get()];
+}
+
+- (void)receiveData:(NSInteger)length
+{
+    auto data = adoptNS([[NSMutableData alloc] init]);
+    while (length-- > 0) {
+        const char byte = 'A';
+        [data.get() appendBytes:static_cast<const void*>(&byte) length:1];
+    }
+
+    [m_protocol.get().client URLProtocol:m_protocol.get() didLoadData:data.get()];
+}
+
+- (void)finishDownloadTask
+{
+    [m_protocol.get().client URLProtocolDidFinishLoading:m_protocol.get()];
+}
+
+- (void)failDownloadTask
+{
+    [m_protocol.get().client URLProtocol:m_protocol.get() didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorNetworkConnectionLost userInfo:nullptr]];
+}
+
+- (void)waitForDownloadDidCreateDestination
+{
+    TestWebKitAPI::Util::run(&m_downloadDidCreateDestination);
+}
+
+- (void)waitForDownloadFinished
+{
+    TestWebKitAPI::Util::run(&m_downloadFinished);
+}
+
+- (void)waitForDownloadCanceled
+{
+    TestWebKitAPI::Util::run(&m_downloadCanceled);
+}
+
+- (void)waitForDownloadFailed
+{
+    TestWebKitAPI::Util::run(&m_downloadFailed);
+}
+
+- (int64_t)waitForUpdatedCompletedUnitCount
+{
+    TestWebKitAPI::Util::run(&m_hasUpdatedCompletedUnitCount);
+    m_hasUpdatedCompletedUnitCount = false;
+
+    return m_progress.get().completedUnitCount;
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
+{
+    if (context == progressObservingContext) {
+        EXPECT_EQ(object, m_progress.get());
+        EXPECT_STREQ(keyPath.UTF8String, "completedUnitCount");
+        m_hasUpdatedCompletedUnitCount = true;
+    } else
+        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+}
+
+// MARK: <WKNavigationDelegate> Methods
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
+{
+    if (m_startType == DownloadStartType::ConvertLoadToDownload)
+        decisionHandler(_WKNavigationResponsePolicyBecomeDownload);
+    else
+        decisionHandler(WKNavigationResponsePolicyAllow);
+}
+
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+    if (m_startType == DownloadStartType::StartFromNavigationAction)
+        decisionHandler(_WKNavigationActionPolicyDownload);
+    else
+        decisionHandler(WKNavigationActionPolicyAllow);
+}
+
+// MARK: <_WKDownloadDelegate> Methods
+
+- (void)_downloadDidStart:(_WKDownload *)download
+{
+    ASSERT(!m_downloadStarted);
+    ASSERT(!m_download);
+
+    m_download = download;
+    m_downloadStarted = true;
+}
+
+- (void)_download:(_WKDownload *)download didCreateDestination:(NSString *)destination
+{
+    EXPECT_EQ(download, m_download.get());
+    m_downloadDidCreateDestination = true;
+}
+
+- (void)_downloadDidFinish:(_WKDownload *)download
+{
+    EXPECT_EQ(download, m_download.get());
+    m_downloadFinished = true;
+}
+
+- (void)_downloadDidCancel:(_WKDownload *)download
+{
+    EXPECT_EQ(download, m_download.get());
+    m_downloadCanceled = true;
+}
+
+- (void)_download:(_WKDownload *)download didFailWithError:(NSError *)error
+{
+    EXPECT_EQ(download, m_download.get());
+    m_downloadFailed = true;
+}
+
+- (void)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename completionHandler:(void (^)(BOOL allowOverwrite, NSString *destination))completionHandler
+{
+    EXPECT_EQ(download, m_download.get());
+
+    WebCore::FileSystem::PlatformFileHandle fileHandle;
+    RetainPtr<NSString *> path = (NSString *)WebCore::FileSystem::openTemporaryFile("TestWebKitAPI", fileHandle);
+    EXPECT_TRUE(fileHandle != WebCore::FileSystem::invalidPlatformFileHandle);
+    WebCore::FileSystem::closeFile(fileHandle);
+
+    completionHandler(YES, path.get());
+}
+
+@end
+
+// End-to-end test of subscribing to progress on a successful download. The client
+// should be able to receive an NSProgress that is updated as the download makes
+// progress, and the NSProgress should be unpublished when the download finishes.
+TEST(DownloadProgress, BasicSubscriptionAndProgressUpdates)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:100];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+
+    [testRunner.get() receiveData:50];
+    EXPECT_EQ([testRunner.get() waitForUpdatedCompletedUnitCount], 50);
+    EXPECT_EQ(testRunner.get().progress.fractionCompleted, .5);
+
+    [testRunner.get() receiveData:50];
+    EXPECT_EQ([testRunner.get() waitForUpdatedCompletedUnitCount], 100);
+    EXPECT_EQ(testRunner.get().progress.fractionCompleted, 1);
+
+    [testRunner.get() finishDownloadTask];
+    [testRunner.get() waitForDownloadFinished];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// Similar test as before, but initiating the download before receiving its response.
+TEST(DownloadProgress, StartDownloadFromNavigationAction)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::StartFromNavigationAction expectedLength:100];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+    [testRunner.get() receiveData:100];
+    [testRunner.get() finishDownloadTask];
+    [testRunner.get() waitForDownloadFinished];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+TEST(DownloadProgress, StartDownloadInProcessPool)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::StartInProcessPool expectedLength:100];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+    [testRunner.get() receiveData:100];
+    [testRunner.get() finishDownloadTask];
+    [testRunner.get() waitForDownloadFinished];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// If the download is canceled, the progress should be unpublished.
+TEST(DownloadProgress, LoseProgressWhenDownloadIsCanceled)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:100];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+    [testRunner.get() receiveData:50];
+    [testRunner.get().download cancel];
+    [testRunner.get() waitForDownloadCanceled];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// If the download fails, the progress should be unpublished.
+TEST(DownloadProgress, LoseProgressWhenDownloadFails)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:100];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+    [testRunner.get() receiveData:50];
+    [testRunner.get() failDownloadTask];
+    [testRunner.get() waitForDownloadFailed];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// Canceling the progress should cancel the download.
+TEST(DownloadProgress, CancelDownloadWhenProgressIsCanceled)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:100];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+    [testRunner.get() receiveData:50];
+    [testRunner.get().progress cancel];
+    [testRunner.get() waitForDownloadCanceled];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// Publishing progress on a download after it has finished should be a safe no-op.
+TEST(DownloadProgress, PublishProgressAfterDownloadFinished)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:100];
+    [testRunner.get() receiveData:100];
+    [testRunner.get() finishDownloadTask];
+    [testRunner.get() waitForDownloadFinished];
+    [testRunner.get() publishProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// Test the behavior of a download of unknown length.
+TEST(DownloadProgress, IndeterminateDownloadSize)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:NSURLResponseUnknownLength];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+    EXPECT_EQ(testRunner.get().progress.totalUnitCount, -1);
+
+    [testRunner.get() receiveData:50];
+    EXPECT_EQ([testRunner.get() waitForUpdatedCompletedUnitCount], 50);
+    EXPECT_EQ(testRunner.get().progress.fractionCompleted, 0);
+    EXPECT_EQ(testRunner.get().progress.totalUnitCount, -1);
+
+    [testRunner.get() finishDownloadTask];
+    [testRunner.get() waitForDownloadFinished];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// Test the behavior when a download continues returning data beyond its expected length.
+TEST(DownloadProgress, ExtraData)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:100];
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+
+    [testRunner.get() receiveData:150];
+    EXPECT_EQ([testRunner.get() waitForUpdatedCompletedUnitCount], 150);
+    EXPECT_EQ(testRunner.get().progress.fractionCompleted, 1.5);
+
+    [testRunner.get() finishDownloadTask];
+    [testRunner.get() waitForDownloadFinished];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+// Clients should be able to publish progress on a download that has already started.
+TEST(DownloadProgress, PublishProgressOnPartialDownload)
+{
+    auto testRunner = adoptNS([[DownloadProgressTestRunner alloc] init]);
+
+    [testRunner.get() startDownload:DownloadStartType::ConvertLoadToDownload expectedLength:100];
+    [testRunner.get() receiveData:50];
+
+    // Ensure that the the data task has become a download task so that we test
+    // telling a live Download, not a PendingDownload, to publish its progress.
+    [testRunner.get() waitForDownloadDidCreateDestination];
+
+    [testRunner.get() publishProgress];
+    [testRunner.get() subscribeAndWaitForProgress];
+    EXPECT_EQ(testRunner.get().progress.completedUnitCount, 50);
+
+    [testRunner.get() receiveData:50];
+    EXPECT_EQ([testRunner.get() waitForUpdatedCompletedUnitCount], 100);
+
+    [testRunner.get() finishDownloadTask];
+    [testRunner.get() waitForDownloadFinished];
+    [testRunner.get() waitToLoseProgress];
+
+    [testRunner.get() tearDown];
+}
+
+#endif // WK_API_ENABLED