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