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