2 * Copyright (C) 2014-2016 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #if PLATFORM(IOS) && ENABLE(WKPDFVIEW)
31 #import "APIFindClient.h"
32 #import "APIUIClient.h"
33 #import "ApplicationStateTracker.h"
34 #import "CorePDFSPI.h"
35 #import "DrawingAreaProxy.h"
36 #import "SessionState.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>
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"
58 using namespace WebCore;
59 using namespace WebKit;
61 const CGFloat pdfPageMargin = 8;
62 const CGFloat pdfMinimumZoomScale = 1;
63 const CGFloat pdfMaximumZoomScale = 5;
65 const float overdrawHeightMultiplier = 1.5;
67 static const CGFloat smartMagnificationElementPadding = 0.05;
71 RetainPtr<UIPDFPageView> view;
72 RetainPtr<UIPDFPage> page;
76 @interface WKPDFView ()
77 - (void)_resetZoomAnimated:(BOOL)animated;
80 @implementation WKPDFView {
81 RetainPtr<CGPDFDocumentRef> _cgPDFDocument;
82 RetainPtr<UIPDFDocument> _pdfDocument;
83 RetainPtr<NSString> _suggestedFilename;
84 RetainPtr<WKPDFPageNumberIndicator> _pageNumberIndicator;
86 Vector<PDFPageInfo> _pages;
87 unsigned _centerPageNumber;
90 CGSize _overlaidAccessoryViewsInset;
92 UIScrollView *_scrollView;
93 UIView *_fixedOverlayView;
96 BOOL _isPerformingSameDocumentNavigation;
98 RetainPtr<WKActionSheetAssistant> _actionSheetAssistant;
99 WebKit::InteractionInformationAtPosition _positionInformation;
101 unsigned _currentFindPageIndex;
102 unsigned _currentFindMatchIndex;
103 RetainPtr<UIPDFSelection> _currentFindSelection;
105 RetainPtr<NSString> _cachedFindString;
106 Vector<RetainPtr<UIPDFSelection>> _cachedFindMatches;
107 unsigned _cachedFindMaximumCount;
108 _WKFindOptions _cachedFindOptionsAffectingResults;
110 std::atomic<unsigned> _nextComputeMatchesOperationID;
111 RetainPtr<NSString> _nextCachedFindString;
112 unsigned _nextCachedFindMaximumCount;
113 _WKFindOptions _nextCachedFindOptionsAffectingResults;
115 dispatch_queue_t _findQueue;
117 RetainPtr<UIWKSelectionAssistant> _webSelectionAssistant;
119 std::unique_ptr<ApplicationStateTracker> _applicationStateTracker;
121 UIEdgeInsets _lastUnobscuredSafeAreaInset;
122 CGFloat _lastLayoutWidth;
125 - (instancetype)web_initWithFrame:(CGRect)frame webView:(WKWebView *)webView
127 if (!(self = [super initWithFrame:frame]))
130 self.backgroundColor = [UIColor grayColor];
134 _scrollView = webView.scrollView;
135 [_scrollView setMinimumZoomScale:pdfMinimumZoomScale];
136 [_scrollView setMaximumZoomScale:pdfMaximumZoomScale];
137 [_scrollView setBackgroundColor:[UIColor grayColor]];
139 _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]);
140 [_actionSheetAssistant setDelegate:self];
142 _findQueue = dispatch_queue_create("com.apple.WebKit.WKPDFViewComputeMatchesQueue", DISPATCH_QUEUE_SERIAL);
149 [[NSNotificationCenter defaultCenter] removeObserver:self];
152 [_pageNumberIndicator removeFromSuperview];
153 dispatch_release(_findQueue);
154 [_actionSheetAssistant cleanupSheet];
158 - (NSString *)web_suggestedFilename
160 return _suggestedFilename.get();
163 - (NSData *)web_dataRepresentation
165 return [(NSData *)CGDataProviderCopyData(CGPDFDocumentGetDataProvider(_cgPDFDocument.get())) autorelease];
168 static void detachViewForPage(PDFPageInfo& page)
170 [page.view removeFromSuperview];
171 [page.view setDelegate:nil];
172 [[page.view annotationController] setDelegate:nil];
178 for (auto& page : _pages)
179 detachViewForPage(page);
184 - (void)_didLoadPDFDocument
186 _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:_cgPDFDocument.get()]);
188 // FIXME: Restore the scroll position and page scale if navigating from the back/forward list.
190 [self _computePageAndDocumentFrames];
191 [self _revalidateViews];
192 [self _scrollToFragment:_webView.URL.fragment];
195 - (void)web_setContentProviderData:(NSData *)data suggestedFilename:(NSString *)filename
197 _suggestedFilename = adoptNS([filename copy]);
201 RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data));
202 _cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get()));
207 if (CGPDFDocumentIsUnlocked(_cgPDFDocument.get())) {
208 [self _didLoadPDFDocument];
212 [self _showPasswordEntryField];
215 - (void)web_setMinimumSize:(CGSize)size
217 if (CGSizeEqualToSize(size, _minimumSize))
222 if (_webView._passwordView) {
223 self.frame = { self.frame.origin, size };
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;
237 [self _computePageAndDocumentFrames];
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];
243 [self _revalidateViews];
246 - (void)web_scrollViewDidScroll:(UIScrollView *)scrollView
248 if (scrollView.isZoomBouncing || scrollView._isAnimatingZoom)
251 [self _revalidateViews];
253 if (!_isPerformingSameDocumentNavigation)
254 [_pageNumberIndicator show];
257 - (void)_ensureViewForPage:(PDFPageInfo&)pageInfo
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()];
268 [pageInfo.view setFrame:pageInfo.frame];
269 [pageInfo.view contentLayer].contentsScale = self.window.screen.scale;
272 - (void)_revalidateViews
277 CGRect targetRect = [_scrollView convertRect:_scrollView.bounds toView:self];
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);
284 _centerPageNumber = 0;
286 for (auto& pageInfo : _pages) {
287 if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw) && pageInfo.index != _currentFindPageIndex) {
288 detachViewForPage(pageInfo);
292 if (!_centerPageNumber && CGRectIntersectsRect(pageInfo.frame, targetRectForCenterPage))
293 _centerPageNumber = pageInfo.index + 1;
295 [self _ensureViewForPage:pageInfo];
298 if (!_centerPageNumber && _pages.size()) {
299 if (CGRectGetMinY(_pages.first().frame) > CGRectGetMaxY(targetRectForCenterPage))
300 _centerPageNumber = 1;
302 ASSERT(CGRectGetMaxY(_pages.last().frame) < CGRectGetMinY(targetRectForCenterPage));
303 _centerPageNumber = _pages.size();
307 [self _updatePageNumberIndicator];
310 - (CGPoint)_offsetForPageNumberIndicator
312 UIEdgeInsets insets = UIEdgeInsetsAdd(_webView._computedUnobscuredSafeAreaInset, _webView._computedObscuredInset, UIRectEdgeAll);
313 return CGPointMake(insets.left, insets.top + _overlaidAccessoryViewsInset.height);
316 - (void)_updatePageNumberIndicator
318 if (_isPerformingSameDocumentNavigation)
321 if (!_pageNumberIndicator)
322 _pageNumberIndicator = adoptNS([[WKPDFPageNumberIndicator alloc] initWithFrame:CGRectZero]);
324 [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
326 [_pageNumberIndicator setCurrentPageNumber:_centerPageNumber];
327 [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:NO];
330 - (void)web_setOverlaidAccessoryViewsInset:(CGSize)inset
332 _overlaidAccessoryViewsInset = inset;
333 [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:YES];
336 - (void)web_computedContentInsetDidChange
338 [self _updatePageNumberIndicator];
340 if (UIEdgeInsetsEqualToEdgeInsets(_webView._computedUnobscuredSafeAreaInset, _lastUnobscuredSafeAreaInset))
343 [self _computePageAndDocumentFrames];
344 [self _revalidateViews];
347 - (void)web_setFixedOverlayView:(UIView *)fixedOverlayView
349 _fixedOverlayView = fixedOverlayView;
351 if (_pageNumberIndicator)
352 [_fixedOverlayView addSubview:_pageNumberIndicator.get()];
355 - (void)_scrollToFragment:(NSString *)fragment
357 if (![fragment hasPrefix:@"page"])
360 NSInteger pageIndex = [[fragment substringFromIndex:4] integerValue] - 1;
361 if (pageIndex < 0 || static_cast<std::size_t>(pageIndex) >= _pages.size())
364 _isPerformingSameDocumentNavigation = YES;
366 [_pageNumberIndicator hide];
367 [self _resetZoomAnimated:NO];
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];
373 _isPerformingSameDocumentNavigation = NO;
376 - (void)web_didSameDocumentNavigation:(WKSameDocumentNavigationType)navigationType
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)
384 // FIXME: restore the scroll position and page scale if navigating back from a fragment.
386 [self _scrollToFragment:_webView.URL.fragment];
389 - (void)_computePageAndDocumentFrames
391 UIEdgeInsets safeAreaInsets = _webView._computedUnobscuredSafeAreaInset;
392 _lastUnobscuredSafeAreaInset = safeAreaInsets;
393 CGSize minimumSizeRespectingContentInset = CGSizeMake(_minimumSize.width - (safeAreaInsets.left + safeAreaInsets.right), _minimumSize.height - (safeAreaInsets.top + safeAreaInsets.bottom));
395 if (!_pages.isEmpty() && _lastLayoutWidth == minimumSizeRespectingContentInset.width) {
396 [self _updateDocumentFrame];
400 NSUInteger pageCount = [_pdfDocument numberOfPages];
404 _lastLayoutWidth = minimumSizeRespectingContentInset.width;
405 [_pageNumberIndicator setPageCount:pageCount];
409 _pages.reserveCapacity(pageCount);
411 CGRect pageFrame = CGRectMake(0, 0, minimumSizeRespectingContentInset.width, minimumSizeRespectingContentInset.height);
412 for (NSUInteger pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
413 UIPDFPage *page = [_pdfDocument pageAtIndex:pageIndex];
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;
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;
431 [self _updateDocumentFrame];
434 - (void)_updateDocumentFrame
436 if (_pages.isEmpty())
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);
447 [self setFrame:newFrame];
448 [_scrollView setContentSize:newFrame.size];
451 - (void)_resetZoomAnimated:(BOOL)animated
453 _isStartingZoom = YES;
455 CGRect scrollViewBounds = _scrollView.bounds;
456 CGPoint centerOfPageInDocumentCoordinates = [_scrollView convertPoint:CGPointMake(CGRectGetMidX(scrollViewBounds), CGRectGetMidY(scrollViewBounds)) toView:self];
457 [_webView _zoomOutWithOrigin:centerOfPageInDocumentCoordinates animated:animated];
459 _isStartingZoom = NO;
462 - (void)_highlightLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation forDuration:(NSTimeInterval)duration completionHandler:(void (^)(void))completionHandler
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];
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];
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];
483 - (NSURL *)_URLForLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation
485 NSURL *documentURL = _webView.URL;
487 if (NSURL *url = linkAnnotation.url)
488 return [NSURL URLWithString:url.relativeString relativeToURL:documentURL];
490 if (NSUInteger pageNumber = linkAnnotation.pageNumber) {
491 String anchorString = ASCIILiteral("#page");
492 anchorString.append(String::number(pageNumber));
493 return [NSURL URLWithString:anchorString relativeToURL:documentURL];
499 #pragma mark Find-in-Page
501 static NSStringCompareOptions stringCompareOptions(_WKFindOptions options)
503 NSStringCompareOptions findOptions = 0;
504 if (options & _WKFindOptionsCaseInsensitive)
505 findOptions |= NSCaseInsensitiveSearch;
506 if (options & _WKFindOptionsBackwards)
507 findOptions |= NSBackwardsSearch;
511 - (void)_computeMatchesForString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount completionHandler:(void (^)(BOOL success))completionHandler
514 completionHandler(NO);
518 _WKFindOptions optionsAffectingResults = options & ~_WKFindOptionsIrrelevantForBatchResults;
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;
525 completionHandler(YES);
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);
535 NSStringCompareOptions findOptions = stringCompareOptions(optionsAffectingResults);
537 Vector<PDFPageInfo> pages = _pages;
539 unsigned computeMatchesOperationID = ++_nextComputeMatchesOperationID;
540 _nextCachedFindString = string;
541 _nextCachedFindOptionsAffectingResults = optionsAffectingResults;
542 _nextCachedFindMaximumCount = maxCount;
544 RetainPtr<WKPDFView> retainedSelf = self;
545 typeof(completionHandler) completionHandlerCopy = Block_copy(completionHandler);
547 dispatch_async(_findQueue, [pages, string, findOptions, optionsAffectingResults, maxCount, computeMatchesOperationID, retainedSelf, completionHandlerCopy] {
548 Vector<RetainPtr<UIPDFSelection>> matches;
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;
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);
569 dispatch_async(dispatch_get_main_queue(), [computeMatchesOperationID, string, optionsAffectingResults, maxCount, matches, completionHandlerCopy, retainedSelf] {
571 // If another search has been enqueued in the meantime, ignore this result.
572 if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) {
573 Block_release(completionHandlerCopy);
577 retainedSelf->_cachedFindString = string;
578 retainedSelf->_cachedFindOptionsAffectingResults = optionsAffectingResults;
579 retainedSelf->_cachedFindMaximumCount = maxCount;
580 retainedSelf->_cachedFindMatches = matches;
582 retainedSelf->_nextCachedFindString = nil;
584 completionHandlerCopy(YES);
585 Block_release(completionHandlerCopy);
590 - (void)web_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
592 RefPtr<WebKit::WebPageProxy> page = _webView->_page;
593 [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
596 page->findClient().didCountStringMatches(page.get(), string, _cachedFindMatches.size());
600 - (void)_didFindMatch:(UIPDFSelection *)match
602 for (auto& pageInfo : _pages) {
603 if (pageInfo.page == match.page) {
604 [self _ensureViewForPage:pageInfo];
606 [pageInfo.view highlightSearchSelection:match animated:NO];
608 _currentFindPageIndex = pageInfo.index;
609 _currentFindSelection = match;
611 CGRect zoomRect = [pageInfo.view convertRectFromPDFPageSpace:match.bounds];
612 if (CGRectIsNull(zoomRect))
615 [self zoom:pageInfo.view.get() to:zoomRect atPoint:CGPointZero kind:kUIPDFObjectKindText];
622 - (void)web_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount
624 if (_currentFindSelection)
625 [_pages[_currentFindPageIndex].view clearSearchHighlights];
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];
634 previousFindSelection = adoptNS([[UIPDFSelection alloc] initWithPage:[_currentFindSelection page] fromIndex:location - 1 toIndex:location]);
638 NSStringCompareOptions findOptions = stringCompareOptions(options);
639 bool backwards = (options & _WKFindOptionsBackwards);
640 RefPtr<WebKit::WebPageProxy> page = _webView->_page;
642 [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) {
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];
651 if (!pageIndex && backwards)
652 pageIndex = _pages.size() - 1;
653 else if (pageIndex == _pages.size() - 1 && !backwards)
656 pageIndex += backwards ? -1 : 1;
660 [self _didFindMatch:match];
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);
669 _currentFindMatchIndex++;
676 page->findClient().didFailToFindString(page.get(), string);
680 - (void)web_hideFindUI
682 if (_currentFindSelection)
683 [_pages[_currentFindPageIndex].view clearSearchHighlights];
685 _currentFindSelection = nullptr;
686 _cachedFindString = nullptr;
687 _cachedFindMatches.clear();
690 #pragma mark UIPDFPageViewDelegate
692 - (void)zoom:(UIPDFPageView *)pageView to:(CGRect)targetRect atPoint:(CGPoint)origin kind:(UIPDFObjectKind)kind
694 _isStartingZoom = YES;
696 BOOL isImage = kind == kUIPDFObjectKindGraphic;
699 targetRect = CGRectInset(targetRect, smartMagnificationElementPadding * targetRect.size.width, smartMagnificationElementPadding * targetRect.size.height);
701 CGRect rectInDocumentCoordinates = [pageView convertRect:targetRect toView:self];
702 CGPoint originInDocumentCoordinates = [pageView convertPoint:origin toView:self];
704 [_webView _zoomToRect:rectInDocumentCoordinates withOrigin:originInDocumentCoordinates fitEntireRect:isImage minimumScale:pdfMinimumZoomScale maximumScale:pdfMaximumZoomScale minimumScrollDistance:0];
706 _isStartingZoom = NO;
709 - (void)resetZoom:(UIPDFPageView *)pageView
711 [self _resetZoomAnimated:YES];
714 #pragma mark UIPDFAnnotationControllerDelegate
716 - (void)annotation:(UIPDFAnnotation *)annotation wasTouchedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller
718 if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]])
721 UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation;
722 RetainPtr<NSURL> url = [self _URLForLinkAnnotation:linkAnnotation];
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;
730 [self _highlightLinkAnnotation:linkAnnotation forDuration:.2 completionHandler:^{
731 retainedWebView->_page->navigateToPDFLinkWithSimulatedClick([url absoluteString], roundedIntPoint(documentPoint), roundedIntPoint(screenPoint));
735 - (void)annotation:(UIPDFAnnotation *)annotation isBeingPressedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller
737 if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]])
740 UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation;
741 NSURL *url = [self _URLForLinkAnnotation:linkAnnotation];
745 _positionInformation.request.point = roundedIntPoint([controller.pageView convertPoint:point toView:self]);
747 _positionInformation.url = url;
748 _positionInformation.bounds = roundedIntRect([self convertRect:[controller.pageView convertRectFromPDFPageSpace:annotation.Rect] fromView:controller.pageView]);
750 [self _highlightLinkAnnotation:linkAnnotation forDuration:.75 completionHandler:^{
751 [_actionSheetAssistant showLinkSheet];
755 #pragma mark WKActionSheetAssistantDelegate
757 - (std::optional<WebKit::InteractionInformationAtPosition>)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
759 return _positionInformation;
762 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant performAction:(WebKit::SheetAction)action
764 if (action != WebKit::SheetAction::Copy)
767 NSDictionary *representations = @{
768 (NSString *)kUTTypeUTF8PlainText : (NSString *)_positionInformation.url,
769 (NSString *)kUTTypeURL : (NSURL *)_positionInformation.url
772 [UIPasteboard generalPasteboard].items = @[ representations ];
775 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant openElementAtLocation:(CGPoint)location
777 CGPoint screenPoint = [self.window convertPoint:[self convertPoint:location toView:nil] toWindow:nil];
778 _webView->_page->navigateToPDFLinkWithSimulatedClick(_positionInformation.url, roundedIntPoint(location), roundedIntPoint(screenPoint));
781 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant shareElementWithURL:(NSURL *)url rect:(CGRect)boundingRect
783 _webSelectionAssistant = adoptNS([[UIWKSelectionAssistant alloc] initWithView:self]);
784 [_webSelectionAssistant showShareSheetFor:userVisibleString(url) fromRect:boundingRect];
785 _webSelectionAssistant = nil;
789 - (BOOL)actionSheetAssistant:(WKActionSheetAssistant *)assistant shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element
791 return _webView->_page->uiClient().shouldIncludeAppLinkActionsForElement(element);
795 - (RetainPtr<NSArray>)actionSheetAssistant:(WKActionSheetAssistant *)assistant decideActionsForElement:(_WKActivatedElementInfo *)element defaultActions:(RetainPtr<NSArray>)defaultActions
797 return _webView->_page->uiClient().actionsForElement(element, WTFMove(defaultActions));
800 #pragma mark Password protection UI
802 - (void)_showPasswordEntryField
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];
810 [webView _hidePasswordView];
811 [retainedSelf _didLoadPDFDocument];
815 - (void)willMoveToWindow:(UIWindow *)newWindow
821 ASSERT(_applicationStateTracker);
822 _applicationStateTracker = nullptr;
825 - (void)didMoveToWindow
830 ASSERT(!_applicationStateTracker);
831 _applicationStateTracker = std::make_unique<ApplicationStateTracker>(self, @selector(_applicationDidEnterBackground), @selector(_applicationDidCreateWindowContext), @selector(_applicationDidFinishSnapshottingAfterEnteringBackground), @selector(_applicationWillEnterForeground));
834 - (BOOL)web_isBackground
836 if (!_applicationStateTracker)
839 return _applicationStateTracker->isInBackground();
842 - (void)_applicationDidEnterBackground
844 _webView->_page->applicationDidEnterBackground();
845 _webView->_page->activityStateDidChange(ActivityState::AllFlags & ~ActivityState::IsInWindow);
848 - (void)_applicationDidCreateWindowContext
852 - (void)_applicationDidFinishSnapshottingAfterEnteringBackground
854 _webView->_page->applicationDidFinishSnapshottingAfterEnteringBackground();
857 - (void)_applicationWillEnterForeground
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);
867 #pragma mark Printing
869 @interface WKPDFView (_WKWebViewPrintFormatter) <_WKWebViewPrintProvider>
872 @implementation WKPDFView (_WKWebViewPrintFormatter)
874 - (NSUInteger)_wk_pageCountForPrintFormatter:(_WKWebViewPrintFormatter *)printFormatter
876 CGPDFDocumentRef document = _cgPDFDocument.get();
877 if (!CGPDFDocumentAllowsPrinting(document))
880 size_t numberOfPages = CGPDFDocumentGetNumberOfPages(document);
881 if (printFormatter.snapshotFirstPage)
882 return std::min<NSUInteger>(numberOfPages, 1);
884 return numberOfPages;
887 - (CGPDFDocumentRef)_wk_printedDocument
889 return _cgPDFDocument.get();
894 #pragma clang diagnostic pop
896 #endif // PLATFORM(IOS) && ENABLE(WKPDFVIEW)