[iOS] Rotating PDF in Safari scrolls to the wrong position
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / WKPDFView.mm
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WKPDFView.h"
28
29 #if PLATFORM(IOS)
30
31 #import "APIFindClient.h"
32 #import "APIUIClient.h"
33 #import "CorePDFSPI.h"
34 #import "SessionState.h"
35 #import "UIKitSPI.h"
36 #import "WKPDFPageNumberIndicator.h"
37 #import "WKWebViewInternal.h"
38 #import "WeakObjCPtr.h"
39 #import "WebPageProxy.h"
40 #import "_WKFindDelegate.h"
41 #import <MobileCoreServices/UTCoreTypes.h>
42 #import <WebCore/FloatRect.h>
43 #import <wtf/RetainPtr.h>
44 #import <wtf/Vector.h>
45
46 using namespace WebCore;
47
48 const CGFloat pdfPageMargin = 8;
49 const CGFloat pdfMinimumZoomScale = 1;
50 const CGFloat pdfMaximumZoomScale = 5;
51
52 const float overdrawHeightMultiplier = 1.5;
53
54 static const CGFloat smartMagnificationElementPadding = 0.05;
55
56 typedef struct {
57     CGRect frame;
58     RetainPtr<UIPDFPageView> view;
59     RetainPtr<UIPDFPage> page;
60     unsigned index;
61 } PDFPageInfo;
62
63 @interface WKPDFView ()
64 - (void)_resetZoomAnimated:(BOOL)animated;
65 @end
66
67 @implementation WKPDFView {
68     RetainPtr<UIPDFDocument> _pdfDocument;
69     RetainPtr<NSString> _suggestedFilename;
70     RetainPtr<WKPDFPageNumberIndicator> _pageNumberIndicator;
71
72     Vector<PDFPageInfo> _pages;
73     unsigned _centerPageNumber;
74
75     CGSize _minimumSize;
76     CGSize _overlaidAccessoryViewsInset;
77     WKWebView *_webView;
78     UIScrollView *_scrollView;
79     UIView *_fixedOverlayView;
80
81     BOOL _isStartingZoom;
82     BOOL _isPerformingSameDocumentNavigation;
83
84     RetainPtr<WKActionSheetAssistant> _actionSheetAssistant;
85     WebKit::InteractionInformationAtPosition _positionInformation;
86
87     unsigned _currentFindPageIndex;
88     unsigned _currentFindMatchIndex;
89     RetainPtr<UIPDFSelection> _currentFindSelection;
90
91     RetainPtr<NSString> _cachedFindString;
92     Vector<RetainPtr<UIPDFSelection>> _cachedFindMatches;
93     unsigned _cachedFindMaximumCount;
94     _WKFindOptions _cachedFindOptionsAffectingResults;
95
96     std::atomic<unsigned> _nextComputeMatchesOperationID;
97     RetainPtr<NSString> _nextCachedFindString;
98     unsigned _nextCachedFindMaximumCount;
99     _WKFindOptions _nextCachedFindOptionsAffectingResults;
100
101     dispatch_queue_t _findQueue;
102 }
103
104 - (instancetype)web_initWithFrame:(CGRect)frame webView:(WKWebView *)webView
105 {
106     if (!(self = [super initWithFrame:frame]))
107         return nil;
108
109     self.backgroundColor = [UIColor grayColor];
110
111     _webView = webView;
112
113     _scrollView = webView.scrollView;
114     [_scrollView setMinimumZoomScale:pdfMinimumZoomScale];
115     [_scrollView setMaximumZoomScale:pdfMaximumZoomScale];
116     [_scrollView setBackgroundColor:[UIColor grayColor]];
117
118     _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]);
119     [_actionSheetAssistant setDelegate:self];
120
121     _findQueue = dispatch_queue_create("com.apple.WebKit.WKPDFViewComputeMatchesQueue", DISPATCH_QUEUE_SERIAL);
122
123     return self;
124 }
125
126 - (void)dealloc
127 {
128     [self _clearPages];
129     [_pageNumberIndicator removeFromSuperview];
130     dispatch_release(_findQueue);
131     [super dealloc];
132 }
133
134 - (NSString *)suggestedFilename
135 {
136     return _suggestedFilename.get();
137 }
138
139 - (CGPDFDocumentRef)pdfDocument
140 {
141     return [_pdfDocument CGDocument];
142 }
143
144 - (void)_clearPages
145 {
146     for (auto& page : _pages) {
147         [page.view removeFromSuperview];
148         [page.view setDelegate:nil];
149         [[page.view annotationController] setDelegate:nil];
150     }
151     
152     _pages.clear();
153 }
154
155 - (void)web_setContentProviderData:(NSData *)data suggestedFilename:(NSString *)filename
156 {
157     _suggestedFilename = adoptNS([filename copy]);
158
159     [self _clearPages];
160
161     RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
162     RetainPtr<CGPDFDocumentRef> cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
163     _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:cgPDFDocument.get()]);
164
165     // FIXME: restore the scroll position and page scale if navigating from the back/forward list.
166
167     [self _computePageAndDocumentFrames];
168     [self _revalidateViews];
169     [self _scrollToFragment:_webView.URL.fragment];
170 }
171
172 - (void)web_setMinimumSize:(CGSize)size
173 {
174     _minimumSize = size;
175
176     CGFloat oldDocumentLeftFraction = 0;
177     CGFloat oldDocumentTopFraction = 0;
178     CGSize contentSize = _scrollView.contentSize;
179     if (contentSize.width && contentSize.height) {
180         CGPoint contentOffset = _scrollView.contentOffset;
181         UIEdgeInsets contentInset = _scrollView.contentInset;
182         oldDocumentLeftFraction = (contentOffset.x + contentInset.left) / contentSize.width;
183         oldDocumentTopFraction = (contentOffset.y + contentInset.top) / contentSize.height;
184     }
185
186     [self _computePageAndDocumentFrames];
187
188     // FIXME: This dispatch_async is unnecessary except to work around rdar://problem/15035620.
189     // Once that is resolved, we should do the setContentOffset without the dispatch_async.
190     RetainPtr<WKPDFView> retainedSelf = self;
191     dispatch_async(dispatch_get_main_queue(), [retainedSelf, oldDocumentLeftFraction, oldDocumentTopFraction] {
192         CGSize contentSize = retainedSelf->_scrollView.contentSize;
193         UIEdgeInsets contentInset = retainedSelf->_scrollView.contentInset;
194         [retainedSelf->_scrollView setContentOffset:CGPointMake((oldDocumentLeftFraction * contentSize.width) - contentInset.left, (oldDocumentTopFraction * contentSize.height) - contentInset.top) animated:NO];
195
196         [retainedSelf _revalidateViews];
197     });
198 }
199
200 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
201 {
202     if (scrollView.isZoomBouncing || scrollView._isAnimatingZoom)
203         return;
204
205     [self _revalidateViews];
206
207     if (!_isPerformingSameDocumentNavigation)
208         [_pageNumberIndicator show];
209 }
210
211 - (void)_ensureViewForPage:(PDFPageInfo&)pageInfo
212 {
213     if (pageInfo.view)
214         return;
215
216     pageInfo.view = adoptNS([[UIPDFPageView alloc] initWithPage:pageInfo.page.get() tiledContent:YES]);
217     [pageInfo.view setUseBackingLayer:YES];
218     [pageInfo.view setDelegate:self];
219     [[pageInfo.view annotationController] setDelegate:self];
220     [self addSubview:pageInfo.view.get()];
221
222     [pageInfo.view setFrame:pageInfo.frame];
223     [pageInfo.view contentLayer].contentsScale = self.window.screen.scale;
224 }
225
226 - (void)_revalidateViews
227 {
228     if (_isStartingZoom)
229         return;
230
231     CGRect targetRect = [_scrollView convertRect:_scrollView.bounds toView:self];
232
233     // We apply overdraw after applying scale in order to avoid excessive
234     // memory use caused by scaling the overdraw.
235     CGRect targetRectWithOverdraw = CGRectInset(targetRect, 0, -targetRect.size.height * overdrawHeightMultiplier);
236     CGRect targetRectForCenterPage = CGRectInset(targetRect, 0, targetRect.size.height / 2 - pdfPageMargin * 2);
237
238     _centerPageNumber = 0;
239
240     for (auto& pageInfo : _pages) {
241         if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw) && pageInfo.index != _currentFindPageIndex) {
242             [pageInfo.view removeFromSuperview];
243             pageInfo.view = nullptr;
244             continue;
245         }
246
247         if (!_centerPageNumber && CGRectIntersectsRect(pageInfo.frame, targetRectForCenterPage))
248             _centerPageNumber = pageInfo.index + 1;
249
250         [self _ensureViewForPage:pageInfo];
251     }
252
253     [self _updatePageNumberIndicator];
254 }
255
256 - (CGPoint)_offsetForPageNumberIndicator
257 {
258     UIEdgeInsets contentInset = [_webView _computedContentInset];
259     return CGPointMake(contentInset.left, contentInset.top + _overlaidAccessoryViewsInset.height);
260 }
261
262 - (void)_updatePageNumberIndicator
263 {
264     if (_isPerformingSameDocumentNavigation)
265         return;
266
267     if (!_pageNumberIndicator)
268         _pageNumberIndicator = adoptNS([[WKPDFPageNumberIndicator alloc] initWithFrame:CGRectZero]);
269
270     [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
271
272     [_pageNumberIndicator setCurrentPageNumber:_centerPageNumber];
273     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:NO];
274 }
275
276 - (void)web_setOverlaidAccessoryViewsInset:(CGSize)inset
277 {
278     _overlaidAccessoryViewsInset = inset;
279     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:YES];
280 }
281
282 - (void)web_computedContentInsetDidChange
283 {
284     [self _updatePageNumberIndicator];
285 }
286
287 - (void)web_setFixedOverlayView:(UIView *)fixedOverlayView
288 {
289     _fixedOverlayView = fixedOverlayView;
290
291     if (_pageNumberIndicator)
292         [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
293 }
294
295 - (void)_scrollToFragment:(NSString *)fragment
296 {
297     if (![fragment hasPrefix:@"page"])
298         return;
299
300     NSInteger pageIndex = [[fragment substringFromIndex:4] integerValue] - 1;
301     if (pageIndex < 0 || static_cast<std::size_t>(pageIndex) >= _pages.size())
302         return;
303
304     _isPerformingSameDocumentNavigation = YES;
305
306     [_pageNumberIndicator hide];
307     [self _resetZoomAnimated:NO];
308
309     // Ensure that the page margin is visible below the content inset.
310     const CGFloat verticalOffset = _pages[pageIndex].frame.origin.y - _webView._computedContentInset.top - pdfPageMargin;
311     [_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x, verticalOffset) animated:NO];
312
313     _isPerformingSameDocumentNavigation = NO;
314 }
315
316 - (void)web_didSameDocumentNavigation:(WKSameDocumentNavigationType)navigationType
317 {
318     // Check for kWKSameDocumentNavigationSessionStatePop instead of kWKSameDocumentNavigationAnchorNavigation since the
319     // latter is only called once when navigating to the same anchor in succession. If the user navigates to a page
320     // then scrolls back and clicks on the same link a second time, we want to scroll again.
321     if (navigationType != kWKSameDocumentNavigationSessionStatePop)
322         return;
323
324     // FIXME: restore the scroll position and page scale if navigating back from a fragment.
325
326     [self _scrollToFragment:_webView.URL.fragment];
327 }
328
329 - (void)_computePageAndDocumentFrames
330 {
331     NSUInteger pageCount = [_pdfDocument numberOfPages];
332     [_pageNumberIndicator setPageCount:pageCount];
333     
334     [self _clearPages];
335
336     _pages.reserveCapacity(pageCount);
337
338     CGRect pageFrame = CGRectMake(0, 0, _minimumSize.width, _minimumSize.height);
339     for (NSUInteger pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
340         UIPDFPage *page = [_pdfDocument pageAtIndex:pageIndex];
341         if (!page)
342             continue;
343
344         CGSize pageSize = [page cropBoxAccountForRotation].size;
345         pageFrame.size.height = pageSize.height / pageSize.width * pageFrame.size.width;
346         CGRect pageFrameWithMarginApplied = CGRectInset(pageFrame, pdfPageMargin, pdfPageMargin);
347
348         PDFPageInfo pageInfo;
349         pageInfo.page = page;
350         pageInfo.frame = pageFrameWithMarginApplied;
351         pageInfo.index = pageIndex;
352         _pages.append(pageInfo);
353         pageFrame.origin.y += pageFrame.size.height - pdfPageMargin;
354     }
355
356     CGFloat scale = _scrollView.zoomScale;
357     CGRect newFrame = [self frame];
358     newFrame.size.width = _minimumSize.width * scale;
359     newFrame.size.height = std::max(pageFrame.origin.y + pdfPageMargin, _minimumSize.height) * scale;
360
361     [self setFrame:newFrame];
362     [_scrollView setContentSize:newFrame.size];
363 }
364
365 - (void)_resetZoomAnimated:(BOOL)animated
366 {
367     _isStartingZoom = YES;
368
369     CGRect scrollViewBounds = _scrollView.bounds;
370     CGPoint centerOfPageInDocumentCoordinates = [_scrollView convertPoint:CGPointMake(CGRectGetMidX(scrollViewBounds), CGRectGetMidY(scrollViewBounds)) toView:self];
371     [_webView _zoomOutWithOrigin:centerOfPageInDocumentCoordinates animated:animated];
372
373     _isStartingZoom = NO;
374 }
375
376 - (void)_highlightLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation forDuration:(NSTimeInterval)duration completionHandler:(void (^)())completionHandler
377 {
378     static const CGFloat highlightBorderRadius = 3;
379     static const CGFloat highlightColorComponent = 26.0 / 255;
380     static UIColor *highlightColor = [[UIColor alloc] initWithRed:highlightColorComponent green:highlightColorComponent blue:highlightColorComponent alpha:0.3];
381
382     UIPDFPageView *pageView = linkAnnotation.annotationController.pageView;
383     CGRect highlightViewFrame = [self convertRect:[pageView convertRectFromPDFPageSpace:linkAnnotation.Rect] fromView:pageView];
384     RetainPtr<_UIHighlightView> highlightView = adoptNS([[_UIHighlightView alloc] initWithFrame:CGRectInset(highlightViewFrame, -highlightBorderRadius, -highlightBorderRadius)]);
385     [highlightView setOpaque:NO];
386     [highlightView setCornerRadius:highlightBorderRadius];
387     [highlightView setColor:highlightColor];
388
389     ASSERT(isMainThread());
390     [self addSubview:highlightView.get()];
391     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
392         [highlightView removeFromSuperview];
393         completionHandler();
394     });
395 }
396
397 - (NSURL *)_URLForLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation
398 {
399     NSURL *documentURL = _webView.URL;
400
401     if (NSURL *url = linkAnnotation.url)
402         return [NSURL URLWithString:url.relativeString relativeToURL:documentURL];
403
404     if (NSUInteger pageNumber = linkAnnotation.pageNumber) {
405         String anchorString = ASCIILiteral("#page");
406         anchorString.append(String::number(pageNumber));
407         return [NSURL URLWithString:anchorString relativeToURL:documentURL];
408     }
409
410     return nil;
411 }
412
413 #pragma mark Find-in-Page
414
415 static NSStringCompareOptions stringCompareOptions(_WKFindOptions options)
416 {
417     NSStringCompareOptions findOptions = 0;
418     if (options & _WKFindOptionsCaseInsensitive)
419         findOptions |= NSCaseInsensitiveSearch;
420     if (options & _WKFindOptionsBackwards)
421         findOptions |= NSBackwardsSearch;
422     return findOptions;
423 }
424
425 - (void)_computeMatchesForString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount completionHandler:(void (^)(BOOL success))completionHandler
426 {
427     if (!_pdfDocument) {
428         completionHandler(NO);
429         return;
430     }
431
432     _WKFindOptions optionsAffectingResults = options & ~_WKFindOptionsIrrelevantForBatchResults;
433
434     // If this search is equivalent to the currently cached search, bail and call the completion handler, because the existing cached results are valid.
435     if (!_cachedFindMatches.isEmpty() && [_cachedFindString isEqualToString:string] && _cachedFindOptionsAffectingResults == optionsAffectingResults && _cachedFindMaximumCount == maxCount) {
436         // Also, cancel any running search, because it will eventually replace the now-valid results.
437         ++_nextComputeMatchesOperationID;
438
439         completionHandler(YES);
440         return;
441     }
442
443     // 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.
444     if ([_nextCachedFindString isEqualToString:string] && _nextCachedFindOptionsAffectingResults == optionsAffectingResults && _nextCachedFindMaximumCount == maxCount) {
445         completionHandler(NO);
446         return;
447     }
448
449     NSStringCompareOptions findOptions = stringCompareOptions(optionsAffectingResults);
450
451     Vector<PDFPageInfo> pages = _pages;
452
453     unsigned computeMatchesOperationID = ++_nextComputeMatchesOperationID;
454     _nextCachedFindString = string;
455     _nextCachedFindOptionsAffectingResults = optionsAffectingResults;
456     _nextCachedFindMaximumCount = maxCount;
457
458     RetainPtr<WKPDFView> retainedSelf = self;
459     typeof(completionHandler) completionHandlerCopy = Block_copy(completionHandler);
460
461     dispatch_async(_findQueue, [pages, string, findOptions, optionsAffectingResults, maxCount, computeMatchesOperationID, retainedSelf, completionHandlerCopy] {
462         Vector<RetainPtr<UIPDFSelection>> matches;
463
464         for (unsigned pageIndex = 0; pageIndex < pages.size(); ++pageIndex) {
465             UIPDFSelection *match = nullptr;
466             while ((match = [pages[pageIndex].page findString:string fromSelection:match options:findOptions])) {
467                 matches.append(match);
468                 if (matches.size() > maxCount)
469                     goto maxCountExceeded;
470
471                 // If we've enqueued another search, cancel this one.
472                 if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) {
473                     dispatch_async(dispatch_get_main_queue(), [completionHandlerCopy] {
474                         completionHandlerCopy(NO);
475                         Block_release(completionHandlerCopy);
476                     });
477                     return;
478                 }
479             };
480         }
481
482     maxCountExceeded:
483         dispatch_async(dispatch_get_main_queue(), [computeMatchesOperationID, string, optionsAffectingResults, maxCount, matches, completionHandlerCopy, retainedSelf] {
484
485             // If another search has been enqueued in the meantime, ignore this result.
486             if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) {
487                 Block_release(completionHandlerCopy);
488                 return;
489             }
490
491             retainedSelf->_cachedFindString = string;
492             retainedSelf->_cachedFindOptionsAffectingResults = optionsAffectingResults;
493             retainedSelf->_cachedFindMaximumCount = maxCount;
494             retainedSelf->_cachedFindMatches = matches;
495
496             retainedSelf->_nextCachedFindString = nil;
497
498             completionHandlerCopy(YES);
499             Block_release(completionHandlerCopy);
500         });
501     });
502 }
503
504 - (void)web_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
505 {
506     RefPtr<WebKit::WebPageProxy> page = _webView->_page;
507     [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
508         if (!success)
509             return;
510         page->findClient().didCountStringMatches(page.get(), string, _cachedFindMatches.size());
511     }];
512 }
513
514 - (void)_didFindMatch:(UIPDFSelection *)match
515 {
516     for (auto& pageInfo : _pages) {
517         if (pageInfo.page == match.page) {
518             [self _ensureViewForPage:pageInfo];
519
520             [pageInfo.view highlightSearchSelection:match animated:NO];
521
522             _currentFindPageIndex = pageInfo.index;
523             _currentFindSelection = match;
524
525             CGRect zoomRect = [pageInfo.view convertRectFromPDFPageSpace:match.bounds];
526             [self zoom:pageInfo.view.get() to:zoomRect atPoint:CGPointZero kind:kUIPDFObjectKindText];
527
528             return;
529         }
530     }
531 }
532
533 - (void)web_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
534 {
535     if (_currentFindSelection)
536         [_pages[_currentFindPageIndex].view clearSearchHighlights];
537
538     RetainPtr<UIPDFSelection> previousFindSelection = _currentFindSelection;
539     unsigned previousFindPageIndex = 0;
540     if (previousFindSelection) {
541         previousFindPageIndex = _currentFindPageIndex;
542         if (![_cachedFindString isEqualToString:string]) {
543             NSUInteger location = [_currentFindSelection startIndex];
544             if (location)
545                 previousFindSelection = adoptNS([[UIPDFSelection alloc] initWithPage:[_currentFindSelection page] fromIndex:location - 1 toIndex:location]);
546         }
547     }
548
549     NSStringCompareOptions findOptions = stringCompareOptions(options);
550     bool backwards = (options & _WKFindOptionsBackwards);
551     RefPtr<WebKit::WebPageProxy> page = _webView->_page;
552
553     [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
554         if (!success)
555             return;
556
557         unsigned pageIndex = previousFindPageIndex;
558         for (unsigned i = 0; i < _pages.size(); ++i) {
559             UIPDFSelection *match = [_pages[pageIndex].page findString:string fromSelection:(pageIndex == previousFindPageIndex ? previousFindSelection.get() : nil) options:findOptions];
560
561             if (!match) {
562                 if (!pageIndex && backwards)
563                     pageIndex = _pages.size() - 1;
564                 else if (pageIndex == _pages.size() - 1 && !backwards)
565                     pageIndex = 0;
566                 else
567                     pageIndex += backwards ? -1 : 1;
568                 continue;
569             }
570
571             [self _didFindMatch:match];
572
573             if (_cachedFindMatches.size() <= maxCount) {
574                 _currentFindMatchIndex = 0;
575                 for (const auto& knownMatch : _cachedFindMatches) {
576                     if (match.stringRange.location == [knownMatch stringRange].location && match.stringRange.length == [knownMatch stringRange].length) {
577                         page->findClient().didFindString(page.get(), string, _cachedFindMatches.size(), _currentFindMatchIndex);
578                         break;
579                     }
580                     _currentFindMatchIndex++;
581                 }
582             }
583
584             return;
585         }
586         
587         page->findClient().didFailToFindString(page.get(), string);
588     }];
589 }
590
591 - (void)web_hideFindUI
592 {
593     if (_currentFindSelection)
594         [_pages[_currentFindPageIndex].view clearSearchHighlights];
595
596     _currentFindSelection = nullptr;
597     _cachedFindString = nullptr;
598     _cachedFindMatches.clear();
599 }
600
601 #pragma mark UIPDFPageViewDelegate
602
603 - (void)zoom:(UIPDFPageView *)pageView to:(CGRect)targetRect atPoint:(CGPoint)origin kind:(UIPDFObjectKind)kind
604 {
605     _isStartingZoom = YES;
606
607     BOOL isImage = kind == kUIPDFObjectKindGraphic;
608
609     if (!isImage)
610         targetRect = CGRectInset(targetRect, smartMagnificationElementPadding * targetRect.size.width, smartMagnificationElementPadding * targetRect.size.height);
611
612     CGRect rectInDocumentCoordinates = [pageView convertRect:targetRect toView:self];
613     CGPoint originInDocumentCoordinates = [pageView convertPoint:origin toView:self];
614
615     [_webView _zoomToRect:rectInDocumentCoordinates withOrigin:originInDocumentCoordinates fitEntireRect:isImage minimumScale:pdfMinimumZoomScale maximumScale:pdfMaximumZoomScale minimumScrollDistance:0];
616
617     _isStartingZoom = NO;
618 }
619
620 - (void)resetZoom:(UIPDFPageView *)pageView
621 {
622     [self _resetZoomAnimated:YES];
623 }
624
625 #pragma mark UIPDFAnnotationControllerDelegate
626
627 - (void)annotation:(UIPDFAnnotation *)annotation wasTouchedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller
628 {
629     if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]])
630         return;
631
632     UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation;
633     RetainPtr<NSURL> url = [self _URLForLinkAnnotation:linkAnnotation];
634     if (!url)
635         return;
636
637     CGPoint documentPoint = [controller.pageView convertPoint:point toView:self];
638     CGPoint screenPoint = [self.window convertPoint:[self convertPoint:documentPoint toView:nil] toWindow:nil];
639     RetainPtr<WKWebView> retainedWebView = _webView;
640
641     [self _highlightLinkAnnotation:linkAnnotation forDuration:.2 completionHandler:^{
642         retainedWebView->_page->navigateToURLWithSimulatedClick([url absoluteString], roundedIntPoint(documentPoint), roundedIntPoint(screenPoint));
643     }];
644 }
645
646 - (void)annotation:(UIPDFAnnotation *)annotation isBeingPressedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller
647 {
648     if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]])
649         return;
650
651     UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation;
652     NSURL *url = [self _URLForLinkAnnotation:linkAnnotation];
653     if (!url)
654         return;
655
656     _positionInformation.url = url.absoluteString;
657     _positionInformation.point = roundedIntPoint([controller.pageView convertPoint:point toView:self]);
658     _positionInformation.bounds = roundedIntRect([self convertRect:[controller.pageView convertRectFromPDFPageSpace:annotation.Rect] fromView:controller.pageView]);
659
660     [self _highlightLinkAnnotation:linkAnnotation forDuration:.75 completionHandler:^{
661         [_actionSheetAssistant showLinkSheet];
662     }];
663 }
664
665 #pragma mark WKActionSheetAssistantDelegate
666
667 - (const WebKit::InteractionInformationAtPosition&)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
668 {
669     return _positionInformation;
670 }
671
672 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant performAction:(WebKit::SheetAction)action
673 {
674     if (action != WebKit::SheetAction::Copy)
675         return;
676
677     NSDictionary *representations = @{
678         (NSString *)kUTTypeUTF8PlainText : _positionInformation.url,
679         (NSString *)kUTTypeURL : [NSURL URLWithString:_positionInformation.url]
680     };
681
682     [UIPasteboard generalPasteboard].items = @[ representations ];
683 }
684
685 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant openElementAtLocation:(CGPoint)location
686 {
687     CGPoint screenPoint = [self.window convertPoint:[self convertPoint:location toView:nil] toWindow:nil];
688     _webView->_page->navigateToURLWithSimulatedClick(_positionInformation.url, roundedIntPoint(location), roundedIntPoint(screenPoint));
689 }
690
691 - (RetainPtr<NSArray>)actionSheetAssistant:(WKActionSheetAssistant *)assistant decideActionsForElement:(_WKActivatedElementInfo *)element defaultActions:(RetainPtr<NSArray>)defaultActions
692 {
693     return _webView->_page->uiClient().actionsForElement(element, WTF::move(defaultActions));
694 }
695
696 @end
697
698 #endif /* PLATFORM(IOS) */