| /* |
| * Copyright (C) 2014-2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "WKLegacyPDFView.h" |
| |
| #if ENABLE(WKLEGACYPDFVIEW) |
| |
| #import "APIFindClient.h" |
| #import "APIUIClient.h" |
| #import "CorePDFSPI.h" |
| #import "DrawingAreaProxy.h" |
| #import "SessionState.h" |
| #import "UIKitSPI.h" |
| #import "WKPDFPageNumberIndicator.h" |
| #import "WKPasswordView.h" |
| #import "WKWebViewInternal.h" |
| #import "WeakObjCPtr.h" |
| #import "WebPageProxy.h" |
| #import "_WKFindDelegate.h" |
| #import "_WKWebViewPrintFormatterInternal.h" |
| #import <MobileCoreServices/UTCoreTypes.h> |
| #import <WebCore/FloatRect.h> |
| #import <WebCore/LocalizedStrings.h> |
| #import <WebCore/WebCoreNSURLExtras.h> |
| #import <pal/spi/cg/CoreGraphicsSPI.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/Vector.h> |
| |
| // All of UIPDFPage* are deprecated, so just ignore deprecated declarations |
| // in this file until we switch off them. |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| |
| using namespace WebCore; |
| using namespace WebKit; |
| |
| const CGFloat pdfPageMargin = 8; |
| const CGFloat pdfMinimumZoomScale = 1; |
| const CGFloat pdfMaximumZoomScale = 5; |
| |
| const float overdrawHeightMultiplier = 1.5; |
| |
| static const CGFloat smartMagnificationElementPadding = 0.05; |
| |
| typedef struct { |
| CGRect frame; |
| RetainPtr<UIPDFPageView> view; |
| RetainPtr<UIPDFPage> page; |
| unsigned index; |
| } PDFPageInfo; |
| |
| @interface WKLegacyPDFView () |
| - (void)_resetZoomAnimated:(BOOL)animated; |
| @end |
| |
| @implementation WKLegacyPDFView { |
| RetainPtr<CGPDFDocumentRef> _cgPDFDocument; |
| RetainPtr<UIPDFDocument> _pdfDocument; |
| RetainPtr<NSString> _suggestedFilename; |
| RetainPtr<WKPDFPageNumberIndicator> _pageNumberIndicator; |
| |
| Vector<PDFPageInfo> _pages; |
| unsigned _centerPageNumber; |
| |
| CGSize _minimumSize; |
| CGSize _overlaidAccessoryViewsInset; |
| WKWebView *_webView; |
| UIScrollView *_scrollView; |
| UIView *_fixedOverlayView; |
| |
| BOOL _isStartingZoom; |
| BOOL _isPerformingSameDocumentNavigation; |
| |
| RetainPtr<WKActionSheetAssistant> _actionSheetAssistant; |
| WebKit::InteractionInformationAtPosition _positionInformation; |
| |
| unsigned _currentFindPageIndex; |
| unsigned _currentFindMatchIndex; |
| RetainPtr<UIPDFSelection> _currentFindSelection; |
| |
| RetainPtr<NSString> _cachedFindString; |
| Vector<RetainPtr<UIPDFSelection>> _cachedFindMatches; |
| unsigned _cachedFindMaximumCount; |
| _WKFindOptions _cachedFindOptionsAffectingResults; |
| |
| std::atomic<unsigned> _nextComputeMatchesOperationID; |
| RetainPtr<NSString> _nextCachedFindString; |
| unsigned _nextCachedFindMaximumCount; |
| _WKFindOptions _nextCachedFindOptionsAffectingResults; |
| |
| dispatch_queue_t _findQueue; |
| |
| RetainPtr<UIWKSelectionAssistant> _webSelectionAssistant; |
| |
| UIEdgeInsets _lastUnobscuredSafeAreaInset; |
| CGFloat _lastLayoutWidth; |
| } |
| |
| - (instancetype)web_initWithFrame:(CGRect)frame webView:(WKWebView *)webView |
| { |
| if (!(self = [super initWithFrame:frame webView:webView])) |
| return nil; |
| |
| self.backgroundColor = [UIColor grayColor]; |
| |
| _webView = webView; |
| |
| _scrollView = webView.scrollView; |
| [_scrollView setMinimumZoomScale:pdfMinimumZoomScale]; |
| [_scrollView setMaximumZoomScale:pdfMaximumZoomScale]; |
| [_scrollView setBackgroundColor:[UIColor grayColor]]; |
| |
| _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]); |
| [_actionSheetAssistant setDelegate:self]; |
| |
| _findQueue = dispatch_queue_create("com.apple.WebKit.WKPDFViewComputeMatchesQueue", DISPATCH_QUEUE_SERIAL); |
| |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| |
| [self _clearPages]; |
| [_pageNumberIndicator removeFromSuperview]; |
| dispatch_release(_findQueue); |
| [_actionSheetAssistant cleanupSheet]; |
| [super dealloc]; |
| } |
| |
| - (NSString *)web_suggestedFilename |
| { |
| return _suggestedFilename.get(); |
| } |
| |
| - (NSData *)web_dataRepresentation |
| { |
| return [(NSData *)CGDataProviderCopyData(CGPDFDocumentGetDataProvider(_cgPDFDocument.get())) autorelease]; |
| } |
| |
| static void detachViewForPage(PDFPageInfo& page) |
| { |
| [page.view removeFromSuperview]; |
| [page.view setDelegate:nil]; |
| [[page.view annotationController] setDelegate:nil]; |
| page.view = nil; |
| } |
| |
| - (void)_clearPages |
| { |
| for (auto& page : _pages) |
| detachViewForPage(page); |
| |
| _pages.clear(); |
| } |
| |
| - (void)_didLoadPDFDocument |
| { |
| _pdfDocument = adoptNS([[UIPDFDocument alloc] initWithCGPDFDocument:_cgPDFDocument.get()]); |
| |
| // FIXME: Restore the scroll position and page scale if navigating from the back/forward list. |
| |
| [self _computePageAndDocumentFrames]; |
| [self _revalidateViews]; |
| [self _scrollToFragment:_webView.URL.fragment]; |
| } |
| |
| - (void)web_setContentProviderData:(NSData *)data suggestedFilename:(NSString *)filename |
| { |
| _suggestedFilename = adoptNS([filename copy]); |
| |
| [self _clearPages]; |
| |
| RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)data)); |
| _cgPDFDocument = adoptCF(CGPDFDocumentCreateWithProvider(dataProvider.get())); |
| |
| if (!_cgPDFDocument) |
| return; |
| |
| if (CGPDFDocumentIsUnlocked(_cgPDFDocument.get())) { |
| [self _didLoadPDFDocument]; |
| return; |
| } |
| |
| [self _showPasswordEntryField]; |
| } |
| |
| - (void)web_setMinimumSize:(CGSize)size |
| { |
| if (CGSizeEqualToSize(size, _minimumSize)) |
| return; |
| |
| _minimumSize = size; |
| |
| if (_webView._passwordView) { |
| self.frame = { self.frame.origin, size }; |
| return; |
| } |
| |
| CGFloat oldDocumentLeftFraction = 0; |
| CGFloat oldDocumentTopFraction = 0; |
| CGSize contentSize = _scrollView.contentSize; |
| if (contentSize.width && contentSize.height) { |
| CGPoint contentOffset = _scrollView.contentOffset; |
| UIEdgeInsets contentInset = _scrollView.contentInset; |
| oldDocumentLeftFraction = (contentOffset.x + contentInset.left) / contentSize.width; |
| oldDocumentTopFraction = (contentOffset.y + contentInset.top) / contentSize.height; |
| } |
| |
| [self _computePageAndDocumentFrames]; |
| |
| CGSize newContentSize = _scrollView.contentSize; |
| UIEdgeInsets contentInset = _scrollView.contentInset; |
| [_scrollView setContentOffset:CGPointMake((oldDocumentLeftFraction * newContentSize.width) - contentInset.left, (oldDocumentTopFraction * newContentSize.height) - contentInset.top) animated:NO]; |
| |
| [self _revalidateViews]; |
| } |
| |
| - (void)web_scrollViewDidScroll:(UIScrollView *)scrollView |
| { |
| if (scrollView.isZoomBouncing || scrollView._isAnimatingZoom) |
| return; |
| |
| [self _revalidateViews]; |
| |
| if (!_isPerformingSameDocumentNavigation) |
| [_pageNumberIndicator show]; |
| } |
| |
| - (void)_ensureViewForPage:(PDFPageInfo&)pageInfo |
| { |
| if (pageInfo.view) |
| return; |
| |
| pageInfo.view = adoptNS([[UIPDFPageView alloc] initWithPage:pageInfo.page.get() tiledContent:YES]); |
| [pageInfo.view setUseBackingLayer:YES]; |
| [pageInfo.view setDelegate:self]; |
| [[pageInfo.view annotationController] setDelegate:self]; |
| [self addSubview:pageInfo.view.get()]; |
| |
| [pageInfo.view setFrame:pageInfo.frame]; |
| [pageInfo.view contentLayer].contentsScale = self.window.screen.scale; |
| } |
| |
| - (void)_revalidateViews |
| { |
| if (_isStartingZoom) |
| return; |
| |
| CGRect targetRect = [_scrollView convertRect:_scrollView.bounds toView:self]; |
| |
| // We apply overdraw after applying scale in order to avoid excessive |
| // memory use caused by scaling the overdraw. |
| CGRect targetRectWithOverdraw = CGRectInset(targetRect, 0, -targetRect.size.height * overdrawHeightMultiplier); |
| CGRect targetRectForCenterPage = CGRectInset(targetRect, 0, targetRect.size.height / 2 - pdfPageMargin * 2); |
| |
| _centerPageNumber = 0; |
| |
| for (auto& pageInfo : _pages) { |
| if (!CGRectIntersectsRect(pageInfo.frame, targetRectWithOverdraw) && pageInfo.index != _currentFindPageIndex) { |
| detachViewForPage(pageInfo); |
| continue; |
| } |
| |
| if (!_centerPageNumber && CGRectIntersectsRect(pageInfo.frame, targetRectForCenterPage)) |
| _centerPageNumber = pageInfo.index + 1; |
| |
| [self _ensureViewForPage:pageInfo]; |
| } |
| |
| if (!_centerPageNumber && _pages.size()) { |
| if (CGRectGetMinY(_pages.first().frame) > CGRectGetMaxY(targetRectForCenterPage)) |
| _centerPageNumber = 1; |
| else { |
| ASSERT(CGRectGetMaxY(_pages.last().frame) < CGRectGetMinY(targetRectForCenterPage)); |
| _centerPageNumber = _pages.size(); |
| } |
| } |
| |
| [self _updatePageNumberIndicator]; |
| } |
| |
| - (CGPoint)_offsetForPageNumberIndicator |
| { |
| UIEdgeInsets insets = UIEdgeInsetsAdd(_webView._computedUnobscuredSafeAreaInset, _webView._computedObscuredInset, UIRectEdgeAll); |
| return CGPointMake(insets.left, insets.top + _overlaidAccessoryViewsInset.height); |
| } |
| |
| - (void)_updatePageNumberIndicator |
| { |
| if (_isPerformingSameDocumentNavigation) |
| return; |
| |
| if (!_pageNumberIndicator) |
| _pageNumberIndicator = adoptNS([[WKPDFPageNumberIndicator alloc] initWithFrame:CGRectZero]); |
| |
| [_fixedOverlayView addSubview:_pageNumberIndicator.get()]; |
| |
| [_pageNumberIndicator setCurrentPageNumber:_centerPageNumber]; |
| [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:NO]; |
| } |
| |
| - (void)web_setOverlaidAccessoryViewsInset:(CGSize)inset |
| { |
| _overlaidAccessoryViewsInset = inset; |
| [_pageNumberIndicator moveToPoint:[self _offsetForPageNumberIndicator] animated:YES]; |
| } |
| |
| - (void)web_computedContentInsetDidChange |
| { |
| [self _updatePageNumberIndicator]; |
| |
| if (UIEdgeInsetsEqualToEdgeInsets(_webView._computedUnobscuredSafeAreaInset, _lastUnobscuredSafeAreaInset)) |
| return; |
| |
| [self _computePageAndDocumentFrames]; |
| [self _revalidateViews]; |
| } |
| |
| - (void)web_setFixedOverlayView:(UIView *)fixedOverlayView |
| { |
| _fixedOverlayView = fixedOverlayView; |
| |
| if (_pageNumberIndicator) |
| [_fixedOverlayView addSubview:_pageNumberIndicator.get()]; |
| } |
| |
| - (void)_scrollToFragment:(NSString *)fragment |
| { |
| if (![fragment hasPrefix:@"page"]) |
| return; |
| |
| NSInteger pageIndex = [[fragment substringFromIndex:4] integerValue] - 1; |
| if (pageIndex < 0 || static_cast<std::size_t>(pageIndex) >= _pages.size()) |
| return; |
| |
| _isPerformingSameDocumentNavigation = YES; |
| |
| [_pageNumberIndicator hide]; |
| [self _resetZoomAnimated:NO]; |
| |
| // Ensure that the page margin is visible below the content inset. |
| const CGFloat verticalOffset = _pages[pageIndex].frame.origin.y - _webView._computedObscuredInset.top - pdfPageMargin; |
| [_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x, verticalOffset) animated:NO]; |
| |
| _isPerformingSameDocumentNavigation = NO; |
| } |
| |
| - (void)web_didSameDocumentNavigation:(WKSameDocumentNavigationType)navigationType |
| { |
| // Check for kWKSameDocumentNavigationSessionStatePop instead of kWKSameDocumentNavigationAnchorNavigation since the |
| // latter is only called once when navigating to the same anchor in succession. If the user navigates to a page |
| // then scrolls back and clicks on the same link a second time, we want to scroll again. |
| if (navigationType != kWKSameDocumentNavigationSessionStatePop) |
| return; |
| |
| // FIXME: restore the scroll position and page scale if navigating back from a fragment. |
| |
| [self _scrollToFragment:_webView.URL.fragment]; |
| } |
| |
| - (UIView *)web_contentView |
| { |
| return self; |
| } |
| |
| - (void)_computePageAndDocumentFrames |
| { |
| UIEdgeInsets safeAreaInsets = _webView._computedUnobscuredSafeAreaInset; |
| _lastUnobscuredSafeAreaInset = safeAreaInsets; |
| CGSize minimumSizeRespectingContentInset = CGSizeMake(_minimumSize.width - (safeAreaInsets.left + safeAreaInsets.right), _minimumSize.height - (safeAreaInsets.top + safeAreaInsets.bottom)); |
| |
| if (!_pages.isEmpty() && _lastLayoutWidth == minimumSizeRespectingContentInset.width) { |
| [self _updateDocumentFrame]; |
| return; |
| } |
| |
| NSUInteger pageCount = [_pdfDocument numberOfPages]; |
| if (!pageCount) |
| return; |
| |
| _lastLayoutWidth = minimumSizeRespectingContentInset.width; |
| [_pageNumberIndicator setPageCount:pageCount]; |
| |
| [self _clearPages]; |
| |
| _pages.reserveCapacity(pageCount); |
| |
| CGRect pageFrame = CGRectMake(0, 0, minimumSizeRespectingContentInset.width, minimumSizeRespectingContentInset.height); |
| for (NSUInteger pageIndex = 0; pageIndex < pageCount; ++pageIndex) { |
| UIPDFPage *page = [_pdfDocument pageAtIndex:pageIndex]; |
| if (!page) |
| continue; |
| |
| CGSize pageSize = [page cropBoxAccountForRotation].size; |
| pageFrame.size.height = pageSize.height / pageSize.width * pageFrame.size.width; |
| CGRect pageFrameWithMarginApplied = CGRectInset(pageFrame, pdfPageMargin, pdfPageMargin); |
| if (CGRectIsNull(pageFrameWithMarginApplied)) |
| pageFrameWithMarginApplied = CGRectZero; |
| |
| PDFPageInfo pageInfo; |
| pageInfo.page = page; |
| pageInfo.frame = pageFrameWithMarginApplied; |
| pageInfo.index = pageIndex; |
| _pages.append(pageInfo); |
| pageFrame.origin.y += pageFrame.size.height - pdfPageMargin; |
| } |
| |
| [self _updateDocumentFrame]; |
| } |
| |
| - (void)_updateDocumentFrame |
| { |
| if (_pages.isEmpty()) |
| return; |
| |
| UIEdgeInsets safeAreaInsets = _webView._computedUnobscuredSafeAreaInset; |
| CGFloat scale = _scrollView.zoomScale; |
| CGRect newFrame = CGRectZero; |
| newFrame.origin.x = safeAreaInsets.left; |
| newFrame.origin.y = safeAreaInsets.top; |
| newFrame.size.width = _lastLayoutWidth * scale; |
| newFrame.size.height = std::max((CGRectGetMaxY(_pages.last().frame) + pdfPageMargin) * scale + safeAreaInsets.bottom, _minimumSize.height * scale); |
| |
| [self setFrame:newFrame]; |
| [_scrollView setContentSize:newFrame.size]; |
| } |
| |
| - (void)_resetZoomAnimated:(BOOL)animated |
| { |
| _isStartingZoom = YES; |
| |
| CGRect scrollViewBounds = _scrollView.bounds; |
| CGPoint centerOfPageInDocumentCoordinates = [_scrollView convertPoint:CGPointMake(CGRectGetMidX(scrollViewBounds), CGRectGetMidY(scrollViewBounds)) toView:self]; |
| [_webView _zoomOutWithOrigin:centerOfPageInDocumentCoordinates animated:animated]; |
| |
| _isStartingZoom = NO; |
| } |
| |
| - (void)_highlightLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation forDuration:(NSTimeInterval)duration completionHandler:(void (^)(void))completionHandler |
| { |
| static const CGFloat highlightBorderRadius = 3; |
| static const CGFloat highlightColorComponent = 26.0 / 255; |
| static UIColor *highlightColor = [[UIColor alloc] initWithRed:highlightColorComponent green:highlightColorComponent blue:highlightColorComponent alpha:0.3]; |
| |
| UIPDFPageView *pageView = linkAnnotation.annotationController.pageView; |
| CGRect highlightViewFrame = [self convertRect:[pageView convertRectFromPDFPageSpace:linkAnnotation.Rect] fromView:pageView]; |
| RetainPtr<_UIHighlightView> highlightView = adoptNS([[_UIHighlightView alloc] initWithFrame:CGRectInset(highlightViewFrame, -highlightBorderRadius, -highlightBorderRadius)]); |
| [highlightView setOpaque:NO]; |
| [highlightView setCornerRadius:highlightBorderRadius]; |
| [highlightView setColor:highlightColor]; |
| |
| ASSERT(RunLoop::isMain()); |
| [self addSubview:highlightView.get()]; |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ |
| [highlightView removeFromSuperview]; |
| completionHandler(); |
| }); |
| } |
| |
| - (NSURL *)_URLForLinkAnnotation:(UIPDFLinkAnnotation *)linkAnnotation |
| { |
| NSURL *documentURL = _webView.URL; |
| |
| if (NSURL *url = linkAnnotation.url) |
| return [NSURL URLWithString:url.relativeString relativeToURL:documentURL]; |
| |
| if (NSUInteger pageNumber = linkAnnotation.pageNumber) { |
| String anchorString = ASCIILiteral("#page"); |
| anchorString.append(String::number(pageNumber)); |
| return [NSURL URLWithString:anchorString relativeToURL:documentURL]; |
| } |
| |
| return nil; |
| } |
| |
| #pragma mark Find-in-Page |
| |
| static NSStringCompareOptions stringCompareOptions(_WKFindOptions options) |
| { |
| NSStringCompareOptions findOptions = 0; |
| if (options & _WKFindOptionsCaseInsensitive) |
| findOptions |= NSCaseInsensitiveSearch; |
| if (options & _WKFindOptionsBackwards) |
| findOptions |= NSBackwardsSearch; |
| return findOptions; |
| } |
| |
| - (void)_computeMatchesForString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount completionHandler:(void (^)(BOOL success))completionHandler |
| { |
| if (!_pdfDocument) { |
| completionHandler(NO); |
| return; |
| } |
| |
| _WKFindOptions optionsAffectingResults = options & ~_WKFindOptionsIrrelevantForBatchResults; |
| |
| // If this search is equivalent to the currently cached search, bail and call the completion handler, because the existing cached results are valid. |
| if (!_cachedFindMatches.isEmpty() && [_cachedFindString isEqualToString:string] && _cachedFindOptionsAffectingResults == optionsAffectingResults && _cachedFindMaximumCount == maxCount) { |
| // Also, cancel any running search, because it will eventually replace the now-valid results. |
| ++_nextComputeMatchesOperationID; |
| |
| completionHandler(YES); |
| return; |
| } |
| |
| // 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. |
| if ([_nextCachedFindString isEqualToString:string] && _nextCachedFindOptionsAffectingResults == optionsAffectingResults && _nextCachedFindMaximumCount == maxCount) { |
| completionHandler(NO); |
| return; |
| } |
| |
| NSStringCompareOptions findOptions = stringCompareOptions(optionsAffectingResults); |
| |
| Vector<PDFPageInfo> pages = _pages; |
| |
| unsigned computeMatchesOperationID = ++_nextComputeMatchesOperationID; |
| _nextCachedFindString = string; |
| _nextCachedFindOptionsAffectingResults = optionsAffectingResults; |
| _nextCachedFindMaximumCount = maxCount; |
| |
| RetainPtr<WKLegacyPDFView> retainedSelf = self; |
| typeof(completionHandler) completionHandlerCopy = Block_copy(completionHandler); |
| |
| dispatch_async(_findQueue, [pages, string, findOptions, optionsAffectingResults, maxCount, computeMatchesOperationID, retainedSelf, completionHandlerCopy] { |
| Vector<RetainPtr<UIPDFSelection>> matches; |
| |
| for (unsigned pageIndex = 0; pageIndex < pages.size(); ++pageIndex) { |
| UIPDFSelection *match = nullptr; |
| while ((match = [pages[pageIndex].page findString:string fromSelection:match options:findOptions])) { |
| matches.append(match); |
| if (matches.size() > maxCount) |
| goto maxCountExceeded; |
| |
| // If we've enqueued another search, cancel this one. |
| if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) { |
| dispatch_async(dispatch_get_main_queue(), [completionHandlerCopy] { |
| completionHandlerCopy(NO); |
| Block_release(completionHandlerCopy); |
| }); |
| return; |
| } |
| }; |
| } |
| |
| maxCountExceeded: |
| dispatch_async(dispatch_get_main_queue(), [computeMatchesOperationID, string, optionsAffectingResults, maxCount, matches, completionHandlerCopy, retainedSelf] { |
| |
| // If another search has been enqueued in the meantime, ignore this result. |
| if (retainedSelf->_nextComputeMatchesOperationID != computeMatchesOperationID) { |
| Block_release(completionHandlerCopy); |
| return; |
| } |
| |
| retainedSelf->_cachedFindString = string; |
| retainedSelf->_cachedFindOptionsAffectingResults = optionsAffectingResults; |
| retainedSelf->_cachedFindMaximumCount = maxCount; |
| retainedSelf->_cachedFindMatches = matches; |
| |
| retainedSelf->_nextCachedFindString = nil; |
| |
| completionHandlerCopy(YES); |
| Block_release(completionHandlerCopy); |
| }); |
| }); |
| } |
| |
| - (void)web_countStringMatches:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount |
| { |
| RefPtr<WebKit::WebPageProxy> page = _webView->_page; |
| [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) { |
| if (!success) |
| return; |
| page->findClient().didCountStringMatches(page.get(), string, _cachedFindMatches.size()); |
| }]; |
| } |
| |
| - (void)_didFindMatch:(UIPDFSelection *)match |
| { |
| for (auto& pageInfo : _pages) { |
| if (pageInfo.page == match.page) { |
| [self _ensureViewForPage:pageInfo]; |
| |
| [pageInfo.view highlightSearchSelection:match animated:NO]; |
| |
| _currentFindPageIndex = pageInfo.index; |
| _currentFindSelection = match; |
| |
| CGRect zoomRect = [pageInfo.view convertRectFromPDFPageSpace:match.bounds]; |
| if (CGRectIsNull(zoomRect)) |
| return; |
| |
| [self zoom:pageInfo.view.get() to:zoomRect atPoint:CGPointZero kind:kUIPDFObjectKindText]; |
| |
| return; |
| } |
| } |
| } |
| |
| - (void)web_findString:(NSString *)string options:(_WKFindOptions)options maxCount:(NSUInteger)maxCount |
| { |
| if (_currentFindSelection) |
| [_pages[_currentFindPageIndex].view clearSearchHighlights]; |
| |
| RetainPtr<UIPDFSelection> previousFindSelection = _currentFindSelection; |
| unsigned previousFindPageIndex = 0; |
| if (previousFindSelection) { |
| previousFindPageIndex = _currentFindPageIndex; |
| if (![_cachedFindString isEqualToString:string]) { |
| NSUInteger location = [_currentFindSelection startIndex]; |
| if (location) |
| previousFindSelection = adoptNS([[UIPDFSelection alloc] initWithPage:[_currentFindSelection page] fromIndex:location - 1 toIndex:location]); |
| } |
| } |
| |
| NSStringCompareOptions findOptions = stringCompareOptions(options); |
| bool backwards = (options & _WKFindOptionsBackwards); |
| RefPtr<WebKit::WebPageProxy> page = _webView->_page; |
| |
| [self _computeMatchesForString:string options:options maxCount:maxCount completionHandler:^(BOOL success) { |
| if (!success) |
| return; |
| |
| unsigned pageIndex = previousFindPageIndex; |
| for (unsigned i = 0; i < _pages.size(); ++i) { |
| UIPDFSelection *match = [_pages[pageIndex].page findString:string fromSelection:(pageIndex == previousFindPageIndex ? previousFindSelection.get() : nil) options:findOptions]; |
| |
| if (!match) { |
| if (!pageIndex && backwards) |
| pageIndex = _pages.size() - 1; |
| else if (pageIndex == _pages.size() - 1 && !backwards) |
| pageIndex = 0; |
| else |
| pageIndex += backwards ? -1 : 1; |
| continue; |
| } |
| |
| [self _didFindMatch:match]; |
| |
| if (_cachedFindMatches.size() <= maxCount) { |
| _currentFindMatchIndex = 0; |
| for (const auto& knownMatch : _cachedFindMatches) { |
| if (match.stringRange.location == [knownMatch stringRange].location && match.stringRange.length == [knownMatch stringRange].length) { |
| page->findClient().didFindString(page.get(), string, { }, _cachedFindMatches.size(), _currentFindMatchIndex, false); |
| break; |
| } |
| _currentFindMatchIndex++; |
| } |
| } |
| |
| return; |
| } |
| |
| page->findClient().didFailToFindString(page.get(), string); |
| }]; |
| } |
| |
| - (void)web_hideFindUI |
| { |
| if (_currentFindSelection) |
| [_pages[_currentFindPageIndex].view clearSearchHighlights]; |
| |
| _currentFindSelection = nullptr; |
| _cachedFindString = nullptr; |
| _cachedFindMatches.clear(); |
| } |
| |
| #pragma mark UIPDFPageViewDelegate |
| |
| - (void)zoom:(UIPDFPageView *)pageView to:(CGRect)targetRect atPoint:(CGPoint)origin kind:(UIPDFObjectKind)kind |
| { |
| _isStartingZoom = YES; |
| |
| BOOL isImage = kind == kUIPDFObjectKindGraphic; |
| |
| if (!isImage) |
| targetRect = CGRectInset(targetRect, smartMagnificationElementPadding * targetRect.size.width, smartMagnificationElementPadding * targetRect.size.height); |
| |
| CGRect rectInDocumentCoordinates = [pageView convertRect:targetRect toView:self]; |
| CGPoint originInDocumentCoordinates = [pageView convertPoint:origin toView:self]; |
| |
| [_webView _zoomToRect:rectInDocumentCoordinates withOrigin:originInDocumentCoordinates fitEntireRect:isImage minimumScale:pdfMinimumZoomScale maximumScale:pdfMaximumZoomScale minimumScrollDistance:0]; |
| |
| _isStartingZoom = NO; |
| } |
| |
| - (void)resetZoom:(UIPDFPageView *)pageView |
| { |
| [self _resetZoomAnimated:YES]; |
| } |
| |
| #pragma mark UIPDFAnnotationControllerDelegate |
| |
| - (void)annotation:(UIPDFAnnotation *)annotation wasTouchedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller |
| { |
| if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]]) |
| return; |
| |
| UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation; |
| RetainPtr<NSURL> url = [self _URLForLinkAnnotation:linkAnnotation]; |
| if (!url) |
| return; |
| |
| CGPoint documentPoint = [controller.pageView convertPoint:point toView:self]; |
| CGPoint screenPoint = [self.window convertPoint:[self convertPoint:documentPoint toView:nil] toWindow:nil]; |
| RetainPtr<WKWebView> retainedWebView = _webView; |
| |
| [self _highlightLinkAnnotation:linkAnnotation forDuration:.2 completionHandler:^{ |
| retainedWebView->_page->navigateToPDFLinkWithSimulatedClick([url absoluteString], roundedIntPoint(documentPoint), roundedIntPoint(screenPoint)); |
| }]; |
| } |
| |
| - (void)annotation:(UIPDFAnnotation *)annotation isBeingPressedAtPoint:(CGPoint)point controller:(UIPDFAnnotationController *)controller |
| { |
| if (![annotation isKindOfClass:[UIPDFLinkAnnotation class]]) |
| return; |
| |
| UIPDFLinkAnnotation *linkAnnotation = (UIPDFLinkAnnotation *)annotation; |
| NSURL *url = [self _URLForLinkAnnotation:linkAnnotation]; |
| if (!url) |
| return; |
| |
| _positionInformation.request.point = roundedIntPoint([controller.pageView convertPoint:point toView:self]); |
| |
| _positionInformation.url = url; |
| _positionInformation.bounds = roundedIntRect([self convertRect:[controller.pageView convertRectFromPDFPageSpace:annotation.Rect] fromView:controller.pageView]); |
| |
| [self _highlightLinkAnnotation:linkAnnotation forDuration:.75 completionHandler:^{ |
| [_actionSheetAssistant showLinkSheet]; |
| }]; |
| } |
| |
| #pragma mark WKActionSheetAssistantDelegate |
| |
| - (std::optional<WebKit::InteractionInformationAtPosition>)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant |
| { |
| return _positionInformation; |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant performAction:(WebKit::SheetAction)action |
| { |
| if (action != WebKit::SheetAction::Copy) |
| return; |
| |
| NSDictionary *representations = @{ |
| (NSString *)kUTTypeUTF8PlainText : (NSString *)_positionInformation.url, |
| (NSString *)kUTTypeURL : (NSURL *)_positionInformation.url |
| }; |
| |
| [UIPasteboard generalPasteboard].items = @[ representations ]; |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant openElementAtLocation:(CGPoint)location |
| { |
| CGPoint screenPoint = [self.window convertPoint:[self convertPoint:location toView:nil] toWindow:nil]; |
| _webView->_page->navigateToPDFLinkWithSimulatedClick(_positionInformation.url, roundedIntPoint(location), roundedIntPoint(screenPoint)); |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant shareElementWithURL:(NSURL *)url rect:(CGRect)boundingRect |
| { |
| _webSelectionAssistant = adoptNS([[UIWKSelectionAssistant alloc] initWithView:self]); |
| [_webSelectionAssistant showShareSheetFor:userVisibleString(url) fromRect:boundingRect]; |
| _webSelectionAssistant = nil; |
| } |
| |
| #if HAVE(APP_LINKS) |
| - (BOOL)actionSheetAssistant:(WKActionSheetAssistant *)assistant shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element |
| { |
| return _webView->_page->uiClient().shouldIncludeAppLinkActionsForElement(element); |
| } |
| #endif |
| |
| - (RetainPtr<NSArray>)actionSheetAssistant:(WKActionSheetAssistant *)assistant decideActionsForElement:(_WKActivatedElementInfo *)element defaultActions:(RetainPtr<NSArray>)defaultActions |
| { |
| return _webView->_page->uiClient().actionsForElement(element, WTFMove(defaultActions)); |
| } |
| |
| #pragma mark Password protection UI |
| |
| - (void)_showPasswordEntryField |
| { |
| [_webView _showPasswordViewWithDocumentName:_suggestedFilename.get() passwordHandler:[retainedSelf = retainPtr(self), webView = retainPtr(_webView)](NSString *password) { |
| if (!CGPDFDocumentUnlockWithPassword(retainedSelf->_cgPDFDocument.get(), password.UTF8String)) { |
| [[webView _passwordView] showPasswordFailureAlert]; |
| return; |
| } |
| |
| [webView _hidePasswordView]; |
| [retainedSelf _didLoadPDFDocument]; |
| }]; |
| } |
| |
| - (BOOL)web_isBackground |
| { |
| return [self isBackground]; |
| } |
| |
| - (void)_applicationWillEnterForeground |
| { |
| [super _applicationWillEnterForeground]; |
| // FIXME: Is it really necessary to hide the web content drawing area when a PDF is displayed? |
| if (auto drawingArea = _webView->_page->drawingArea()) |
| drawingArea->hideContentUntilAnyUpdate(); |
| } |
| |
| @end |
| |
| #pragma mark Printing |
| |
| #if !ENABLE(MINIMAL_SIMULATOR) |
| |
| @interface WKLegacyPDFView (_WKWebViewPrintFormatter) <_WKWebViewPrintProvider> |
| @end |
| |
| @implementation WKLegacyPDFView (_WKWebViewPrintFormatter) |
| |
| - (NSUInteger)_wk_pageCountForPrintFormatter:(_WKWebViewPrintFormatter *)printFormatter |
| { |
| CGPDFDocumentRef document = _cgPDFDocument.get(); |
| if (!CGPDFDocumentAllowsPrinting(document)) |
| return 0; |
| |
| size_t numberOfPages = CGPDFDocumentGetNumberOfPages(document); |
| if (printFormatter.snapshotFirstPage) |
| return std::min<NSUInteger>(numberOfPages, 1); |
| |
| return numberOfPages; |
| } |
| |
| - (CGPDFDocumentRef)_wk_printedDocument |
| { |
| return _cgPDFDocument.get(); |
| } |
| |
| @end |
| |
| #endif // !ENABLE(MINIMAL_SIMULATOR) |
| |
| #pragma clang diagnostic pop |
| |
| #endif // ENABLE(WKLEGACYPDFVIEW) |