Title: [257900] trunk/Source
Revision
257900
Author
beid...@apple.com
Date
2020-03-04 21:26:14 -0800 (Wed, 04 Mar 2020)

Log Message

Lay initial groundwork for new PDF loading model
https://bugs.webkit.org/show_bug.cgi?id=208599

Reviewed by Alex Christensen.

Source/WebKit:

First piece of adopting some new platform PDF APIs.
Disabled by default even on platforms that support it.
No behavior change for shipping configs.

* WebProcess/Plugins/PDF/PDFPlugin.h:
* WebProcess/Plugins/PDF/PDFPlugin.mm:
(WebKit::PDFPlugin::PDFPlugin):
(WebKit::dataProviderGetBytesAtPositionCallback):
(WebKit::dataProviderGetByteRangesCallback):
(WebKit::dataProviderReleaseInfoCallback):
(WebKit::PDFPlugin::threadEntry):
(WebKit::PDFPlugin::unconditionalCompleteRangeRequest):
(WebKit::PDFPlugin::getResourceBytesAtPosition):
(WebKit::PDFPlugin::adoptBackgroundThreadDocument):
(WebKit::PDFPlugin::pdfDocumentDidLoad):
(WebKit::PDFPlugin::installPDFDocument):
(WebKit::PDFPlugin::manualStreamDidReceiveData):
(WebKit::PDFPlugin::attemptToUnlockPDF):
(WebKit::PDFPlugin::calculateSizes):
(WebKit::PDFPlugin::handleMouseEvent):
(WebKit::PDFPlugin::saveToPDF):
(WebKit::PDFPlugin::openWithNativeApplication):
(WebKit::PDFPlugin::countFindMatches):
(WebKit::PDFPlugin::nextMatchForString):

Source/WTF:

* wtf/PlatformHave.h:

Modified Paths

Diff

Modified: trunk/Source/WTF/ChangeLog (257899 => 257900)


--- trunk/Source/WTF/ChangeLog	2020-03-05 03:46:21 UTC (rev 257899)
+++ trunk/Source/WTF/ChangeLog	2020-03-05 05:26:14 UTC (rev 257900)
@@ -1,3 +1,12 @@
+2020-03-04  Brady Eidson  <beid...@apple.com>
+
+        Lay initial groundwork for new PDF loading model
+        https://bugs.webkit.org/show_bug.cgi?id=208599
+
+        Reviewed by Alex Christensen.
+
+        * wtf/PlatformHave.h:
+
 2020-03-04  Wenson Hsieh  <wenson_hs...@apple.com>
 
         Add system trace points around display list replay

Modified: trunk/Source/WTF/wtf/PlatformHave.h (257899 => 257900)


--- trunk/Source/WTF/wtf/PlatformHave.h	2020-03-05 03:46:21 UTC (rev 257899)
+++ trunk/Source/WTF/wtf/PlatformHave.h	2020-03-05 05:26:14 UTC (rev 257900)
@@ -601,3 +601,7 @@
 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101600) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 140000)
 #define HAVE_WEBP 1
 #endif
+
+#if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101600)
+#define HAVE_INCREMENTAL_PDF_APIS 1
+#endif

Modified: trunk/Source/WebKit/ChangeLog (257899 => 257900)


--- trunk/Source/WebKit/ChangeLog	2020-03-05 03:46:21 UTC (rev 257899)
+++ trunk/Source/WebKit/ChangeLog	2020-03-05 05:26:14 UTC (rev 257900)
@@ -1,3 +1,35 @@
+2020-03-04  Brady Eidson  <beid...@apple.com>
+
+        Lay initial groundwork for new PDF loading model
+        https://bugs.webkit.org/show_bug.cgi?id=208599
+
+        Reviewed by Alex Christensen.
+
+        First piece of adopting some new platform PDF APIs.
+        Disabled by default even on platforms that support it.
+        No behavior change for shipping configs.
+
+        * WebProcess/Plugins/PDF/PDFPlugin.h:
+        * WebProcess/Plugins/PDF/PDFPlugin.mm:
+        (WebKit::PDFPlugin::PDFPlugin):
+        (WebKit::dataProviderGetBytesAtPositionCallback):
+        (WebKit::dataProviderGetByteRangesCallback):
+        (WebKit::dataProviderReleaseInfoCallback):
+        (WebKit::PDFPlugin::threadEntry):
+        (WebKit::PDFPlugin::unconditionalCompleteRangeRequest):
+        (WebKit::PDFPlugin::getResourceBytesAtPosition):
+        (WebKit::PDFPlugin::adoptBackgroundThreadDocument):
+        (WebKit::PDFPlugin::pdfDocumentDidLoad):
+        (WebKit::PDFPlugin::installPDFDocument):
+        (WebKit::PDFPlugin::manualStreamDidReceiveData):
+        (WebKit::PDFPlugin::attemptToUnlockPDF):
+        (WebKit::PDFPlugin::calculateSizes):
+        (WebKit::PDFPlugin::handleMouseEvent):
+        (WebKit::PDFPlugin::saveToPDF):
+        (WebKit::PDFPlugin::openWithNativeApplication):
+        (WebKit::PDFPlugin::countFindMatches):
+        (WebKit::PDFPlugin::nextMatchForString):
+
 2020-03-04  Chris Dumez  <cdu...@apple.com>
 
         Construct fewer unnecessary temporary WebProcessPool objects in WebsiteDataStore implementation

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


--- trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.h	2020-03-05 03:46:21 UTC (rev 257899)
+++ trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.h	2020-03-05 05:26:14 UTC (rev 257900)
@@ -36,7 +36,14 @@
 #include <WebCore/FindOptions.h>
 #include <WebCore/ScrollableArea.h>
 #include <wtf/RetainPtr.h>
+#include <wtf/Threading.h>
 
+// For now, disable new PDF APIs by default even on platforms where otherwise enabled.
+// FIXME: Enable this when ready.
+#ifdef HAVE_INCREMENTAL_PDF_APIS
+#undef HAVE_INCREMENTAL_PDF_APIS
+#endif
+
 typedef const struct OpaqueJSContext* JSContextRef;
 typedef struct OpaqueJSValue* JSObjectRef;
 typedef const struct OpaqueJSValue* JSValueRef;
@@ -116,6 +123,10 @@
     PDFPluginAnnotation* activeAnnotation() const { return m_activeAnnotation.get(); }
     WebCore::AXObjectCache* axObjectCache() const;
 
+#if HAVE(INCREMENTAL_PDF_APIS)
+    void getResourceBytesAtPosition(size_t count, off_t position, CompletionHandler<void(const uint8_t*, size_t count)>&&);
+#endif
+
 private:
     explicit PDFPlugin(WebFrame&);
 
@@ -225,6 +236,7 @@
     Ref<WebCore::Scrollbar> createScrollbar(WebCore::ScrollbarOrientation);
     void destroyScrollbar(WebCore::ScrollbarOrientation);
     void pdfDocumentDidLoad();
+    void installPDFDocument();
     void addArchiveResource();
     void calculateSizes();
     void runScriptsInPDFDocument();
@@ -241,9 +253,6 @@
 
     void createPasswordEntryForm();
 
-    RetainPtr<PDFDocument> pdfDocument() const { return m_pdfDocument; }
-    void setPDFDocument(RetainPtr<PDFDocument> document) { m_pdfDocument = document; }
-
     WebCore::IntSize pdfDocumentSize() const { return m_pdfDocumentSize; }
     void setPDFDocumentSize(WebCore::IntSize size) { m_pdfDocumentSize = size; }
 
@@ -302,11 +311,30 @@
     RetainPtr<CFMutableDataRef> m_data;
 
     RetainPtr<PDFDocument> m_pdfDocument;
+
+    bool m_documentFinishedLoading { false };
     unsigned m_firstPageHeight { 0 };
     WebCore::IntSize m_pdfDocumentSize; // All pages, including gaps.
 
     RefPtr<WebCore::Scrollbar> m_horizontalScrollbar;
     RefPtr<WebCore::Scrollbar> m_verticalScrollbar;
+
+#if HAVE(INCREMENTAL_PDF_APIS)
+    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;
+    };
+    void unconditionalCompleteRangeRequest(ByteRangeRequest&);
+    void unconditionalCompleteOutstandingRangeRequests();
+
+    RetainPtr<PDFDocument> m_backgroundThreadDocument;
+    RefPtr<Thread> m_pdfThread;
+    Vector<ByteRangeRequest> m_outstandingByteRangeRequests;
+#endif
 };
 
 } // namespace WebKit

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


--- trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm	2020-03-05 03:46:21 UTC (rev 257899)
+++ trunk/Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm	2020-03-05 05:26:14 UTC (rev 257900)
@@ -87,7 +87,14 @@
 #import <pal/spi/cg/CoreGraphicsSPI.h>
 #import <pal/spi/mac/NSMenuSPI.h>
 #import <wtf/UUID.h>
+#import <wtf/WTFSemaphore.h>
 
+#if HAVE(INCREMENTAL_PDF_APIS)
+@interface PDFDocument ()
+-(instancetype) initWithProvider:(CGDataProviderRef)dataProvider;
+@end
+#endif
+
 // Set overflow: hidden on the annotation container so <input> elements scrolled out of view don't show
 // scrollbars on the body. We can't add annotations directly to the body, because overflow: hidden on the body
 // will break rubber-banding.
@@ -574,6 +581,9 @@
     , m_scrollCornerLayer(adoptNS([[WKPDFPluginScrollbarLayer alloc] initWithPDFPlugin:this]))
     , m_pdfLayerController(adoptNS([[pdfLayerControllerClass() alloc] init]))
     , m_pdfLayerControllerDelegate(adoptNS([[WKPDFLayerControllerDelegate alloc] initWithPDFPlugin:this]))
+#if HAVE(INCREMENTAL_PDF_APIS)
+    , m_pdfThread(Thread::create("PDF document thread", [protectedThis = makeRef(*this), this] () mutable { threadEntry(WTFMove(protectedThis)); }))
+#endif
 {
     m_pdfLayerController.get().delegate = m_pdfLayerControllerDelegate.get();
     m_pdfLayerController.get().parentLayer = m_contentLayer.get();
@@ -608,6 +618,145 @@
 {
 }
 
+#if HAVE(INCREMENTAL_PDF_APIS)
+static size_t dataProviderGetBytesAtPositionCallback(void* info, void* buffer, off_t position, size_t count)
+{
+    ASSERT(!isMainThread());
+
+    Ref<PDFPlugin> plugin = *((PDFPlugin*)info);
+    WTF::Semaphore dataSemaphore { 0 };
+    size_t bytesProvided = 0;
+
+    RunLoop::main().dispatch([plugin = WTFMove(plugin), position, count, buffer, &dataSemaphore, &bytesProvided] {
+        plugin->getResourceBytesAtPosition(count, position, [count, buffer, &dataSemaphore, &bytesProvided](const uint8_t* bytes, size_t bytesCount) {
+            ASSERT_UNUSED(count, bytesCount <= count);
+            memcpy(buffer, bytes, bytesCount);
+            bytesProvided = bytesCount;
+            dataSemaphore.signal();
+        });
+    });
+
+    dataSemaphore.wait();
+    return bytesProvided;
+}
+
+static void dataProviderGetByteRangesCallback(void* info, CFMutableArrayRef buffers, const CFRange* ranges, size_t count)
+{
+    ASSERT(!isMainThread());
+
+    Ref<PDFPlugin> plugin = *((PDFPlugin*)info);
+    WTF::Semaphore dataSemaphore { 0 };
+    Vector<RetainPtr<CFDataRef>> dataResults(count);
+
+    // FIXME: Once we support range requests, make a single request for all ranges instead of <count> individual requests.
+    RunLoop::main().dispatch([plugin = WTFMove(plugin), &dataResults, ranges, count, &dataSemaphore] {
+        for (size_t i = 0; i < count; ++i) {
+            plugin->getResourceBytesAtPosition(ranges[i].length, ranges[i].location, [i, &dataResults, &dataSemaphore](const uint8_t* bytes, size_t bytesCount) {
+                dataResults[i] = adoptCF(CFDataCreate(kCFAllocatorDefault, bytes, bytesCount));
+                dataSemaphore.signal();
+            });
+        }
+    });
+
+    for (size_t i = 0; i < count; ++i)
+        dataSemaphore.wait();
+
+    for (auto& result : dataResults) {
+        if (!result)
+            result = adoptCF(CFDataCreate(kCFAllocatorDefault, 0, 0));
+        CFArrayAppendValue(buffers, result.get());
+    }
+}
+
+static void dataProviderReleaseInfoCallback(void* info)
+{
+    ASSERT(!isMainThread());
+    adoptRef((PDFPlugin*)info);
+}
+
+void PDFPlugin::threadEntry(Ref<PDFPlugin>&& protectedPlugin)
+{
+    CGDataProviderDirectAccessRangesCallbacks dataProviderCallbacks {
+        0,
+        dataProviderGetBytesAtPositionCallback,
+        dataProviderGetByteRangesCallback,
+        dataProviderReleaseInfoCallback,
+    };
+
+    // Balanced by a deref inside of the dataProviderReleaseInfoCallback
+    ref();
+
+    RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateMultiRangeDirectAccess(this, kCGDataProviderIndeterminateSize, &dataProviderCallbacks));
+    CGDataProviderSetProperty(dataProvider.get(), kCGDataProviderHasHighLatency, kCFBooleanTrue);
+    m_backgroundThreadDocument = adoptNS([[pdfDocumentClass() alloc] initWithProvider:dataProvider.get()]);
+
+    // The main thread dispatch below removes the last reference to the PDF thread.
+    // It must be the last code executed in this function.
+    callOnMainThread([this, protectedPlugin = WTFMove(protectedPlugin)] {
+        adoptBackgroundThreadDocument();
+    });
+}
+
+void PDFPlugin::unconditionalCompleteOutstandingRangeRequests()
+{
+    for (auto& request : m_outstandingByteRangeRequests)
+        unconditionalCompleteRangeRequest(request);
+    m_outstandingByteRangeRequests.clear();
+}
+
+void PDFPlugin::unconditionalCompleteRangeRequest(ByteRangeRequest& request)
+{
+    uint64_t fullSize = m_data ? static_cast<uint64_t>(CFDataGetLength(m_data.get())) : 0;
+    if (static_cast<uint64_t>(request.position) >= fullSize) {
+        request.completionHandler(nullptr, 0);
+        return;
+    }
+
+    auto count = request.position + request.count > fullSize ? fullSize - 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());
+
+    uint64_t dataLength = m_data ? static_cast<uint64_t>(CFDataGetLength(m_data.get())) : 0;
+    if (m_data && dataLength >= position + count) {
+        completionHandler(CFDataGetBytePtr(m_data.get()) + position, count);
+        return;
+    }
+
+    ByteRangeRequest request = { position, count, WTFMove(completionHandler) };
+    if (m_documentFinishedLoading) {
+        unconditionalCompleteRangeRequest(request);
+        return;
+    }
+
+    m_outstandingByteRangeRequests.append(WTFMove(request));
+}
+
+void PDFPlugin::adoptBackgroundThreadDocument()
+{
+    ASSERT(!m_pdfDocument);
+    ASSERT(m_backgroundThreadDocument);
+    ASSERT(isMainThread());
+
+    m_pdfDocument = WTFMove(m_backgroundThreadDocument);
+
+    // If the plugin was manually destroyed, the m_pdfThread might already be gone.
+    if (m_pdfThread) {
+        m_pdfThread->waitForCompletion();
+        m_pdfThread = nullptr;
+    }
+
+    // If the plugin is being destroyed, no point in doing any more PDF work
+    if (m_isBeingDestroyed)
+        return;
+
+    installPDFDocument();
+}
+#endif // HAVE(INCREMENTAL_PDF_APIS)
+
 PluginInfo PDFPlugin::pluginInfo()
 {
     PluginInfo info;
@@ -944,15 +1093,27 @@
 void PDFPlugin::pdfDocumentDidLoad()
 {
     addArchiveResource();
-    
-    RetainPtr<PDFDocument> document = adoptNS([[pdfDocumentClass() alloc] initWithData:rawData()]);
 
-    setPDFDocument(document);
+    m_documentFinishedLoading = true;
 
+#if HAVE(INCREMENTAL_PDF_APIS)
+    // At this point we know all data for the document, and therefore we know how to answer any outstanding range requests.
+    unconditionalCompleteOutstandingRangeRequests();
+#else
+    m_pdfDocument = adoptNS([[pdfDocumentClass() alloc] initWithData:rawData()]);
+    installPDFDocument();
+#endif
+}
+
+void PDFPlugin::installPDFDocument()
+{
+    ASSERT(m_pdfDocument);
+    ASSERT(isMainThread());
+
     updatePageAndDeviceScaleFactors();
 
     [m_pdfLayerController setFrameSize:size()];
-    m_pdfLayerController.get().document = document.get();
+    m_pdfLayerController.get().document = m_pdfDocument.get();
 
     if (handlesPageScaleFactor())
         pluginView()->setPageScaleFactor([m_pdfLayerController contentScaleFactor], IntPoint());
@@ -964,7 +1125,7 @@
 
     runScriptsInPDFDocument();
 
-    if ([document isLocked])
+    if ([m_pdfDocument isLocked])
         createPasswordEntryForm();
 
     if ([m_pdfLayerController respondsToSelector:@selector(setURLFragment:)]) {
@@ -1033,6 +1194,19 @@
         m_data = adoptCF(CFDataCreateMutable(0, 0));
 
     CFDataAppendBytes(m_data.get(), reinterpret_cast<const UInt8*>(bytes), length);
+
+#if HAVE(INCREMENTAL_PDF_APIS)
+    size_t index = 0;
+    while (index < m_outstandingByteRangeRequests.size()) {
+        auto& request = m_outstandingByteRangeRequests[index];
+        if (static_cast<uint64_t>(CFDataGetLength(m_data.get())) >= request.position + request.count) {
+            request.completionHandler(CFDataGetBytePtr(m_data.get()) + request.position, request.count);
+            m_outstandingByteRangeRequests.remove(index);
+            continue;
+        }
+        ++index;
+    }
+#endif
 }
 
 void PDFPlugin::manualStreamDidFinishLoading()
@@ -1043,7 +1217,10 @@
 
 void PDFPlugin::manualStreamDidFail(bool)
 {
-    m_data.clear();
+    m_data = nullptr;
+#if HAVE(INCREMENTAL_PDF_APIS)
+    unconditionalCompleteOutstandingRangeRequests();
+#endif
 }
 
 void PDFPlugin::runScriptsInPDFDocument()
@@ -1074,7 +1251,7 @@
 {
     [m_pdfLayerController attemptToUnlockWithPassword:password];
 
-    if (![pdfDocument() isLocked]) {
+    if (![m_pdfDocument isLocked]) {
         m_passwordField = nullptr;
 
         calculateSizes();
@@ -1099,7 +1276,7 @@
 
 void PDFPlugin::calculateSizes()
 {
-    if ([pdfDocument() isLocked]) {
+    if ([m_pdfDocument isLocked]) {
         m_firstPageHeight = 0;
         setPDFDocumentSize(IntSize(0, 0));
         return;
@@ -1129,6 +1306,18 @@
 
 void PDFPlugin::destroy()
 {
+    m_documentFinishedLoading = true;
+
+#if HAVE(INCREMENTAL_PDF_APIS)
+    // By clearing out the resource data and handling all outstanding range requests,
+    // we can force the PDFThread to complete quickly
+    if (m_pdfThread) {
+        m_data = nullptr;
+        unconditionalCompleteOutstandingRangeRequests();
+        m_pdfThread->waitForCompletion();
+    }
+#endif
+
     m_pdfLayerController.get().delegate = 0;
 
     if (webFrame()) {
@@ -1418,7 +1607,7 @@
     if (m_scrollCornerLayer && IntRect(m_scrollCornerLayer.get().frame).contains(mousePosition))
         return false;
 
-    if ([pdfDocument() isLocked])
+    if ([m_pdfDocument isLocked])
         return false;
 
     // Right-clicks and Control-clicks always call handleContextMenuEvent as well.
@@ -1713,7 +1902,7 @@
 {
     // FIXME: We should probably notify the user that they can't save before the document is finished loading.
     // PDFViewController does an NSBeep(), but that seems insufficient.
-    if (!pdfDocument())
+    if (!m_documentFinishedLoading)
         return;
 
     NSData *data = ""
@@ -1725,7 +1914,7 @@
     if (!m_temporaryPDFUUID) {
         // FIXME: We should probably notify the user that they can't save before the document is finished loading.
         // PDFViewController does an NSBeep(), but that seems insufficient.
-        if (!pdfDocument())
+        if (!m_documentFinishedLoading)
             return;
 
         NSData *data = ""
@@ -1810,7 +1999,7 @@
         return 0;
 
     NSStringCompareOptions nsOptions = options.contains(WebCore::CaseInsensitive) ? NSCaseInsensitiveSearch : 0;
-    return [[pdfDocument() findString:target withOptions:nsOptions] count];
+    return [[m_pdfDocument findString:target withOptions:nsOptions] count];
 }
 
 PDFSelection *PDFPlugin::nextMatchForString(const String& target, BOOL searchForward, BOOL caseSensitive, BOOL wrapSearch, PDFSelection *initialSelection, BOOL startInSelection)
@@ -1824,8 +2013,6 @@
     if (!caseSensitive)
         options |= NSCaseInsensitiveSearch;
 
-    PDFDocument *document = pdfDocument().get();
-
     RetainPtr<PDFSelection> selectionForInitialSearch = adoptNS([initialSelection copy]);
     if (startInSelection) {
         // Initially we want to include the selected text in the search. So we must modify the starting search
@@ -1841,15 +2028,15 @@
         }
     }
 
-    PDFSelection *foundSelection = [document findString:target fromSelection:selectionForInitialSearch.get() withOptions:options];
+    PDFSelection *foundSelection = [m_pdfDocument findString:target fromSelection:selectionForInitialSearch.get() withOptions:options];
 
     // If we first searched in the selection, and we found the selection, search again from just past the selection.
     if (startInSelection && [foundSelection isEqual:initialSelection])
-        foundSelection = [document findString:target fromSelection:initialSelection withOptions:options];
+        foundSelection = [m_pdfDocument findString:target fromSelection:initialSelection withOptions:options];
 
     if (!foundSelection && wrapSearch) {
-        auto emptySelection = adoptNS([[pdfSelectionClass() alloc] initWithDocument:document]);
-        foundSelection = [document findString:target fromSelection:emptySelection.get() withOptions:options];
+        auto emptySelection = adoptNS([[pdfSelectionClass() alloc] initWithDocument:m_pdfDocument.get()]);
+        foundSelection = [m_pdfDocument findString:target fromSelection:emptySelection.get() withOptions:options];
     }
 
     return foundSelection;
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to