| /* | 
 |  * Copyright (C) 2006, 2007, 2008, 2010, 2011 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. | 
 |  */ | 
 |  | 
 | #if PLATFORM(IOS_FAMILY) | 
 |  | 
 | #import "WebPDFViewPlaceholder.h" | 
 |  | 
 | #import "WebFrameInternal.h" | 
 | #import "WebPDFViewIOS.h" | 
 | #import <JavaScriptCore/JSContextRef.h> | 
 | #import <JavaScriptCore/OpaqueJSString.h> | 
 | #import <WebCore/DataTransfer.h> | 
 | #import <WebCore/EventHandler.h> | 
 | #import <WebCore/EventNames.h> | 
 | #import <WebCore/FormState.h> | 
 | #import <WebCore/Frame.h> | 
 | #import <WebCore/FrameLoadRequest.h> | 
 | #import <WebCore/FrameLoader.h> | 
 | #import <WebCore/HTMLFormElement.h> | 
 | #import <WebCore/MouseEvent.h> | 
 | #import <WebKitLegacy/WebDataSourcePrivate.h> | 
 | #import <WebKitLegacy/WebFramePrivate.h> | 
 | #import <WebKitLegacy/WebJSPDFDoc.h> | 
 | #import <WebKitLegacy/WebNSURLExtras.h> | 
 | #import <WebKitLegacy/WebNSViewExtras.h> | 
 | #import <WebKitLegacy/WebPDFDocumentExtras.h> | 
 | #import <WebKitLegacy/WebViewPrivate.h> | 
 | #import <wtf/MonotonicTime.h> | 
 | #import <wtf/SoftLinking.h> | 
 | #import <wtf/Vector.h> | 
 |  | 
 | using namespace WebCore; | 
 |  | 
 | #pragma mark Constants | 
 |  | 
 | static const float PAGE_WIDTH_INSET = 4.0f * 2.0f; | 
 | static const float PAGE_HEIGHT_INSET = 4.0f * 2.0f; | 
 |  | 
 | #pragma mark NSValue helpers | 
 |  | 
 | @interface NSValue (_web_Extensions) | 
 | + (NSValue *)_web_valueWithCGRect:(CGRect)rect; | 
 | @end | 
 |  | 
 | @implementation NSValue (_web_Extensions) | 
 |  | 
 | + (NSValue *)_web_valueWithCGRect:(CGRect)rect | 
 | { | 
 |     return [NSValue valueWithBytes:&rect objCType:@encode(CGRect)]; | 
 | } | 
 |  | 
 | - (CGRect)_web_CGRectValue | 
 | { | 
 |     CGRect result; | 
 |     [self getValue:&result]; | 
 |     return result; | 
 | } | 
 |  | 
 | @end | 
 |  | 
 | #pragma mark - | 
 |  | 
 | @interface WebPDFViewPlaceholder () | 
 |  | 
 | - (void)_evaluateJSForDocument:(CGPDFDocumentRef)document; | 
 | - (void)_updateTitleForDocumentIfAvailable; | 
 | - (void)_updateTitleForURL:(NSURL *)URL; | 
 | - (CGSize)_computePageRects:(CGPDFDocumentRef)document; | 
 |  | 
 | @property (retain) NSArray *pageRects; | 
 | @property (retain) NSArray *pageYOrigins; | 
 | @property (retain) NSString *title; | 
 |  | 
 | @end | 
 |  | 
 | #pragma mark WebPDFViewPlaceholder implementation | 
 |  | 
 | @implementation WebPDFViewPlaceholder { | 
 |     NSString *_title; | 
 |     NSArray *_pageRects; | 
 |     NSArray *_pageYOrigins; | 
 |     CGPDFDocumentRef _document; | 
 |     WebDataSource *_dataSource; // weak to prevent cycles. | 
 |      | 
 |     NSObject<WebPDFViewPlaceholderDelegate> *_delegate; | 
 |      | 
 |     BOOL _didFinishLoad; | 
 |      | 
 |     CGSize _containerSize; | 
 | } | 
 |  | 
 | @synthesize delegate = _delegate; | 
 | @synthesize pageRects = _pageRects; | 
 | @synthesize pageYOrigins = _pageYOrigins; | 
 | @synthesize document = _document; | 
 | @synthesize title = _title; | 
 | @synthesize containerSize = _containerSize; | 
 |  | 
 | - (CGPDFDocumentRef)document | 
 | { | 
 |     @synchronized(self) { | 
 |         if (_document) | 
 |             return _document; | 
 |     } | 
 |  | 
 |     if ([self.delegate respondsToSelector:@selector(cgPDFDocument)]) | 
 |         return [self.delegate cgPDFDocument]; | 
 |  | 
 |     return nil; | 
 | } | 
 |  | 
 | - (void)setDocument:(CGPDFDocumentRef)document | 
 | { | 
 |     @synchronized(self) { | 
 |         CGPDFDocumentRetain(document); | 
 |         CGPDFDocumentRelease(_document); | 
 |         _document = document; | 
 |     } | 
 | } | 
 |  | 
 | - (void)clearDocument | 
 | { | 
 |     [self setDocument:nil]; | 
 | } | 
 |  | 
 | - (CGPDFDocumentRef)doc | 
 | { | 
 |     return [self document]; | 
 | } | 
 |  | 
 | - (NSUInteger)totalPages | 
 | { | 
 |     return CGPDFDocumentGetNumberOfPages([self document]); | 
 | } | 
 |  | 
 | + (void)setAsPDFDocRepAndView | 
 | { | 
 |     [WebView _setPDFRepresentationClass:[WebPDFViewPlaceholder class]]; | 
 |     [WebView _setPDFViewClass:[WebPDFViewPlaceholder class]]; | 
 | } | 
 |  | 
 | // This is a secret protocol for WebDataSource and WebFrameView to offer us the opportunity to do something different  | 
 | // depending upon which frame is trying to instantiate a representation for PDF. | 
 | + (Class)_representationClassForWebFrame:(WebFrame *)webFrame | 
 | { | 
 |     return [WebPDFView _representationClassForWebFrame:webFrame]; | 
 | } | 
 |  | 
 | - (void)dealloc | 
 | { | 
 |     if ([self.delegate respondsToSelector:@selector(viewWillClose)]) | 
 |         [self.delegate viewWillClose]; | 
 |  | 
 |     [self setTitle:nil]; | 
 |  | 
 |     [self setPageRects:nil]; | 
 |     [self setPageYOrigins:nil]; | 
 |  | 
 |     [self setDocument:nil]; | 
 |     [super dealloc]; | 
 | } | 
 |  | 
 | #pragma mark WebPDFDocumentView and WebPDFDocumentRepresentation protocols | 
 |  | 
 | + (NSArray *)supportedMIMETypes | 
 | { | 
 |     return [WebPDFView supportedMIMETypes]; | 
 | } | 
 |  | 
 | #pragma mark WebDocumentView and WebDocumentRepresentation protocols | 
 |  | 
 | - (void)setDataSource:(WebDataSource *)dataSource | 
 | { | 
 |     [self dataSourceUpdated:dataSource]; | 
 |  | 
 |     if ([dataSource request]) | 
 |         [self _updateTitleForURL:[[dataSource request] URL]]; | 
 |  | 
 |     WAKView *superview = [self superview]; | 
 |     if (superview) | 
 |         [self setBoundsSize:[self convertRect:[superview bounds] fromView:superview].size]; | 
 | } | 
 |  | 
 | #pragma mark WebPDFViewPlaceholderDelegate stuff | 
 |  | 
 | - (void)_notifyDidCompleteLayout | 
 | { | 
 |     if ([NSThread isMainThread]) { | 
 |         if ([self.delegate respondsToSelector:@selector(didCompleteLayout)]) | 
 |             [self.delegate didCompleteLayout]; | 
 |     } else | 
 |         [self performSelectorOnMainThread:@selector(_notifyDidCompleteLayout) withObject:nil waitUntilDone:NO]; | 
 | } | 
 |  | 
 | #pragma mark WebDocumentView protocol | 
 |  | 
 | - (void)dataSourceUpdated:(WebDataSource *)dataSource | 
 | { | 
 |     _dataSource = dataSource; | 
 | } | 
 |  | 
 | - (void)layout | 
 | { | 
 |     if (!_didFinishLoad) | 
 |         return; | 
 |  | 
 |     if (self.pageRects) | 
 |         return; | 
 |  | 
 |     CGSize boundingSize = [self _computePageRects:_document]; | 
 |  | 
 |     [self setBoundsSize:boundingSize]; | 
 |  | 
 |     _didCompleteLayout = YES; | 
 |     [self _notifyDidCompleteLayout]; | 
 | } | 
 |  | 
 | - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow | 
 | { | 
 | } | 
 |  | 
 | - (void)viewDidMoveToHostWindow | 
 | { | 
 | } | 
 |  | 
 | #pragma mark WebDocumentRepresentation protocol | 
 |  | 
 | - (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource | 
 | { | 
 | } | 
 |  | 
 | - (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource | 
 | { | 
 | } | 
 |  | 
 | - (void)_doPostLoadOrUnlockTasks | 
 | { | 
 |     if (!_document) | 
 |         return; | 
 |  | 
 |     [self _updateTitleForDocumentIfAvailable]; | 
 |     [self _evaluateJSForDocument:_document]; | 
 |  | 
 |     // Any remaining work on the document should be done before this call to -layout, | 
 |     // which will hand ownership of the document to the delegate (UIWebPDFView) and | 
 |     // release the placeholder's document. | 
 |     [self layout]; | 
 | } | 
 |  | 
 | - (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource | 
 | { | 
 |     [self dataSourceUpdated:dataSource]; | 
 |  | 
 |     _didFinishLoad = YES; | 
 |     CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[dataSource data]); | 
 |     if (!provider) | 
 |         return; | 
 |  | 
 |     _document = CGPDFDocumentCreateWithProvider(provider); | 
 |  | 
 |     CGDataProviderRelease(provider); | 
 |  | 
 |     [self _doPostLoadOrUnlockTasks]; | 
 | } | 
 |  | 
 | - (BOOL)canProvideDocumentSource | 
 | { | 
 |     return NO; | 
 | } | 
 |  | 
 | - (NSString *)documentSource | 
 | { | 
 |     return nil; | 
 | } | 
 |  | 
 | #pragma mark internal stuff | 
 |  | 
 | - (void)_evaluateJSForDocument:(CGPDFDocumentRef)pdfDocument | 
 | { | 
 |     if (!pdfDocument || !CGPDFDocumentIsUnlocked(pdfDocument)) | 
 |         return; | 
 |  | 
 |     NSArray *scripts = allScriptsInPDFDocument(pdfDocument); | 
 |  | 
 |     if ([scripts count]) { | 
 |         JSGlobalContextRef ctx = JSGlobalContextCreate(0); | 
 |         JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx, _dataSource); | 
 |         for (NSString *script in scripts) | 
 |             JSEvaluateScript(ctx, OpaqueJSString::tryCreate(script).get(), jsPDFDoc, nullptr, 0, nullptr); | 
 |         JSGlobalContextRelease(ctx); | 
 |     } | 
 | } | 
 |  | 
 | - (void)_updateTitleForURL:(NSURL *)URL | 
 | { | 
 |     NSString *titleFromURL = [URL lastPathComponent]; | 
 |     if (![titleFromURL length] || [titleFromURL isEqualToString:@"/"]) | 
 |         titleFromURL = [[URL _web_hostString] _webkit_decodeHostName]; | 
 |  | 
 |     [self setTitle:titleFromURL]; | 
 |     [[self _frame] _dispatchDidReceiveTitle:titleFromURL]; | 
 | } | 
 |  | 
 | - (void)_updateTitleForDocumentIfAvailable | 
 | { | 
 |     if (!_document || !CGPDFDocumentIsUnlocked(_document)) | 
 |         return; | 
 |  | 
 |     NSString *title = nil; | 
 |  | 
 |     CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_document); | 
 |     CGPDFStringRef value; | 
 |     if (CGPDFDictionaryGetString(info, "Title", &value)) | 
 |         title = (NSString *)CGPDFStringCopyTextString(value); | 
 |  | 
 |     if ([title length]) { | 
 |         [self setTitle:title]; | 
 |         [[self _frame] _dispatchDidReceiveTitle:title]; | 
 |     } | 
 |  | 
 |     [title release]; | 
 | } | 
 |  | 
 | - (CGRect)_getPDFPageBounds:(CGPDFPageRef)page | 
 | {     | 
 |     if (!page) | 
 |         return CGRectZero; | 
 |  | 
 |     CGRect bounds = CGPDFPageGetBoxRect(page, kCGPDFCropBox); | 
 |     CGFloat rotation = CGPDFPageGetRotationAngle(page) * (M_PI / 180.0f); | 
 |     if (rotation != 0) | 
 |         bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeRotation(rotation)); | 
 |  | 
 |     bounds.origin.x     = roundf(bounds.origin.x); | 
 |     bounds.origin.y     = roundf(bounds.origin.y); | 
 |     bounds.size.width   = roundf(bounds.size.width); | 
 |     bounds.size.height  = roundf(bounds.size.height); | 
 |  | 
 |     return bounds; | 
 | } | 
 |  | 
 | - (CGSize)_computePageRects:(CGPDFDocumentRef)pdfDocument | 
 | { | 
 |     // Do we even need to compute the page rects? | 
 |     if (self.pageRects) | 
 |         return [self bounds].size; | 
 |  | 
 |     if (!pdfDocument) | 
 |         return CGSizeZero; | 
 |  | 
 |     if (!CGPDFDocumentIsUnlocked(pdfDocument)) | 
 |         return CGSizeZero; | 
 |  | 
 |     size_t pageCount = CGPDFDocumentGetNumberOfPages(pdfDocument); | 
 |     if (!pageCount) | 
 |         return CGSizeZero; | 
 |  | 
 |     NSMutableArray *pageRects = [NSMutableArray array]; | 
 |     if (!pageRects) | 
 |         return CGSizeZero; | 
 |  | 
 |     NSMutableArray *pageYOrigins = [NSMutableArray array]; | 
 |     if (!pageYOrigins) | 
 |         return CGSizeZero; | 
 |  | 
 |     // Temporary vector to avoid getting the page rects twice. | 
 |     WTF::Vector<CGRect> pageCropBoxes; | 
 |  | 
 |     // First we need to determine the width of the widest page. | 
 |     CGFloat maxPageWidth = 0.0f; | 
 |  | 
 |     // CG uses page numbers instead of page indices, so 1 based. | 
 |     for (size_t i = 1; i <= pageCount; i++) { | 
 |         CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, i); | 
 |         if (!page) { | 
 |             // So if there is a missing page, then effectively the document ends here. | 
 |             // See <rdar://problem/10428152> iOS Mail crashes when opening a PDF | 
 |             pageCount = i - 1; | 
 |             break; | 
 |         } | 
 |  | 
 |         CGRect pageRect = [self _getPDFPageBounds:page]; | 
 |  | 
 |         maxPageWidth = std::max<CGFloat>(maxPageWidth, pageRect.size.width); | 
 |  | 
 |         pageCropBoxes.append(pageRect); | 
 |     } | 
 |  | 
 |     if (!pageCount) | 
 |         return CGSizeZero; | 
 |  | 
 |     CGFloat scalingFactor = _containerSize.width / maxPageWidth; | 
 |     if (_containerSize.width < FLT_EPSILON) | 
 |         scalingFactor = 1.0; | 
 |  | 
 |     // Including the margins, what is the desired width of the content? | 
 |     CGFloat desiredWidth = (maxPageWidth + (PAGE_WIDTH_INSET * 2.0f)) * scalingFactor; | 
 |     CGFloat desiredHeight = PAGE_HEIGHT_INSET; | 
 |  | 
 |     for (size_t i = 0; i < pageCount; i++) { | 
 |  | 
 |         CGRect pageRect = pageCropBoxes[i]; | 
 |  | 
 |         // Apply our scaling to this page's bounds. | 
 |         pageRect.size.width  = roundf(pageRect.size.width * scalingFactor); | 
 |         pageRect.size.height = roundf(pageRect.size.height * scalingFactor); | 
 |  | 
 |         // Center this page horizontally and update the starting vertical offset. | 
 |         pageRect.origin.x = roundf((desiredWidth - pageRect.size.width) / 2.0f); | 
 |         pageRect.origin.y = desiredHeight; | 
 |  | 
 |         // Save this page's rect and minimum y-offset. | 
 |         [pageRects addObject:[NSValue _web_valueWithCGRect:pageRect]]; | 
 |         [pageYOrigins addObject:[NSNumber numberWithFloat:CGRectGetMinY(pageRect)]]; | 
 |  | 
 |         // Update the next starting vertical offset. | 
 |         desiredHeight += pageRect.size.height + PAGE_HEIGHT_INSET; | 
 |     } | 
 |  | 
 |     // Save the resulting page rects array and minimum y-offsets array. | 
 |     self.pageRects = pageRects; | 
 |     self.pageYOrigins = pageYOrigins; | 
 |  | 
 |     // Determine the result desired bounds. | 
 |     return CGSizeMake(roundf(desiredWidth), roundf(desiredHeight)); | 
 | } | 
 |  | 
 | - (void)didUnlockDocument | 
 | { | 
 |     [self _doPostLoadOrUnlockTasks]; | 
 | } | 
 |  | 
 | - (CGRect)rectForPageNumber:(NSUInteger)pageNumber | 
 | { | 
 |     // Page number is 1-based, not 0 based. | 
 |     if ((!pageNumber) || (pageNumber > [_pageRects count])) | 
 |         return CGRectNull; | 
 |  | 
 |     return [[_pageRects objectAtIndex:pageNumber - 1] _web_CGRectValue]; | 
 | } | 
 |  | 
 | - (void)simulateClickOnLinkToURL:(NSURL *)URL | 
 | { | 
 |     if (!URL) | 
 |         return; | 
 |  | 
 |     auto event = MouseEvent::create(eventNames().clickEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes, Event::IsComposed::Yes, | 
 |         MonotonicTime::now(), nullptr, 1, { }, { }, { }, { }, 0, 0, nullptr, 0, 0, MouseEvent::IsSimulated::Yes); | 
 |  | 
 |     // Call to the frame loader because this is where our security checks are made. | 
 |     Frame* frame = core([_dataSource webFrame]); | 
 |     FrameLoadRequest frameLoadRequest { *frame->document(), frame->document()->securityOrigin(), { URL }, { }, InitiatedByMainFrame::Unknown }; | 
 | frameLoadRequest.setReferrerPolicy(ReferrerPolicy::NoReferrer); | 
 |     frame->loader().loadFrameRequest(WTFMove(frameLoadRequest), event.ptr(), nullptr); | 
 | } | 
 |  | 
 | @end | 
 |  | 
 | #endif /* PLATFORM(IOS_FAMILY) */ |