blob: e4776dc1f452ace5e40c01bb1e002d38c45e3e1e [file] [log] [blame]
/*
* 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;
__weak WebDataSource *_dataSource; // Weak to prevent cycles.
__weak 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;
auto provider = adoptCF(CGDataProviderCreateWithCFData((CFDataRef)[dataSource data]));
if (!provider)
return;
_document = CGPDFDocumentCreateWithProvider(provider.get());
[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;
RetainPtr<CFStringRef> title;
CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_document);
CGPDFStringRef value;
if (CGPDFDictionaryGetString(info, "Title", &value))
title = adoptCF(CGPDFStringCopyTextString(value));
if (title && CFStringGetLength(title.get())) {
[self setTitle:(NSString *)title.get()];
[[self _frame] _dispatchDidReceiveTitle:(NSString *)title.get()];
}
}
- (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) */