Tapping and holding a link should have a share option
[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 <WebCore/LocalizedStrings.h>
44 #import <WebCore/WebCoreNSURLExtras.h>
45 #import <wtf/RetainPtr.h>
46 #import <wtf/Vector.h>
47
48 using namespace WebCore;
49
50 const CGFloat pdfPageMargin = 8;
51 const CGFloat pdfMinimumZoomScale = 1;
52 const CGFloat pdfMaximumZoomScale = 5;
53
54 const CGFloat passwordEntryFieldPadding = 10;
55
56 const float overdrawHeightMultiplier = 1.5;
57
58 static const CGFloat smartMagnificationElementPadding = 0.05;
59
60 typedef struct {
61     CGRect frame;
62     RetainPtr<UIPDFPageView> view;
63     RetainPtr<UIPDFPage> page;
64     unsigned index;
65 } PDFPageInfo;
66
67 @interface WKPDFView ()
68 - (void)_resetZoomAnimated:(BOOL)animated;
69 @end
70
71 @implementation WKPDFView {
72     RetainPtr<CGPDFDocumentRef> _cgPDFDocument;
73     RetainPtr<UIPDFDocument> _pdfDocument;
74     RetainPtr<NSString> _suggestedFilename;
75     RetainPtr<WKPDFPageNumberIndicator> _pageNumberIndicator;
76
77     RetainPtr<UIDocumentPasswordView> _passwordView;
78
79     Vector<PDFPageInfo> _pages;
80     unsigned _centerPageNumber;
81
82     CGSize _minimumSize;
83     CGSize _overlaidAccessoryViewsInset;
84     WKWebView *_webView;
85     UIScrollView *_scrollView;
86     UIView *_fixedOverlayView;
87
88     BOOL _isStartingZoom;
89     BOOL _isPerformingSameDocumentNavigation;
90
91     RetainPtr<WKActionSheetAssistant> _actionSheetAssistant;
92     WebKit::InteractionInformationAtPosition _positionInformation;
93
94     unsigned _currentFindPageIndex;
95     unsigned _currentFindMatchIndex;
96     RetainPtr<UIPDFSelection> _currentFindSelection;
97
98     RetainPtr<NSString> _cachedFindString;
99     Vector<RetainPtr<UIPDFSelection>> _cachedFindMatches;
100     unsigned _cachedFindMaximumCount;
101     _WKFindOptions _cachedFindOptionsAffectingResults;
102
103     std::atomic<unsigned> _nextComputeMatchesOperationID;
104     RetainPtr<NSString> _nextCachedFindString;
105     unsigned _nextCachedFindMaximumCount;
106     _WKFindOptions _nextCachedFindOptionsAffectingResults;
107
108     dispatch_queue_t _findQueue;
109
110     RetainPtr<UIWKSelectionAssistant> _webSelectionAssistant;
111 }
112
113 - (instancetype)web_initWithFrame:(CGRect)frame webView:(WKWebView *)webView
114 {
115     if (!(self = [super initWithFrame:frame]))
116         return nil;
117
118     self.backgroundColor = [UIColor grayColor];
119
120     _webView = webView;
121
122     _scrollView = webView.scrollView;
123     [_scrollView setMinimumZoomScale:pdfMinimumZoomScale];
124     [_scrollView setMaximumZoomScale:pdfMaximumZoomScale];
125     [_scrollView setBackgroundColor:[UIColor grayColor]];
126
127     _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]);
128     [_actionSheetAssistant setDelegate:self];
129
130     _findQueue = dispatch_queue_create("com.apple.WebKit.WKPDFViewComputeMatchesQueue", DISPATCH_QUEUE_SERIAL);
131
132     return self;
133 }
134
135 - (void)dealloc
136 {
137     [[NSNotificationCenter defaultCenter] removeObserver:self];
138
139     [self _clearPages];
140     [_pageNumberIndicator removeFromSuperview];
141     dispatch_release(_findQueue);
142     [super dealloc];
143 }
144
145 - (NSString *)suggestedFilename
146 {
147     return _suggestedFilename.get();
148 }
149
150 - (CGPDFDocumentRef)pdfDocument
151 {
152     return [_pdfDocument CGDocument];
153 }
154
155 - (void)_clearPages
156 {
157     for (auto& page : _pages) {
158         [page.view removeFromSuperview];
159         [page.view setDelegate:nil];
160         [[page.view annotationController] setDelegate:nil];
161     }
162     
163     _pages.clear();
164 }
165
166 - (void)_didLoadPDFDocument
167 {
168     _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:_cgPDFDocument.get()]);
169
170     // FIXME: Restore the scroll position and page scale if navigating from the back/forward list.
171
172     [self _computePageAndDocumentFrames];
173     [self _revalidateViews];
174     [self _scrollToFragment:_webView.URL.fragment];
175 }
176
177 - (void)web_setContentProviderData:(NSData *)data suggestedFilename:(NSString *)filename
178 {
179     _suggestedFilename = adoptNS([filename copy]);
180
181     [self _clearPages];
182
183     RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
184     _cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
185
186     if (!_cgPDFDocument)
187         return;
188
189     if (CGPDFDocumentIsUnlocked(_cgPDFDocument.get())) {
190         [self _didLoadPDFDocument];
191         return;
192     }
193
194     [self _showPasswordEntryField];
195 }
196
197 - (void)web_setMinimumSize:(CGSize)size
198 {
199     if (_passwordView) {
200         [self _updatePasswordEntryField];
201         return;
202     }
203
204     _minimumSize = size;
205
206     CGFloat oldDocumentLeftFraction = 0;
207     CGFloat oldDocumentTopFraction = 0;
208     CGSize contentSize = _scrollView.contentSize;
209     if (contentSize.width && contentSize.height) {
210         CGPoint contentOffset = _scrollView.contentOffset;
211         UIEdgeInsets contentInset = _scrollView.contentInset;
212         oldDocumentLeftFraction = (contentOffset.x + contentInset.left) / contentSize.width;
213         oldDocumentTopFraction = (contentOffset.y + contentInset.top) / contentSize.height;
214     }
215
216     [self _computePageAndDocumentFrames];
217
218     // FIXME: This dispatch_async is unnecessary except to work around rdar://problem/15035620.
219     // Once that is resolved, we should do the setContentOffset without the dispatch_async.
220     RetainPtr<WKPDFView> retainedSelf = self;
221     dispatch_async(dispatch_get_main_queue(), [retainedSelf, oldDocumentLeftFraction, oldDocumentTopFraction] {
222         CGSize contentSize = retainedSelf->_scrollView.contentSize;
223         UIEdgeInsets contentInset = retainedSelf->_scrollView.contentInset;
224         [retainedSelf->_scrollView setContentOffset:CGPointMake((oldDocumentLeftFraction * contentSize.width) - contentInset.left, (oldDocumentTopFraction * contentSize.height) - contentInset.top) animated:NO];
225
226         [retainedSelf _revalidateViews];
227     });
228 }
229
230 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
231 {
232     if (scrollView.isZoomBouncing || scrollView._isAnimatingZoom)
233         return;
234
235     [self _revalidateViews];
236
237     if (!_isPerformingSameDocumentNavigation)
238         [_pageNumberIndicator show];
239 }
240
241 - (void)_ensureViewForPage:(PDFPageInfo&)pageInfo
242 {
243     if (pageInfo.view)
244         return;
245
246     pageInfo.view = adoptNS([[UIPDFPageView alloc] initWithPage:pageInfo.page.get() tiledContent:YES]);
247     [pageInfo.view setUseBackingLayer:YES];
248     [pageInfo.view setDelegate:self];
249     [[pageInfo.view annotationController] setDelegate:self];
250     [self addSubview:pageInfo.view.get()];
251
252     [pageInfo.view setFrame:pageInfo.frame];
253     [pageInfo.view contentLayer].contentsScale = self.window.screen.scale;
254 }
255
256 - (void)_revalidateViews
257 {
258     if (_isStartingZoom)
259         return;
260
261     CGRect targetRect = [_scrollView convertRect:_scrollView.bounds toView:self];
262
263     // We apply overdraw after applying scale in order to avoid excessive
264     // memory use caused by scaling the overdraw.
265     CGRect targetRectWithOverdraw = CGRectInset(targetRect, 0, -targetRect.size.height * overdrawHeightMultiplier);
266     CGRect targetRectForCenterPage = CGRectInset(targetRect, 0, targetRect.size.height / 2 - pdfPageMargin * 2);
267
268     _centerPageNumber = 0;
269
270     for (auto& pageInfo : _pages) {
271         if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw) && pageInfo.index != _currentFindPageIndex) {
272             [pageInfo.view removeFromSuperview];
273             pageInfo.view = nullptr;
274             continue;
275         }
276
277         if (!_centerPageNumber && CGRectIntersectsRect(pageInfo.frame, targetRectForCenterPage))
278             _centerPageNumber = pageInfo.index + 1;
279
280         [self _ensureViewForPage:pageInfo];
281     }
282
283     [self _updatePageNumberIndicator];
284 }
285
286 - (CGPoint)_offsetForPageNumberIndicator
287 {
288     UIEdgeInsets contentInset = [_webView _computedContentInset];
289     return CGPointMake(contentInset.left, contentInset.top + _overlaidAccessoryViewsInset.height);
290 }
291
292 - (void)_updatePageNumberIndicator
293 {
294     if (_isPerformingSameDocumentNavigation)
295         return;
296
297     if (!_pageNumberIndicator)
298         _pageNumberIndicator = adoptNS([[WKPDFPageNumberIndicator alloc] initWithFrame:CGRectZero]);
299
300     [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
301
302     [_pageNumberIndicator setCurrentPageNumber:_centerPageNumber];
303     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:NO];
304 }
305
306 - (void)web_setOverlaidAccessoryViewsInset:(CGSize)inset
307 {
308     _overlaidAccessoryViewsInset = inset;
309     [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:YES];
310 }
311
312 - (void)web_computedContentInsetDidChange
313 {
314     [self _updatePageNumberIndicator];
315 }
316
317 - (void)web_setFixedOverlayView:(UIView *)fixedOverlayView
318 {
319     _fixedOverlayView = fixedOverlayView;
320
321     if (_pageNumberIndicator)
322         [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
323 }
324
325 - (void)_scrollToFragment:(NSString *)fragment
326 {
327     if (![fragment hasPrefix:@"page"])
328         return;
329
330     NSInteger pageIndex = [[fragment substringFromIndex:4] integerValue] - 1;
331     if (pageIndex < 0 || static_cast<std::size_t>(pageIndex) >= _pages.size())
332         return;
333
334     _isPerformingSameDocumentNavigation = YES;
335
336     [_pageNumberIndicator hide];
337     [self _resetZoomAnimated:NO];
338
339     // Ensure that the page margin is visible below the content inset.
340     const CGFloat verticalOffset = _pages[pageIndex].frame.origin.y - _webView._computedContentInset.top - pdfPageMargin;
341     [_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x, verticalOffset) animated:NO];
342
343     _isPerformingSameDocumentNavigation = NO;
344 }
345
346 - (void)web_didSameDocumentNavigation:(WKSameDocumentNavigationType)navigationType
347 {
348     // Check for kWKSameDocumentNavigationSessionStatePop instead of kWKSameDocumentNavigationAnchorNavigation since the
349     // latter is only called once when navigating to the same anchor in succession. If the user navigates to a page
350     // then scrolls back and clicks on the same link a second time, we want to scroll again.
351     if (navigationType != kWKSameDocumentNavigationSessionStatePop)
352         return;
353
354     // FIXME: restore the scroll position and page scale if navigating back from a fragment.
355
356     [self _scrollToFragment:_webView.URL.fragment];
357 }
358
359 - (void)_computePageAndDocumentFrames
360 {
361     if (_passwordView)
362         return;
363
364     NSUInteger pageCount = [_pdfDocument numberOfPages];
365     [_pageNumberIndicator setPageCount:pageCount];
366     
367     [self _clearPages];
368
369     _pages.reserveCapacity(pageCount);
370
371     CGRect pageFrame = CGRectMake(0, 0, _minimumSize.width, _minimumSize.height);
372     for (NSUInteger pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
373         UIPDFPage *page = [_pdfDocument pageAtIndex:pageIndex];
374         if (!page)
375             continue;
376
377         CGSize pageSize = [page cropBoxAccountForRotation].size;
378         pageFrame.size.height = pageSize.height / pageSize.width * pageFrame.size.width;
379         CGRect pageFrameWithMarginApplied = CGRectInset(pageFrame, pdfPageMargin, pdfPageMargin);
380
381         PDFPageInfo pageInfo;
382         pageInfo.page = page;
383         pageInfo.frame = pageFrameWithMarginApplied;
384         pageInfo.index = pageIndex;
385         _pages.append(pageInfo);
386         pageFrame.origin.y += pageFrame.size.height - pdfPageMargin;
387     }
388
389     CGFloat scale = _scrollView.zoomScale;
390     CGRect newFrame = [self frame];
391     newFrame.size.width = _minimumSize.width * scale;
392     newFrame.size.height = std::max(pageFrame.origin.y + pdfPageMargin, _minimumSize.height) * scale;
393
394     [self setFrame:newFrame];
395     [_scrollView setContentSize:newFrame.size];
396 }
397
398 - (void)_resetZoomAnimated:(BOOL)animated
399 {
400     _isStartingZoom = YES;
401
402     CGRect scrollViewBounds = _scrollView.bounds;
403     CGPoint centerOfPageInDocumentCoordinates = [_scrollView convertPoint:CGPointMake(CGRectGetMidX(scrollViewBounds), CGRectGetMidY(scrollViewBounds)) toView:self];
404     [_webView _zoomOutWithOrigin:centerOfPageInDocumentCoordinates animated:animated];
405
406     _isStartingZoom = NO;
407 }
408
409 - (void)_highlightLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation forDuration:(NSTimeInterval)duration completionHandler:(void (^)(void))completionHandler
410 {
411     static const CGFloat highlightBorderRadius = 3;
412     static const CGFloat highlightColorComponent = 26.0 / 255;
413     static UIColor *highlightColor = [[UIColor alloc] initWithRed:highlightColorComponent green:highlightColorComponent blue:highlightColorComponent alpha:0.3];
414
415     UIPDFPageView *pageView = linkAnnotation.annotationController.pageView;
416     CGRect highlightViewFrame = [self convertRect:[pageView convertRectFromPDFPageSpace:linkAnnotation.Rect] fromView:pageView];
417     RetainPtr<_UIHighlightView> highlightView = adoptNS([[_UIHighlightView alloc] initWithFrame:CGRectInset(highlightViewFrame, -highlightBorderRadius, -highlightBorderRadius)]);
418     [highlightView setOpaque:NO];
419     [highlightView setCornerRadius:highlightBorderRadius];
420     [highlightView setColor:highlightColor];
421
422     ASSERT(isMainThread());
423     [self addSubview:highlightView.get()];
424     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
425         [highlightView removeFromSuperview];
426         completionHandler();
427     });
428 }
429
430 - (NSURL *)_URLForLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation
431 {
432     NSURL *documentURL = _webView.URL;
433
434     if (NSURL *url = linkAnnotation.url)
435         return [NSURL URLWithString:url.relativeString relativeToURL:documentURL];
436
437     if (NSUInteger pageNumber = linkAnnotation.pageNumber) {
438         String anchorString = ASCIILiteral("#page");
439         anchorString.append(String::number(pageNumber));
440         return [NSURL URLWithString:anchorString relativeToURL:documentURL];
441     }
442
443     return nil;
444 }
445
446 #pragma mark Find-in-Page
447
448 static NSStringCompareOptions stringCompareOptions(_WKFindOptions options)
449 {
450     NSStringCompareOptions findOptions = 0;
451     if (options & _WKFindOptionsCaseInsensitive)
452         findOptions |= NSCaseInsensitiveSearch;
453     if (options & _WKFindOptionsBackwards)
454         findOptions |= NSBackwardsSearch;
455     return findOptions;
456 }
457
458 - (void)_computeMatchesForString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount completionHandler:(void (^)(BOOL success))completionHandler
459 {
460     if (!_pdfDocument) {
461         completionHandler(NO);
462         return;
463     }
464
465     _WKFindOptions optionsAffectingResults = options & ~_WKFindOptionsIrrelevantForBatchResults;
466
467     // If this search is equivalent to the currently cached search, bail and call the completion handler, because the existing cached results are valid.
468     if (!_cachedFindMatches.isEmpty() && [_cachedFindString isEqualToString:string] && _cachedFindOptionsAffectingResults == optionsAffectingResults && _cachedFindMaximumCount == maxCount) {
469         // Also, cancel any running search, because it will eventually replace the now-valid results.
470         ++_nextComputeMatchesOperationID;
471
472         completionHandler(YES);
473         return;
474     }
475
476     // 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.
477     if ([_nextCachedFindString isEqualToString:string] && _nextCachedFindOptionsAffectingResults == optionsAffectingResults && _nextCachedFindMaximumCount == maxCount) {
478         completionHandler(NO);
479         return;
480     }
481
482     NSStringCompareOptions findOptions = stringCompareOptions(optionsAffectingResults);
483
484     Vector<PDFPageInfo> pages = _pages;
485
486     unsigned computeMatchesOperationID = ++_nextComputeMatchesOperationID;
487     _nextCachedFindString = string;
488     _nextCachedFindOptionsAffectingResults = optionsAffectingResults;
489     _nextCachedFindMaximumCount = maxCount;
490
491     RetainPtr<WKPDFView> retainedSelf = self;
492     typeof(completionHandler) completionHandlerCopy = Block_copy(completionHandler);
493
494     dispatch_async(_findQueue, [pages, string, findOptions, optionsAffectingResults, maxCount, computeMatchesOperationID, retainedSelf, completionHandlerCopy] {
495         Vector<RetainPtr<UIPDFSelection>> matches;
496
497         for (unsigned pageIndex = 0; pageIndex < pages.size(); ++pageIndex) {
498             UIPDFSelection *match = nullptr;
499             while ((match = [pages[pageIndex].page findString:string fromSelection:match options:findOptions])) {
500                 matches.append(match);
501                 if (matches.size() > maxCount)
502                     goto maxCountExceeded;
503
504                 // If we've enqueued another search, cancel this one.
505                 if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) {
506                     dispatch_async(dispatch_get_main_queue(), [completionHandlerCopy] {
507                         completionHandlerCopy(NO);
508                         Block_release(completionHandlerCopy);
509                     });
510                     return;
511                 }
512             };
513         }
514
515     maxCountExceeded:
516         dispatch_async(dispatch_get_main_queue(), [computeMatchesOperationID, string, optionsAffectingResults, maxCount, matches, completionHandlerCopy, retainedSelf] {
517
518             // If another search has been enqueued in the meantime, ignore this result.
519             if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) {
520                 Block_release(completionHandlerCopy);
521                 return;
522             }
523
524             retainedSelf->_cachedFindString = string;
525             retainedSelf->_cachedFindOptionsAffectingResults = optionsAffectingResults;
526             retainedSelf->_cachedFindMaximumCount = maxCount;
527             retainedSelf->_cachedFindMatches = matches;
528
529             retainedSelf->_nextCachedFindString = nil;
530
531             completionHandlerCopy(YES);
532             Block_release(completionHandlerCopy);
533         });
534     });
535 }
536
537 - (void)web_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
538 {
539     RefPtr<WebKit::WebPageProxy> page = _webView->_page;
540     [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
541         if (!success)
542             return;
543         page->findClient().didCountStringMatches(page.get(), string, _cachedFindMatches.size());
544     }];
545 }
546
547 - (void)_didFindMatch:(UIPDFSelection *)match
548 {
549     for (auto& pageInfo : _pages) {
550         if (pageInfo.page == match.page) {
551             [self _ensureViewForPage:pageInfo];
552
553             [pageInfo.view highlightSearchSelection:match animated:NO];
554
555             _currentFindPageIndex = pageInfo.index;
556             _currentFindSelection = match;
557
558             CGRect zoomRect = [pageInfo.view convertRectFromPDFPageSpace:match.bounds];
559             [self zoom:pageInfo.view.get() to:zoomRect atPoint:CGPointZero kind:kUIPDFObjectKindText];
560
561             return;
562         }
563     }
564 }
565
566 - (void)web_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
567 {
568     if (_currentFindSelection)
569         [_pages[_currentFindPageIndex].view clearSearchHighlights];
570
571     RetainPtr<UIPDFSelection> previousFindSelection = _currentFindSelection;
572     unsigned previousFindPageIndex = 0;
573     if (previousFindSelection) {
574         previousFindPageIndex = _currentFindPageIndex;
575         if (![_cachedFindString isEqualToString:string]) {
576             NSUInteger location = [_currentFindSelection startIndex];
577             if (location)
578                 previousFindSelection = adoptNS([[UIPDFSelection alloc] initWithPage:[_currentFindSelection page] fromIndex:location - 1 toIndex:location]);
579         }
580     }
581
582     NSStringCompareOptions findOptions = stringCompareOptions(options);
583     bool backwards = (options & _WKFindOptionsBackwards);
584     RefPtr<WebKit::WebPageProxy> page = _webView->_page;
585
586     [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
587         if (!success)
588             return;
589
590         unsigned pageIndex = previousFindPageIndex;
591         for (unsigned i = 0; i < _pages.size(); ++i) {
592             UIPDFSelection *match = [_pages[pageIndex].page findString:string fromSelection:(pageIndex == previousFindPageIndex ? previousFindSelection.get() : nil) options:findOptions];
593
594             if (!match) {
595                 if (!pageIndex && backwards)
596                     pageIndex = _pages.size() - 1;
597                 else if (pageIndex == _pages.size() - 1 && !backwards)
598                     pageIndex = 0;
599                 else
600                     pageIndex += backwards ? -1 : 1;
601                 continue;
602             }
603
604             [self _didFindMatch:match];
605
606             if (_cachedFindMatches.size() <= maxCount) {
607                 _currentFindMatchIndex = 0;
608                 for (const auto& knownMatch : _cachedFindMatches) {
609                     if (match.stringRange.location == [knownMatch stringRange].location && match.stringRange.length == [knownMatch stringRange].length) {
610                         page->findClient().didFindString(page.get(), string, _cachedFindMatches.size(), _currentFindMatchIndex);
611                         break;
612                     }
613                     _currentFindMatchIndex++;
614                 }
615             }
616
617             return;
618         }
619         
620         page->findClient().didFailToFindString(page.get(), string);
621     }];
622 }
623
624 - (void)web_hideFindUI
625 {
626     if (_currentFindSelection)
627         [_pages[_currentFindPageIndex].view clearSearchHighlights];
628
629     _currentFindSelection = nullptr;
630     _cachedFindString = nullptr;
631     _cachedFindMatches.clear();
632 }
633
634 #pragma mark UIPDFPageViewDelegate
635
636 - (void)zoom:(UIPDFPageView *)pageView to:(CGRect)targetRect atPoint:(CGPoint)origin kind:(UIPDFObjectKind)kind
637 {
638     _isStartingZoom = YES;
639
640     BOOL isImage = kind == kUIPDFObjectKindGraphic;
641
642     if (!isImage)
643         targetRect = CGRectInset(targetRect, smartMagnificationElementPadding * targetRect.size.width, smartMagnificationElementPadding * targetRect.size.height);
644
645     CGRect rectInDocumentCoordinates = [pageView convertRect:targetRect toView:self];
646     CGPoint originInDocumentCoordinates = [pageView convertPoint:origin toView:self];
647
648     [_webView _zoomToRect:rectInDocumentCoordinates withOrigin:originInDocumentCoordinates fitEntireRect:isImage minimumScale:pdfMinimumZoomScale maximumScale:pdfMaximumZoomScale minimumScrollDistance:0];
649
650     _isStartingZoom = NO;
651 }
652
653 - (void)resetZoom:(UIPDFPageView *)pageView
654 {
655     [self _resetZoomAnimated:YES];
656 }
657
658 #pragma mark UIPDFAnnotationControllerDelegate
659
660 - (void)annotation:(UIPDFAnnotation *)annotation wasTouchedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller
661 {
662     if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]])
663         return;
664
665     UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation;
666     RetainPtr<NSURL> url = [self _URLForLinkAnnotation:linkAnnotation];
667     if (!url)
668         return;
669
670     CGPoint documentPoint = [controller.pageView convertPoint:point toView:self];
671     CGPoint screenPoint = [self.window convertPoint:[self convertPoint:documentPoint toView:nil] toWindow:nil];
672     RetainPtr<WKWebView> retainedWebView = _webView;
673
674     [self _highlightLinkAnnotation:linkAnnotation forDuration:.2 completionHandler:^{
675         retainedWebView->_page->navigateToPDFLinkWithSimulatedClick([url absoluteString], roundedIntPoint(documentPoint), roundedIntPoint(screenPoint));
676     }];
677 }
678
679 - (void)annotation:(UIPDFAnnotation *)annotation isBeingPressedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller
680 {
681     if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]])
682         return;
683
684     UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation;
685     NSURL *url = [self _URLForLinkAnnotation:linkAnnotation];
686     if (!url)
687         return;
688
689     _positionInformation.url = url.absoluteString;
690     _positionInformation.point = roundedIntPoint([controller.pageView convertPoint:point toView:self]);
691     _positionInformation.bounds = roundedIntRect([self convertRect:[controller.pageView convertRectFromPDFPageSpace:annotation.Rect] fromView:controller.pageView]);
692
693     [self _highlightLinkAnnotation:linkAnnotation forDuration:.75 completionHandler:^{
694         [_actionSheetAssistant showLinkSheet];
695     }];
696 }
697
698 #pragma mark WKActionSheetAssistantDelegate
699
700 - (const WebKit::InteractionInformationAtPosition&)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
701 {
702     return _positionInformation;
703 }
704
705 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant performAction:(WebKit::SheetAction)action
706 {
707     if (action != WebKit::SheetAction::Copy)
708         return;
709
710     NSDictionary *representations = @{
711         (NSString *)kUTTypeUTF8PlainText : _positionInformation.url,
712         (NSString *)kUTTypeURL : [NSURL URLWithString:_positionInformation.url]
713     };
714
715     [UIPasteboard generalPasteboard].items = @[ representations ];
716 }
717
718 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant openElementAtLocation:(CGPoint)location
719 {
720     CGPoint screenPoint = [self.window convertPoint:[self convertPoint:location toView:nil] toWindow:nil];
721     _webView->_page->navigateToPDFLinkWithSimulatedClick(_positionInformation.url, roundedIntPoint(location), roundedIntPoint(screenPoint));
722 }
723
724 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant shareElementWithURL:(NSURL *)url rect:(CGRect)boundingRect
725 {
726     _webSelectionAssistant = adoptNS([[UIWKSelectionAssistant alloc] initWithView:self]);
727     [_webSelectionAssistant showShareSheetFor:userVisibleString(url) fromRect:boundingRect];
728     _webSelectionAssistant = nil;
729 }
730
731 #if HAVE(APP_LINKS)
732 - (BOOL)actionSheetAssistant:(WKActionSheetAssistant *)assistant shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element
733 {
734     return _webView->_page->uiClient().shouldIncludeAppLinkActionsForElement(element);
735 }
736 #endif
737
738 - (RetainPtr<NSArray>)actionSheetAssistant:(WKActionSheetAssistant *)assistant decideActionsForElement:(_WKActivatedElementInfo *)element defaultActions:(RetainPtr<NSArray>)defaultActions
739 {
740     return _webView->_page->uiClient().actionsForElement(element, WTF::move(defaultActions));
741 }
742
743 #pragma mark Password protection UI
744
745 - (void)_updatePasswordEntryField
746 {
747     [_passwordView setFrame:CGRectMake(0, 0, _webView.bounds.size.width, _webView.bounds.size.height)];
748     [_scrollView setContentSize:[_passwordView bounds].size];
749 }
750
751 - (void)_keyboardDidShow:(NSNotification *)notification
752 {
753     UITextField *passwordField = [_passwordView passwordField];
754     if (!passwordField.isEditing)
755         return;
756
757     CGRect keyboardRect = [UIPeripheralHost visiblePeripheralFrame];
758     if (CGRectIsEmpty(keyboardRect))
759         return;
760
761     UIWindow *window = _scrollView.window;
762     keyboardRect = [window convertRect:keyboardRect fromWindow:nil];
763     keyboardRect = [_scrollView convertRect:keyboardRect fromView:window];
764
765     CGRect passwordFieldFrame = [passwordField convertRect:passwordField.bounds toView:_scrollView];
766
767     CGSize contentSize = [_passwordView bounds].size;
768     contentSize.height += CGRectGetHeight(keyboardRect);
769     [_scrollView setContentSize:contentSize];
770
771     if (CGRectIntersectsRect(passwordFieldFrame, keyboardRect)) {
772         CGFloat yDelta = CGRectGetMaxY(passwordFieldFrame) - CGRectGetMinY(keyboardRect);
773
774         CGPoint contentOffset = _scrollView.contentOffset;
775         contentOffset.y += yDelta + passwordEntryFieldPadding;
776
777         [_scrollView setContentOffset:contentOffset animated:YES];
778     }
779 }
780
781 - (void)_showPasswordEntryField
782 {
783     [_scrollView setMinimumZoomScale:1];
784     [_scrollView setMaximumZoomScale:1];
785     [_scrollView setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
786
787     _passwordView = adoptNS([[UIDocumentPasswordView alloc] initWithDocumentName:_suggestedFilename.get()]);
788     [_passwordView setPasswordDelegate:self];
789
790     [self _updatePasswordEntryField];
791
792     [self addSubview:_passwordView.get()];
793 }
794
795 - (void)_hidePasswordEntryField
796 {
797     [_passwordView removeFromSuperview];
798     _passwordView = nil;
799
800     [_scrollView setMinimumZoomScale:pdfMinimumZoomScale];
801     [_scrollView setMaximumZoomScale:pdfMaximumZoomScale];
802     [_scrollView setBackgroundColor:[UIColor grayColor]];
803 }
804
805 - (void)userDidEnterPassword:(NSString *)password forPasswordView:(UIDocumentPasswordView *)passwordView
806 {
807     [self _tryToUnlockWithPassword:password];
808 }
809
810 - (void)didBeginEditingPassword:(UITextField *)passwordField inView:(UIDocumentPasswordView *)passwordView
811 {
812     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
813 }
814
815 - (void)didEndEditingPassword:(UITextField *)passwordField inView:(UIDocumentPasswordView *)passwordView
816 {
817     [_scrollView setContentSize:[_passwordView frame].size];
818     [_scrollView setContentOffset:CGPointMake(-_scrollView.contentInset.left, -_scrollView.contentInset.top) animated:YES];
819
820     [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
821 }
822
823 - (void)_didFailToUnlock
824 {
825     [[_passwordView passwordField] setText:@""];
826     UIAlertController* alert = [UIAlertController alertControllerWithTitle:WEB_UI_STRING("The document could not be opened with that password.", "PDF password failure alert message") message:@"" preferredStyle:UIAlertControllerStyleAlert];
827
828     UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:WEB_UI_STRING_KEY("OK", "OK (PDF password failure alert)", "OK button label in PDF password failure alert") style:UIAlertActionStyleDefault handler:[](UIAlertAction *) { }];
829     
830     [alert addAction:defaultAction];
831
832     [self.window.rootViewController presentViewController:alert animated:YES completion:nil];
833 }
834
835 - (BOOL)_tryToUnlockWithPassword:(NSString *)password
836 {
837     if (CGPDFDocumentUnlockWithPassword(_cgPDFDocument.get(), [password UTF8String])) {
838         [self _hidePasswordEntryField];
839         [self _didLoadPDFDocument];
840         return YES;
841     }
842
843     [self _didFailToUnlock];
844     return NO;
845 }
846
847 @end
848
849 #endif /* PLATFORM(IOS) */