Title: [258096] trunk/Source/WebKit
Revision
258096
Author
beid...@apple.com
Date
2020-03-07 20:19:37 -0800 (Sat, 07 Mar 2020)

Log Message

Make PDF range requests to the network.
https://bugs.webkit.org/show_bug.cgi?id=208776

Reviewed by Alex Christensen.

We already handle a set of range requests from our data provider callbacks.
The current strategy is "wait until we've streamed enough of the entire resource,
perform that request's callback"

For linearized PDFs the second request ever made is for the very end of the PDF document,
so we have to wait for the entire document to load (which is what we already do in non-incremental mode)

Now, we'll actually make the requests for missing data chunks from the network by using the document's request
and adding range headers.

While it is now entirely misnamed, NetscapePlugInStreamLoader is a perfect fit for this.

Once the range request completes from the network we can unblock the PDF thread and go to the next request.

* WebProcess/Plugins/PDF/PDFPlugin.h:
* WebProcess/Plugins/PDF/PDFPlugin.mm:
(WebKit::PDFPlugin::unconditionalCompleteOutstandingRangeRequests):
(WebKit::PDFPlugin::getResourceBytesAtPosition):
(WebKit::PDFPlugin::ByteRangeRequest::clearStreamLoader):
(WebKit::PDFPlugin::ByteRangeRequest::completeWithBytes):
(WebKit::PDFPlugin::ByteRangeRequest::completeWithAccumulatedData):
(WebKit::PDFPlugin::ByteRangeRequest::maybeComplete):
(WebKit::PDFPlugin::ByteRangeRequest::completeUnconditionally):
(WebKit::PDFPlugin::willSendRequest):
(WebKit::PDFPlugin::didReceiveResponse):
(WebKit::PDFPlugin::didReceiveData):
(WebKit::PDFPlugin::didFail):
(WebKit::PDFPlugin::didFinishLoading):
(WebKit::PDFPlugin::byteRangeRequestForLoader):
(WebKit::PDFPlugin::cancelAndForgetLoader):
(WebKit::PDFPlugin::manualStreamDidReceiveData):
(WebKit::PDFPlugin::unconditionalCompleteRangeRequest): Deleted.

Modified Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (258095 => 258096)


--- trunk/Source/WebKit/ChangeLog	2020-03-08 04:01:00 UTC (rev 258095)
+++ trunk/Source/WebKit/ChangeLog	2020-03-08 04:19:37 UTC (rev 258096)
@@ -1,3 +1,43 @@
+2020-03-07  Brady Eidson  <beid...@apple.com>
+
+        Make PDF range requests to the network.
+        https://bugs.webkit.org/show_bug.cgi?id=208776
+
+        Reviewed by Alex Christensen.
+
+        We already handle a set of range requests from our data provider callbacks.
+        The current strategy is "wait until we've streamed enough of the entire resource, 
+        perform that request's callback"
+        
+        For linearized PDFs the second request ever made is for the very end of the PDF document,
+        so we have to wait for the entire document to load (which is what we already do in non-incremental mode)
+        
+        Now, we'll actually make the requests for missing data chunks from the network by using the document's request
+        and adding range headers.
+        
+        While it is now entirely misnamed, NetscapePlugInStreamLoader is a perfect fit for this.
+        
+        Once the range request completes from the network we can unblock the PDF thread and go to the next request.
+
+        * WebProcess/Plugins/PDF/PDFPlugin.h:
+        * WebProcess/Plugins/PDF/PDFPlugin.mm:
+        (WebKit::PDFPlugin::unconditionalCompleteOutstandingRangeRequests):
+        (WebKit::PDFPlugin::getResourceBytesAtPosition):
+        (WebKit::PDFPlugin::ByteRangeRequest::clearStreamLoader):
+        (WebKit::PDFPlugin::ByteRangeRequest::completeWithBytes):
+        (WebKit::PDFPlugin::ByteRangeRequest::completeWithAccumulatedData):
+        (WebKit::PDFPlugin::ByteRangeRequest::maybeComplete):
+        (WebKit::PDFPlugin::ByteRangeRequest::completeUnconditionally):
+        (WebKit::PDFPlugin::willSendRequest):
+        (WebKit::PDFPlugin::didReceiveResponse):
+        (WebKit::PDFPlugin::didReceiveData):
+        (WebKit::PDFPlugin::didFail):
+        (WebKit::PDFPlugin::didFinishLoading):
+        (WebKit::PDFPlugin::byteRangeRequestForLoader):
+        (WebKit::PDFPlugin::cancelAndForgetLoader):
+        (WebKit::PDFPlugin::manualStreamDidReceiveData):
+        (WebKit::PDFPlugin::unconditionalCompleteRangeRequest): Deleted.
+
 2020-03-07  David Quesada  <david_ques...@apple.com>
 
         Crash in -[_WKRemoteObjectRegistry _invokeMethod:] block when calling a reply block after the web view has deallocated

Modified: trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.h (258095 => 258096)


--- trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.h	2020-03-08 04:01:00 UTC (rev 258095)
+++ trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.h	2020-03-08 04:19:37 UTC (rev 258096)
@@ -34,7 +34,10 @@
 #include <WebCore/AXObjectCache.h>
 #include <WebCore/AffineTransform.h>
 #include <WebCore/FindOptions.h>
+#include <WebCore/NetscapePlugInStreamLoader.h>
 #include <WebCore/ScrollableArea.h>
+#include <wtf/HashMap.h>
+#include <wtf/Identified.h>
 #include <wtf/RetainPtr.h>
 #include <wtf/Threading.h>
 
@@ -69,7 +72,11 @@
 class PluginView;
 class WebFrame;
 
-class PDFPlugin final : public Plugin, private WebCore::ScrollableArea {
+class PDFPlugin final : public Plugin, private WebCore::ScrollableArea
+#if HAVE(INCREMENTAL_PDF_APIS)
+    , private WebCore::NetscapePlugInStreamLoaderClient
+#endif
+{
 public:
     static Ref<PDFPlugin> create(WebFrame&);
     ~PDFPlugin();
@@ -316,17 +323,50 @@
     void threadEntry(Ref<PDFPlugin>&&);
     void adoptBackgroundThreadDocument();
 
-    struct ByteRangeRequest {
-        off_t position { 0 };
-        size_t count { 0 };
-        CompletionHandler<void(const uint8_t*, size_t count)> completionHandler;
+    // WebCore::NetscapePlugInStreamLoaderClient
+    void willSendRequest(WebCore::NetscapePlugInStreamLoader*, WebCore::ResourceRequest&&, const WebCore::ResourceResponse& redirectResponse, CompletionHandler<void(WebCore::ResourceRequest&&)>&&) final;
+    void didReceiveResponse(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceResponse&) final;
+    void didReceiveData(WebCore::NetscapePlugInStreamLoader*, const char*, int) final;
+    void didFail(WebCore::NetscapePlugInStreamLoader*, const WebCore::ResourceError&) final;
+    void didFinishLoading(WebCore::NetscapePlugInStreamLoader*) final;
+
+    class ByteRangeRequest : public Identified<ByteRangeRequest> {
+    public:
+        ByteRangeRequest() = default;
+        ByteRangeRequest(uint64_t position, size_t count, CompletionHandler<void(const uint8_t*, size_t count)>&& completionHandler)
+            : m_position(position)
+            , m_count(count)
+            , m_completionHandler(WTFMove(completionHandler))
+        {
+        }
+
+        WebCore::NetscapePlugInStreamLoader* streamLoader() { return m_streamLoader; }
+        void setStreamLoader(WebCore::NetscapePlugInStreamLoader* loader) { m_streamLoader = loader; }
+        void clearStreamLoader();
+        void addData(const uint8_t* data, size_t count) { m_accumulatedData.append(data, count); }
+
+        void completeWithBytes(const uint8_t*, size_t);
+        void completeWithAccumulatedData();
+
+        bool maybeComplete(PDFPlugin&);
+        void completeUnconditionally(PDFPlugin&);
+
+    private:
+        uint64_t m_position { 0 };
+        size_t m_count { 0 };
+        CompletionHandler<void(const uint8_t*, size_t count)> m_completionHandler;
+        Vector<uint8_t> m_accumulatedData;
+        WebCore::NetscapePlugInStreamLoader* m_streamLoader { nullptr };
     };
-    void unconditionalCompleteRangeRequest(ByteRangeRequest&);
     void unconditionalCompleteOutstandingRangeRequests();
 
+    ByteRangeRequest* byteRangeRequestForLoader(WebCore::NetscapePlugInStreamLoader&);
+    void cancelAndForgetLoader(WebCore::NetscapePlugInStreamLoader&);
+
     RetainPtr<PDFDocument> m_backgroundThreadDocument;
     RefPtr<Thread> m_pdfThread;
-    Vector<ByteRangeRequest> m_outstandingByteRangeRequests;
+    HashMap<uint64_t, ByteRangeRequest> m_outstandingByteRangeRequests;
+    HashMap<RefPtr<WebCore::NetscapePlugInStreamLoader>, uint64_t> m_streamLoaderMap;
     bool m_incrementalPDFLoadingEnabled;
 #endif
 };

Modified: trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm (258095 => 258096)


--- trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm	2020-03-08 04:01:00 UTC (rev 258095)
+++ trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm	2020-03-08 04:19:37 UTC (rev 258096)
@@ -43,6 +43,7 @@
 #import "WebEvent.h"
 #import "WebEventConversion.h"
 #import "WebFindOptions.h"
+#import "WebLoaderStrategy.h"
 #import "WebPage.h"
 #import "WebPageProxyMessages.h"
 #import "WebPasteboardProxyMessages.h"
@@ -91,7 +92,6 @@
 #import <wtf/UUID.h>
 #import <wtf/WTFSemaphore.h>
 #import <wtf/WorkQueue.h>
-#import <wtf/text/TextStream.h>
 
 #if HAVE(INCREMENTAL_PDF_APIS)
 @interface PDFDocument ()
@@ -735,41 +735,50 @@
 
 void PDFPlugin::unconditionalCompleteOutstandingRangeRequests()
 {
-    for (auto& request : m_outstandingByteRangeRequests)
-        unconditionalCompleteRangeRequest(request);
+    for (auto& request : m_outstandingByteRangeRequests.values())
+        request.completeUnconditionally(*this);
     m_outstandingByteRangeRequests.clear();
 }
 
-void PDFPlugin::unconditionalCompleteRangeRequest(ByteRangeRequest& request)
-{
-    if (static_cast<uint64_t>(request.position) >= m_streamedBytes) {
-        request.completionHandler(nullptr, 0);
-        return;
-    }
-
-    ASSERT(m_data);
-
-    auto count = request.position + request.count > m_streamedBytes ? m_streamedBytes - request.position : request.count;
-    request.completionHandler(CFDataGetBytePtr(m_data.get()) + request.position, count);
-}
-
 void PDFPlugin::getResourceBytesAtPosition(size_t count, off_t position, CompletionHandler<void(const uint8_t*, size_t)>&& completionHandler)
 {
     ASSERT(isMainThread()); 
+    ASSERT(position >= 0);
 
-    if (m_streamedBytes >= position + count) {
-        ASSERT(m_data);
-        completionHandler(CFDataGetBytePtr(m_data.get()) + position, count);
+    ByteRangeRequest request = { static_cast<uint64_t>(position), count, WTFMove(completionHandler) };
+    if (request.maybeComplete(*this))
         return;
-    }
 
-    ByteRangeRequest request = { position, count, WTFMove(completionHandler) };
     if (m_documentFinishedLoading) {
-        unconditionalCompleteRangeRequest(request);
+        request.completeUnconditionally(*this);
         return;
     }
 
-    m_outstandingByteRangeRequests.append(WTFMove(request));
+    auto identifier = request.identifier();
+    m_outstandingByteRangeRequests.set(identifier, WTFMove(request));
+
+    auto* coreFrame = m_frame.coreFrame();
+    if (!coreFrame)
+        return;
+
+    auto* documentLoader = coreFrame->loader().documentLoader();
+    if (!documentLoader)
+        return;
+
+    auto resourceRequest = documentLoader->request();
+    resourceRequest.setHTTPHeaderField(HTTPHeaderName::Range, makeString("bytes="_s, position, "-"_s, position + count - 1));
+    resourceRequest.setCachePolicy(ResourceRequestCachePolicy::DoNotUseAnyCache);
+
+    LOG(PDF, "Scheduling a stream loader for request %llu (%lu bytes at %llu)\n", identifier, count, position);
+
+    WebProcess::singleton().webLoaderStrategy().schedulePluginStreamLoad(*coreFrame, *this, WTFMove(resourceRequest), [this, protectedThis = makeRef(*this), identifier] (RefPtr<WebCore::NetscapePlugInStreamLoader>&& loader) {
+        auto iterator = m_outstandingByteRangeRequests.find(identifier);
+        if (iterator == m_outstandingByteRangeRequests.end())
+            return;
+
+        iterator->value.setStreamLoader(loader.get());
+        m_streamLoaderMap.set(WTFMove(loader), identifier);
+    });
 }
 
 void PDFPlugin::adoptBackgroundThreadDocument()
@@ -792,6 +801,134 @@
 
     installPDFDocument();
 }
+
+void PDFPlugin::ByteRangeRequest::clearStreamLoader()
+{
+    ASSERT(m_streamLoader);
+    m_streamLoader = nullptr;
+    m_accumulatedData.clear();
+}
+
+void PDFPlugin::ByteRangeRequest::completeWithBytes(const uint8_t* data, size_t count)
+{
+    LOG(PDF, "Completing range request %llu (%lu bytes at %llu) with %lu bytes from the main PDF buffer", identifier(), m_count, m_position, count);
+    m_completionHandler(data, count);
+}
+
+void PDFPlugin::ByteRangeRequest::completeWithAccumulatedData()
+{
+    LOG(PDF, "Completing range request %llu (%lu bytes at %llu) with %lu bytes from the network", identifier(), m_count, m_position, m_accumulatedData.size());
+    m_completionHandler(m_accumulatedData.data(), m_accumulatedData.size());
+}
+
+bool PDFPlugin::ByteRangeRequest::maybeComplete(PDFPlugin& plugin)
+{
+    if (plugin.m_streamedBytes >= m_position + m_count) {
+        completeWithBytes(CFDataGetBytePtr(plugin.m_data.get()) + m_position, m_count);
+        return true;
+    }
+    return false;
+}
+
+void PDFPlugin::ByteRangeRequest::completeUnconditionally(PDFPlugin& plugin)
+{
+    if (m_position >= plugin.m_streamedBytes) {
+        completeWithBytes(nullptr, 0);
+        return;
+    }
+
+    ASSERT(plugin.m_data);
+
+    auto count = m_position + m_count > plugin.m_streamedBytes ? plugin.m_streamedBytes - m_position : m_count;
+    completeWithBytes(CFDataGetBytePtr(plugin.m_data.get()) + m_position, count);
+}
+
+void PDFPlugin::willSendRequest(NetscapePlugInStreamLoader* loader, ResourceRequest&&, const ResourceResponse&, CompletionHandler<void(ResourceRequest&&)>&&)
+{
+    // Redirections for range requests are unexpected.
+    cancelAndForgetLoader(*loader);
+}
+
+void PDFPlugin::didReceiveResponse(NetscapePlugInStreamLoader* loader, const ResourceResponse& response)
+{
+    auto* request = byteRangeRequestForLoader(*loader);
+    if (!request)
+        cancelAndForgetLoader(*loader);
+
+    ASSERT(request->streamLoader() == loader);
+
+    // Range success! We'll expect to receive the data in future didReceiveData callbacks.
+    if (response.httpStatusCode() == 206)
+        return;
+
+    // If the response wasn't a successful range response, we don't need this stream loader anymore.
+    // This can happen, for example, if the server doesn't support range requests.
+    // We'll still resolve the ByteRangeRequest later once enough of the full resource has loaded.
+    cancelAndForgetLoader(*loader);
+
+    // The server might support range requests and explicitly told us this range was not satisfiable.
+    // In this case, we can reject the ByteRangeRequest right away.
+    if (response.httpStatusCode() == 416 && request) {
+        request->completeWithAccumulatedData();
+        m_outstandingByteRangeRequests.remove(request->identifier());
+    }
+}
+
+void PDFPlugin::didReceiveData(NetscapePlugInStreamLoader* loader, const char* data, int count)
+{
+    auto* request = byteRangeRequestForLoader(*loader);
+    if (!request)
+        return;
+
+    request->addData(reinterpret_cast<const uint8_t*>(data), count);
+}
+
+void PDFPlugin::didFail(NetscapePlugInStreamLoader* loader, const ResourceError&)
+{
+    if (m_documentFinishedLoading) {
+        auto identifier = m_streamLoaderMap.get(loader);
+        if (identifier)
+            m_outstandingByteRangeRequests.remove(identifier);
+    }
+
+    cancelAndForgetLoader(*loader);
+}
+
+void PDFPlugin::didFinishLoading(NetscapePlugInStreamLoader* loader)
+{
+    auto* request = byteRangeRequestForLoader(*loader);
+    if (!request)
+        return;
+
+    request->completeWithAccumulatedData();
+    m_outstandingByteRangeRequests.remove(request->identifier());
+}
+
+PDFPlugin::ByteRangeRequest* PDFPlugin::byteRangeRequestForLoader(NetscapePlugInStreamLoader& loader)
+{
+    uint64_t identifier = m_streamLoaderMap.get(&loader);
+    if (!identifier)
+        return nullptr;
+
+    auto request = m_outstandingByteRangeRequests.find(identifier);
+    if (request == m_outstandingByteRangeRequests.end())
+        return nullptr;
+
+    return &(request->value);
+}
+
+void PDFPlugin::cancelAndForgetLoader(NetscapePlugInStreamLoader& loader)
+{
+    if (auto* request = byteRangeRequestForLoader(loader)) {
+        if (request->streamLoader()) {
+            ASSERT(request->streamLoader() == &loader);
+            request->clearStreamLoader();
+        }
+    }
+
+    loader.cancel(loader.cancelledError());
+    m_streamLoaderMap.remove(&loader);
+}
 #endif // HAVE(INCREMENTAL_PDF_APIS)
 
 PluginInfo PDFPlugin::pluginInfo()
@@ -1235,16 +1372,17 @@
 
 #if HAVE(INCREMENTAL_PDF_APIS)
     if (m_incrementalPDFLoadingEnabled) {
-        size_t index = 0;
-        while (index < m_outstandingByteRangeRequests.size()) {
-            auto& request = m_outstandingByteRangeRequests[index];
-            if (m_streamedBytes >= request.position + request.count) {
-                request.completionHandler(CFDataGetBytePtr(m_data.get()) + request.position, request.count);
-                m_outstandingByteRangeRequests.remove(index);
-                continue;
-            }
-            ++index;
+        HashSet<uint64_t> handledRequests;
+        for (auto& request : m_outstandingByteRangeRequests.values()) {
+            if (request.maybeComplete(*this))
+                handledRequests.add(request.identifier());
         }
+
+        for (auto identifier : handledRequests) {
+            auto request = m_outstandingByteRangeRequests.take(identifier);
+            if (request.streamLoader())
+                cancelAndForgetLoader(*request.streamLoader());
+        }
     }
 #endif
 }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to