Implement iOS WebKit2 PDF Find-in-Page
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 Mar 2015 22:04:55 +0000 (22:04 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 Mar 2015 22:04:55 +0000 (22:04 +0000)
https://bugs.webkit.org/show_bug.cgi?id=143065
<rdar://problem/16793370>

Reviewed by Dan Bernstein.

* Platform/spi/ios/CorePDFSPI.h:
Add some SPI.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _countStringMatches:options:maxCount:]):
(-[WKWebView _findString:options:maxCount:]):
(-[WKWebView _hideFindUI]):
If we have a custom content view, forward find-in-page methods to it.

* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/API/Cocoa/_WKFindOptions.h:
Move _WKFindOptions into its own file.

* UIProcess/Cocoa/WKWebViewContentProvider.h:
Add find-related methods to the WKWebViewContentProvider protocol.

* UIProcess/ios/WKPDFView.mm:
(-[WKPDFView web_initWithFrame:webView:]):
(-[WKPDFView dealloc]):
Maintain a dispatch queue for searching the PDF.

(-[WKPDFView web_setContentProviderData:suggestedFilename:]):
Fix a type.

(-[WKPDFView _ensureViewForPage:]):
Split the code to create/parent a page view out from _revalidateViews,
so that the find code can create/parent the targetted UIPDFPageView
in order to install the find highlight in it, but before it is scrolled into view.

(-[WKPDFView _revalidateViews]):
Use the stored page index instead of counting again.
Don't unparent page views that have find-in-page highlights in them, because
we won't know enough to recreate them later.

(-[WKPDFView _computePageAndDocumentFrames]):
Put the page index in PDFPageInfo, for reference elsewhere.

(-[WKPDFView _stringCompareOptionsFromWKFindOptions:]):
(-[WKPDFView _computeMatchesForString:options:maxCount:completionHandler:]):
Asynchronously (and on our serial queue) search the PDF for the given string.
We'll cache the most recent results to avoid searching more than once,
cancel existing searches when another begins (since it's a serial queue,
this ensures we'll only have one search active at a time).

(-[WKPDFView web_countStringMatches:options:maxCount:]):
Count the number of matches for the given string, and inform the FindClient
of the result.

(-[WKPDFView _didFindMatch:]):
When we find a match, create a view for it, highlight the match, and
zoom so that it's in-view.

(-[WKPDFView web_findString:options:maxCount:]):
Incrementally search the document for the given string. We do an incremental
search even if we have all of the results so that we can anchor the search
in the right place if the new string is a prefix of the old string, etc.
Wrap around appropriately when searching past the beginning or end of the
document. If we have all of the results (there were less than maxCount results),
look through the cached matches for the incrementally-found result and
report its index to the client.

(-[WKPDFView web_hideFindUI]):
Dismiss the selection and clear some state so we start the next search afresh.

* WebKit2.xcodeproj/project.pbxproj:

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

Source/WebKit2/ChangeLog
Source/WebKit2/Platform/spi/ios/CorePDFSPI.h
Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit2/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit2/UIProcess/API/Cocoa/_WKFindOptions.h [new file with mode: 0644]
Source/WebKit2/UIProcess/Cocoa/WKWebViewContentProvider.h
Source/WebKit2/UIProcess/ios/WKPDFView.mm
Source/WebKit2/WebKit2.xcodeproj/project.pbxproj

index 3367722..ff3b634 100644 (file)
@@ -1,3 +1,77 @@
+2015-03-27  Tim Horton  <timothy_horton@apple.com>
+
+        Implement iOS WebKit2 PDF Find-in-Page
+        https://bugs.webkit.org/show_bug.cgi?id=143065
+        <rdar://problem/16793370>
+
+        Reviewed by Dan Bernstein.
+
+        * Platform/spi/ios/CorePDFSPI.h:
+        Add some SPI.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _countStringMatches:options:maxCount:]):
+        (-[WKWebView _findString:options:maxCount:]):
+        (-[WKWebView _hideFindUI]):
+        If we have a custom content view, forward find-in-page methods to it.
+        
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/API/Cocoa/_WKFindOptions.h:
+        Move _WKFindOptions into its own file.
+
+        * UIProcess/Cocoa/WKWebViewContentProvider.h:
+        Add find-related methods to the WKWebViewContentProvider protocol.
+
+        * UIProcess/ios/WKPDFView.mm:
+        (-[WKPDFView web_initWithFrame:webView:]):
+        (-[WKPDFView dealloc]):
+        Maintain a dispatch queue for searching the PDF.
+
+        (-[WKPDFView web_setContentProviderData:suggestedFilename:]):
+        Fix a type.
+
+        (-[WKPDFView _ensureViewForPage:]):
+        Split the code to create/parent a page view out from _revalidateViews,
+        so that the find code can create/parent the targetted UIPDFPageView
+        in order to install the find highlight in it, but before it is scrolled into view.
+
+        (-[WKPDFView _revalidateViews]):
+        Use the stored page index instead of counting again.
+        Don't unparent page views that have find-in-page highlights in them, because
+        we won't know enough to recreate them later.
+
+        (-[WKPDFView _computePageAndDocumentFrames]):
+        Put the page index in PDFPageInfo, for reference elsewhere.
+
+        (-[WKPDFView _stringCompareOptionsFromWKFindOptions:]):
+        (-[WKPDFView _computeMatchesForString:options:maxCount:completionHandler:]):
+        Asynchronously (and on our serial queue) search the PDF for the given string.
+        We'll cache the most recent results to avoid searching more than once,
+        cancel existing searches when another begins (since it's a serial queue,
+        this ensures we'll only have one search active at a time).
+
+        (-[WKPDFView web_countStringMatches:options:maxCount:]):
+        Count the number of matches for the given string, and inform the FindClient
+        of the result.
+
+        (-[WKPDFView _didFindMatch:]):
+        When we find a match, create a view for it, highlight the match, and
+        zoom so that it's in-view.
+
+        (-[WKPDFView web_findString:options:maxCount:]):
+        Incrementally search the document for the given string. We do an incremental
+        search even if we have all of the results so that we can anchor the search
+        in the right place if the new string is a prefix of the old string, etc.
+        Wrap around appropriately when searching past the beginning or end of the
+        document. If we have all of the results (there were less than maxCount results),
+        look through the cached matches for the incrementally-found result and
+        report its index to the client.
+
+        (-[WKPDFView web_hideFindUI]):
+        Dismiss the selection and clear some state so we start the next search afresh.
+
+        * WebKit2.xcodeproj/project.pbxproj:
+
 2015-03-27  Andreas Kling  <akling@apple.com>
 
         WebProcessProxy should not retain WebBackForwardListItems forever.
index 6395813..1cb99dd 100644 (file)
 #import <CorePDF/UIPDFLinkAnnotation.h>
 #import <CorePDF/UIPDFPage.h>
 #import <CorePDF/UIPDFPageView.h>
+#import <CorePDF/UIPDFSelection.h>
 
 #else
 
+@class UIPDFSelection;
+
 @interface UIPDFPage : NSObject
 @end
 
 @interface UIPDFPage (Details)
 - (CGRect)cropBoxAccountForRotation;
+- (UIPDFSelection *)findString:(NSString *)string fromSelection:(UIPDFSelection *)selection options:(NSStringCompareOptions)options;
 @end
 
 @interface UIPDFDocument : NSObject
@@ -55,6 +59,7 @@
 
 typedef enum {
     kUIPDFObjectKindGraphic = 1,
+    kUIPDFObjectKindText = 2
 } UIPDFObjectKind;
 
 @class UIPDFPageView;
@@ -76,6 +81,8 @@ typedef enum {
 @interface UIPDFPageView (Details)
 - (id)initWithPage:(UIPDFPage *) page tiledContent:(BOOL)tiled;
 - (CGRect)convertRectFromPDFPageSpace:(CGRect)p;
+- (void)highlightSearchSelection:(UIPDFSelection *)selection animated:(BOOL)animated;
+- (void)clearSearchHighlights;
 @property (nonatomic, assign) BOOL useBackingLayer;
 @property (nonatomic, assign) id<NSObject, UIPDFPageViewDelegate> delegate;
 @property (nonatomic, readonly) CALayer *contentLayer;
@@ -120,4 +127,15 @@ typedef enum {
 - (void)annotation:(UIPDFAnnotation *)annotation isBeingPressedAtPoint:(CGPoint) point controller:(UIPDFAnnotationController *)controller;
 @end
 
+@interface UIPDFSelection : NSObject
+@end
+
+@interface UIPDFSelection (Details)
+- (id)initWithPage:(UIPDFPage *)page fromIndex:(NSUInteger)startIndex toIndex:(NSUInteger)endIndex;
+- (CGRect)bounds;
+- (UIPDFPage *)page;
+- (NSUInteger)startIndex;
+@property (nonatomic, assign) CFRange stringRange;
+@end
+
 #endif
index 68f8197..364f220 100644 (file)
@@ -2094,16 +2094,34 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
 
 - (void)_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
 {
+#if PLATFORM(IOS)
+    if (_customContentView) {
+        [_customContentView web_countStringMatches:string options:options maxCount:maxCount];
+        return;
+    }
+#endif
     _page->countStringMatches(string, toFindOptions(options), maxCount);
 }
 
 - (void)_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
 {
+#if PLATFORM(IOS)
+    if (_customContentView) {
+        [_customContentView web_findString:string options:options maxCount:maxCount];
+        return;
+    }
+#endif
     _page->findString(string, toFindOptions(options), maxCount);
 }
 
 - (void)_hideFindUI
 {
+#if PLATFORM(IOS)
+    if (_customContentView) {
+        [_customContentView web_hideFindUI];
+        return;
+    }
+#endif
     _page->hideFindUI();
 }
 
index 1e2f445..300d1ee 100644 (file)
@@ -27,6 +27,7 @@
 
 #if WK_API_ENABLED
 
+#import <WebKit/_WKFindOptions.h>
 #import <WebKit/_WKRenderingProgressEvents.h>
 
 typedef NS_ENUM(NSInteger, _WKPaginationMode) {
@@ -37,18 +38,6 @@ typedef NS_ENUM(NSInteger, _WKPaginationMode) {
     _WKPaginationModeBottomToTop,
 } WK_ENUM_AVAILABLE(10_10, 8_0);
 
-typedef NS_OPTIONS(NSUInteger, _WKFindOptions) {
-    _WKFindOptionsCaseInsensitive = 1 << 0,
-    _WKFindOptionsAtWordStarts = 1 << 1,
-    _WKFindOptionsTreatMedialCapitalAsWordStart = 1 << 2,
-    _WKFindOptionsBackwards = 1 << 3,
-    _WKFindOptionsWrapAround = 1 << 4,
-    _WKFindOptionsShowOverlay = 1 << 5,
-    _WKFindOptionsShowFindIndicator = 1 << 6,
-    _WKFindOptionsShowHighlight = 1 << 7,
-    _WKFindOptionsDetermineMatchIndex = 1 << 8,
-} WK_ENUM_AVAILABLE(10_10, 8_0);
-
 @class WKBrowsingContextHandle;
 @class _WKRemoteObjectRegistry;
 @class _WKSessionState;
diff --git a/Source/WebKit2/UIProcess/API/Cocoa/_WKFindOptions.h b/Source/WebKit2/UIProcess/API/Cocoa/_WKFindOptions.h
new file mode 100644 (file)
index 0000000..c25c5d9
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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
+
+typedef NS_OPTIONS(NSUInteger, _WKFindOptions) {
+    _WKFindOptionsCaseInsensitive = 1 << 0,
+    _WKFindOptionsAtWordStarts = 1 << 1,
+    _WKFindOptionsTreatMedialCapitalAsWordStart = 1 << 2,
+    _WKFindOptionsBackwards = 1 << 3,
+    _WKFindOptionsWrapAround = 1 << 4,
+    _WKFindOptionsShowOverlay = 1 << 5,
+    _WKFindOptionsShowFindIndicator = 1 << 6,
+    _WKFindOptionsShowHighlight = 1 << 7,
+    _WKFindOptionsDetermineMatchIndex = 1 << 8,
+
+    _WKFindOptionsIrrelevantForIncrementalResults = _WKFindOptionsShowOverlay | _WKFindOptionsShowFindIndicator | _WKFindOptionsShowHighlight | _WKFindOptionsDetermineMatchIndex,
+    _WKFindOptionsIrrelevantForBatchResults = _WKFindOptionsBackwards | _WKFindOptionsWrapAround | _WKFindOptionsIrrelevantForIncrementalResults
+
+} WK_ENUM_AVAILABLE(10_10, 8_0);
+
+#endif
index debea6b..c1a0501 100644 (file)
@@ -30,6 +30,7 @@
 #if PLATFORM(IOS)
 
 #import <WebKit/WKPageLoadTypes.h>
+#import <WebKit/_WKFindOptions.h>
 
 @class NSData;
 @class UIScrollView;
@@ -50,6 +51,9 @@ struct UIEdgeInsets;
 - (void)web_computedContentInsetDidChange;
 - (void)web_setFixedOverlayView:(UIView *)fixedOverlayView;
 - (void)web_didSameDocumentNavigation:(WKSameDocumentNavigationType)navigationType;
+- (void)web_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount;
+- (void)web_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount;
+- (void)web_hideFindUI;
 
 @end
 
index e2495e4..043492c 100644 (file)
 
 #if PLATFORM(IOS)
 
+#import "APIFindClient.h"
 #import "APIUIClient.h"
 #import "CorePDFSPI.h"
 #import "SessionState.h"
 #import "UIKitSPI.h"
 #import "WKPDFPageNumberIndicator.h"
 #import "WKWebViewInternal.h"
+#import "WeakObjCPtr.h"
 #import "WebPageProxy.h"
+#import "_WKFindDelegate.h"
 #import <MobileCoreServices/UTCoreTypes.h>
 #import <WebCore/FloatRect.h>
 #import <wtf/RetainPtr.h>
@@ -54,6 +57,7 @@ typedef struct {
     CGRect frame;
     RetainPtr<UIPDFPageView> view;
     RetainPtr<UIPDFPage> page;
+    unsigned index;
 } PDFPageInfo;
 
 @interface WKPDFView ()
@@ -79,6 +83,22 @@ typedef struct {
 
     RetainPtr<WKActionSheetAssistant> _actionSheetAssistant;
     WebKit::InteractionInformationAtPosition _positionInformation;
+
+    unsigned _currentFindPageIndex;
+    unsigned _currentFindMatchIndex;
+    RetainPtr<UIPDFSelection> _currentFindSelection;
+
+    RetainPtr<NSString> _cachedFindString;
+    Vector<RetainPtr<UIPDFSelection>> _cachedFindMatches;
+    unsigned _cachedFindMaximumCount;
+    _WKFindOptions _cachedFindOptionsAffectingResults;
+
+    std::atomic<unsigned> _nextComputeMatchesOperationID;
+    RetainPtr<NSString> _nextCachedFindString;
+    unsigned _nextCachedFindMaximumCount;
+    _WKFindOptions _nextCachedFindOptionsAffectingResults;
+
+    dispatch_queue_t _findQueue;
 }
 
 - (instancetype)web_initWithFrame:(CGRect)frame webView:(WKWebView *)webView
@@ -98,6 +118,8 @@ typedef struct {
     _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]);
     [_actionSheetAssistant setDelegate:self];
 
+    _findQueue = dispatch_queue_create("com.apple.WebKit.WKPDFViewComputeMatchesQueue", DISPATCH_QUEUE_SERIAL);
+
     return self;
 }
 
@@ -105,6 +127,7 @@ typedef struct {
 {
     [self _clearPages];
     [_pageNumberIndicator removeFromSuperview];
+    dispatch_release(_findQueue);
     [super dealloc];
 }
 
@@ -135,7 +158,7 @@ typedef struct {
 
     [self _clearPages];
 
-    RetainPtr<CGDataProvider> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
+    RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
     RetainPtr<CGPDFDocumentRef> cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
     _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:cgPDFDocument.get()]);
 
@@ -165,6 +188,21 @@ typedef struct {
         [_pageNumberIndicator show];
 }
 
+- (void)_ensureViewForPage:(PDFPageInfo&)pageInfo
+{
+    if (pageInfo.view)
+        return;
+
+    pageInfo.view = adoptNS([[UIPDFPageView alloc] initWithPage:pageInfo.page.get() tiledContent:YES]);
+    [pageInfo.view setUseBackingLayer:YES];
+    [pageInfo.view setDelegate:self];
+    [[pageInfo.view annotationController] setDelegate:self];
+    [self addSubview:pageInfo.view.get()];
+
+    [pageInfo.view setFrame:pageInfo.frame];
+    [pageInfo.view contentLayer].contentsScale = self.window.screen.scale;
+}
+
 - (void)_revalidateViews
 {
     if (_isStartingZoom)
@@ -178,31 +216,18 @@ typedef struct {
     CGRect targetRectForCenterPage = CGRectInset(targetRect, 0, targetRect.size.height / 2 - pdfPageMargin * 2);
 
     _centerPageNumber = 0;
-    unsigned currentPage = 0;
 
     for (auto& pageInfo : _pages) {
-        ++currentPage;
-
-        if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw)) {
+        if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw) && pageInfo.index != _currentFindPageIndex) {
             [pageInfo.view removeFromSuperview];
             pageInfo.view = nullptr;
             continue;
         }
 
         if (!_centerPageNumber && CGRectIntersectsRect(pageInfo.frame, targetRectForCenterPage))
-            _centerPageNumber = currentPage;
-
-        if (pageInfo.view)
-            continue;
-
-        pageInfo.view = adoptNS([[UIPDFPageView alloc] initWithPage:pageInfo.page.get() tiledContent:YES]);
-        [pageInfo.view setUseBackingLayer:YES];
-        [pageInfo.view setDelegate:self];
-        [[pageInfo.view annotationController] setDelegate:self];
-        [self addSubview:pageInfo.view.get()];
+            _centerPageNumber = pageInfo.index + 1;
 
-        [pageInfo.view setFrame:pageInfo.frame];
-        [pageInfo.view contentLayer].contentsScale = self.window.screen.scale;
+        [self _ensureViewForPage:pageInfo];
     }
 
     [self _updatePageNumberIndicator];
@@ -291,8 +316,8 @@ typedef struct {
     _pages.reserveCapacity(pageCount);
 
     CGRect pageFrame = CGRectMake(0, 0, _minimumSize.width, _minimumSize.height);
-    for (NSUInteger pageNumber = 0; pageNumber < pageCount; ++pageNumber) {
-        UIPDFPage *page = [_pdfDocument pageAtIndex:pageNumber];
+    for (NSUInteger pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
+        UIPDFPage *page = [_pdfDocument pageAtIndex:pageIndex];
         if (!page)
             continue;
 
@@ -303,6 +328,7 @@ typedef struct {
         PDFPageInfo pageInfo;
         pageInfo.page = page;
         pageInfo.frame = pageFrameWithMarginApplied;
+        pageInfo.index = pageIndex;
         _pages.append(pageInfo);
         pageFrame.origin.y += pageFrame.size.height - pdfPageMargin;
     }
@@ -364,6 +390,194 @@ typedef struct {
     return nil;
 }
 
+#pragma mark Find-in-Page
+
+static NSStringCompareOptions stringCompareOptions(_WKFindOptions options)
+{
+    NSStringCompareOptions findOptions = 0;
+    if (options & _WKFindOptionsCaseInsensitive)
+        findOptions |= NSCaseInsensitiveSearch;
+    if (options & _WKFindOptionsBackwards)
+        findOptions |= NSBackwardsSearch;
+    return findOptions;
+}
+
+- (void)_computeMatchesForString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount completionHandler:(void (^)(BOOL success))completionHandler
+{
+    if (!_pdfDocument) {
+        completionHandler(NO);
+        return;
+    }
+
+    _WKFindOptions optionsAffectingResults = options & ~_WKFindOptionsIrrelevantForBatchResults;
+
+    // If this search is equivalent to the currently cached search, bail and call the completion handler, because the existing cached results are valid.
+    if (!_cachedFindMatches.isEmpty() && [_cachedFindString isEqualToString:string] && _cachedFindOptionsAffectingResults == optionsAffectingResults && _cachedFindMaximumCount == maxCount) {
+        // Also, cancel any running search, because it will eventually replace the now-valid results.
+        ++_nextComputeMatchesOperationID;
+
+        completionHandler(YES);
+        return;
+    }
+
+    // If this search is equivalent to the currently running asynchronous search, bail as if this search were cancelled; the original search's completion handler will eventually fire.
+    if ([_nextCachedFindString isEqualToString:string] && _nextCachedFindOptionsAffectingResults == optionsAffectingResults && _nextCachedFindMaximumCount == maxCount) {
+        completionHandler(NO);
+        return;
+    }
+
+    NSStringCompareOptions findOptions = stringCompareOptions(optionsAffectingResults);
+
+    Vector<PDFPageInfo> pages = _pages;
+
+    unsigned computeMatchesOperationID = ++_nextComputeMatchesOperationID;
+    _nextCachedFindString = string;
+    _nextCachedFindOptionsAffectingResults = optionsAffectingResults;
+    _nextCachedFindMaximumCount = maxCount;
+
+    RetainPtr<WKPDFView> retainedSelf = self;
+    typeof(completionHandler) completionHandlerCopy = Block_copy(completionHandler);
+
+    dispatch_async(_findQueue, [pages, string, findOptions, optionsAffectingResults, maxCount, computeMatchesOperationID, retainedSelf, completionHandlerCopy] {
+        Vector<RetainPtr<UIPDFSelection>> matches;
+
+        for (unsigned pageIndex = 0; pageIndex < pages.size(); ++pageIndex) {
+            UIPDFSelection *match = nullptr;
+            while ((match = [pages[pageIndex].page findString:string fromSelection:match options:findOptions])) {
+                matches.append(match);
+                if (matches.size() > maxCount)
+                    goto maxCountExceeded;
+
+                // If we've enqueued another search, cancel this one.
+                if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) {
+                    dispatch_async(dispatch_get_main_queue(), [completionHandlerCopy] {
+                        completionHandlerCopy(NO);
+                        Block_release(completionHandlerCopy);
+                    });
+                    return;
+                }
+            };
+        }
+
+    maxCountExceeded:
+        dispatch_async(dispatch_get_main_queue(), [computeMatchesOperationID, string, optionsAffectingResults, maxCount, matches, completionHandlerCopy, retainedSelf] {
+
+            // If another search has been enqueued in the meantime, ignore this result.
+            if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) {
+                Block_release(completionHandlerCopy);
+                return;
+            }
+
+            retainedSelf->_cachedFindString = string;
+            retainedSelf->_cachedFindOptionsAffectingResults = optionsAffectingResults;
+            retainedSelf->_cachedFindMaximumCount = maxCount;
+            retainedSelf->_cachedFindMatches = matches;
+
+            retainedSelf->_nextCachedFindString = nil;
+
+            completionHandlerCopy(YES);
+            Block_release(completionHandlerCopy);
+        });
+    });
+}
+
+- (void)web_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
+{
+    RefPtr<WebKit::WebPageProxy> page = _webView->_page;
+    [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
+        if (!success)
+            return;
+        page->findClient().didCountStringMatches(page.get(), string, _cachedFindMatches.size());
+    }];
+}
+
+- (void)_didFindMatch:(UIPDFSelection *)match
+{
+    for (auto& pageInfo : _pages) {
+        if (pageInfo.page == match.page) {
+            [self _ensureViewForPage:pageInfo];
+
+            [pageInfo.view highlightSearchSelection:match animated:NO];
+
+            _currentFindPageIndex = pageInfo.index;
+            _currentFindSelection = match;
+
+            CGRect zoomRect = [pageInfo.view convertRectFromPDFPageSpace:match.bounds];
+            [self zoom:pageInfo.view.get() to:zoomRect atPoint:CGPointZero kind:kUIPDFObjectKindText];
+
+            return;
+        }
+    }
+}
+
+- (void)web_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
+{
+    if (_currentFindSelection)
+        [_pages[_currentFindPageIndex].view clearSearchHighlights];
+
+    RetainPtr<UIPDFSelection> previousFindSelection = _currentFindSelection;
+    unsigned previousFindPageIndex = 0;
+    if (previousFindSelection) {
+        previousFindPageIndex = _currentFindPageIndex;
+        if (![_cachedFindString isEqualToString:string]) {
+            NSUInteger location = [_currentFindSelection startIndex];
+            if (location)
+                previousFindSelection = adoptNS([[UIPDFSelection alloc] initWithPage:[_currentFindSelection page] fromIndex:location - 1 toIndex:location]);
+        }
+    }
+
+    NSStringCompareOptions findOptions = stringCompareOptions(options);
+    bool backwards = (options & _WKFindOptionsBackwards);
+    RefPtr<WebKit::WebPageProxy> page = _webView->_page;
+
+    [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
+        if (!success)
+            return;
+
+        unsigned pageIndex = previousFindPageIndex;
+        for (unsigned i = 0; i < _pages.size(); ++i) {
+            UIPDFSelection *match = [_pages[pageIndex].page findString:string fromSelection:(pageIndex == previousFindPageIndex ? previousFindSelection.get() : nil) options:findOptions];
+
+            if (!match) {
+                if (!pageIndex && backwards)
+                    pageIndex = _pages.size() - 1;
+                else if (pageIndex == _pages.size() - 1 && !backwards)
+                    pageIndex = 0;
+                else
+                    pageIndex += backwards ? -1 : 1;
+                continue;
+            }
+
+            [self _didFindMatch:match];
+
+            if (_cachedFindMatches.size() <= maxCount) {
+                _currentFindMatchIndex = 0;
+                for (const auto& knownMatch : _cachedFindMatches) {
+                    if (match.stringRange.location == [knownMatch stringRange].location && match.stringRange.length == [knownMatch stringRange].length) {
+                        page->findClient().didFindString(page.get(), string, _cachedFindMatches.size(), _currentFindMatchIndex);
+                        break;
+                    }
+                    _currentFindMatchIndex++;
+                }
+            }
+
+            return;
+        }
+        
+        page->findClient().didFailToFindString(page.get(), string);
+    }];
+}
+
+- (void)web_hideFindUI
+{
+    if (_currentFindSelection)
+        [_pages[_currentFindPageIndex].view clearSearchHighlights];
+
+    _currentFindSelection = nullptr;
+    _cachedFindString = nullptr;
+    _cachedFindMatches.clear();
+}
+
 #pragma mark UIPDFPageViewDelegate
 
 - (void)zoom:(UIPDFPageView *)pageView to:(CGRect)targetRect atPoint:(CGPoint)origin kind:(UIPDFObjectKind)kind
index 7b16037..5b822f2 100644 (file)
                2DDF731618E95060004F5A66 /* RemoteLayerBackingStoreCollection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2DDF731418E95060004F5A66 /* RemoteLayerBackingStoreCollection.mm */; };
                2DE6943D18BD2A68005C15E5 /* SmartMagnificationControllerMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DE6943B18BD2A68005C15E5 /* SmartMagnificationControllerMessageReceiver.cpp */; };
                2DE6943E18BD2A68005C15E5 /* SmartMagnificationControllerMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DE6943C18BD2A68005C15E5 /* SmartMagnificationControllerMessages.h */; };
+               2DEAC5CF1AC368BB00A195D8 /* _WKFindOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DEAC5CE1AC368BB00A195D8 /* _WKFindOptions.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2DF9593B18A42412009785A1 /* ViewGestureControllerIOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2DF9593418A42412009785A1 /* ViewGestureControllerIOS.mm */; };
                2DF9EEE51A781FB400B6CFBE /* APIFrameInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DF9EEE31A781FB400B6CFBE /* APIFrameInfo.cpp */; };
                2DF9EEE61A781FB400B6CFBE /* APIFrameInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DF9EEE41A781FB400B6CFBE /* APIFrameInfo.h */; };
                2DDF731418E95060004F5A66 /* RemoteLayerBackingStoreCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RemoteLayerBackingStoreCollection.mm; sourceTree = "<group>"; };
                2DE6943B18BD2A68005C15E5 /* SmartMagnificationControllerMessageReceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SmartMagnificationControllerMessageReceiver.cpp; sourceTree = "<group>"; };
                2DE6943C18BD2A68005C15E5 /* SmartMagnificationControllerMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmartMagnificationControllerMessages.h; sourceTree = "<group>"; };
+               2DEAC5CE1AC368BB00A195D8 /* _WKFindOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKFindOptions.h; sourceTree = "<group>"; };
                2DF9593418A42412009785A1 /* ViewGestureControllerIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ViewGestureControllerIOS.mm; path = ios/ViewGestureControllerIOS.mm; sourceTree = "<group>"; };
                2DF9EEE31A781FB400B6CFBE /* APIFrameInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = APIFrameInfo.cpp; sourceTree = "<group>"; };
                2DF9EEE41A781FB400B6CFBE /* APIFrameInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APIFrameInfo.h; sourceTree = "<group>"; };
                                1AD01BC71905D37E00C9C45F /* _WKErrorRecoveryAttempting.h */,
                                1AD01BC61905D37E00C9C45F /* _WKErrorRecoveryAttempting.mm */,
                                005D158E18E4C4EB00734619 /* _WKFindDelegate.h */,
+                               2DEAC5CE1AC368BB00A195D8 /* _WKFindOptions.h */,
                                37A64E5418F38E3C00EB30F1 /* _WKFormDelegate.h */,
                                37A64E5618F38F4600EB30F1 /* _WKFormInputSession.h */,
                                1A43E828188F3CDC009E4D30 /* _WKProcessPoolConfiguration.h */,
                                1A6FBD2811E69BC200DB1371 /* NetscapePlugin.h in Headers */,
                                1A4A9C5612B816CF008FE984 /* NetscapePluginModule.h in Headers */,
                                1AA5889211EE70400061B882 /* NetscapePluginStream.h in Headers */,
+                               2DEAC5CF1AC368BB00A195D8 /* _WKFindOptions.h in Headers */,
                                E1798C7A16E6818800240139 /* NetworkBlobRegistry.h in Headers */,
                                E4436ECC1A0D040B00EAD204 /* NetworkCache.h in Headers */,
                                E489D28A1A0A2DB80078C06A /* NetworkCacheCoder.h in Headers */,