7a6220f27e5b834439d0bc1ee7358c5b44fbea79
[WebKit-https.git] / Source / WebKit / ios / WebView / WebPDFViewPlaceholder.mm
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2010, 2011 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 #if PLATFORM(IOS)
27
28 #import "WebPDFViewPlaceholder.h"
29
30 #import "WebFrameInternal.h"
31 #import "WebPDFViewIOS.h"
32 #import <JavaScriptCore/JSContextRef.h>
33 #import <JavaScriptCore/JSStringRef.h>
34 #import <JavaScriptCore/JSStringRefCF.h>
35 #import <WebCore/DataTransfer.h>
36 #import <WebCore/EventHandler.h>
37 #import <WebCore/EventNames.h>
38 #import <WebCore/FormState.h>
39 #import <WebCore/Frame.h>
40 #import <WebCore/FrameLoadRequest.h>
41 #import <WebCore/FrameLoader.h>
42 #import <WebCore/HTMLFormElement.h>
43 #import <WebCore/MouseEvent.h>
44 #import <WebKitLegacy/WebDataSourcePrivate.h>
45 #import <WebKitLegacy/WebFramePrivate.h>
46 #import <WebKitLegacy/WebJSPDFDoc.h>
47 #import <WebKitLegacy/WebNSURLExtras.h>
48 #import <WebKitLegacy/WebNSViewExtras.h>
49 #import <WebKitLegacy/WebPDFDocumentExtras.h>
50 #import <WebKitLegacy/WebViewPrivate.h>
51 #import <wtf/CurrentTime.h>
52 #import <wtf/SoftLinking.h>
53 #import <wtf/Vector.h>
54
55 using namespace WebCore;
56
57 @interface WebPDFView (Secrets)
58 + (Class)_representationClassForWebFrame:(WebFrame *)webFrame;
59 @end
60
61 #pragma mark Constants
62
63 static const float PAGE_WIDTH_INSET = 4.0f * 2.0f;
64 static const float PAGE_HEIGHT_INSET = 4.0f * 2.0f;
65
66 #pragma mark NSValue helpers
67
68 @interface NSValue (_web_Extensions)
69 + (NSValue *)_web_valueWithCGRect:(CGRect)rect;
70 @end
71
72 @implementation NSValue (_web_Extensions)
73
74 + (NSValue *)_web_valueWithCGRect:(CGRect)rect
75 {
76     return [NSValue valueWithBytes:&rect objCType:@encode(CGRect)];
77 }
78
79 - (CGRect)CGRectValue
80 {
81     CGRect result;
82     [self getValue:&result];
83     return result;
84 }
85
86 @end
87
88 #pragma mark -
89
90 @interface WebPDFViewPlaceholder ()
91
92 - (void)_evaluateJSForDocument:(CGPDFDocumentRef)document;
93 - (void)_updateTitleForDocumentIfAvailable;
94 - (void)_updateTitleForURL:(NSURL *)URL;
95 - (CGSize)_computePageRects:(CGPDFDocumentRef)document;
96
97 @property (retain) NSArray *pageRects;
98 @property (retain) NSArray *pageYOrigins;
99 @property (retain) NSString *title;
100
101 @end
102
103 #pragma mark WebPDFViewPlaceholder implementation
104
105 @implementation WebPDFViewPlaceholder {
106     NSString *_title;
107     NSArray *_pageRects;
108     NSArray *_pageYOrigins;
109     CGPDFDocumentRef _document;
110     WebDataSource *_dataSource; // weak to prevent cycles.
111     
112     NSObject<WebPDFViewPlaceholderDelegate> *_delegate;
113     
114     BOOL _didFinishLoad;
115     
116     CGSize _containerSize;
117 }
118
119 @synthesize delegate = _delegate;
120 @synthesize pageRects = _pageRects;
121 @synthesize pageYOrigins = _pageYOrigins;
122 @synthesize document = _document;
123 @synthesize title = _title;
124 @synthesize containerSize = _containerSize;
125
126 - (CGPDFDocumentRef)document
127 {
128     @synchronized(self) {
129         if (_document)
130             return _document;
131     }
132
133     if ([self.delegate respondsToSelector:@selector(cgPDFDocument)])
134         return [self.delegate cgPDFDocument];
135
136     return nil;
137 }
138
139 - (void)setDocument:(CGPDFDocumentRef)document
140 {
141     @synchronized(self) {
142         CGPDFDocumentRetain(document);
143         CGPDFDocumentRelease(_document);
144         _document = document;
145     }
146 }
147
148 - (void)clearDocument
149 {
150     [self setDocument:nil];
151 }
152
153 - (CGPDFDocumentRef)doc
154 {
155     return [self document];
156 }
157
158 - (NSUInteger)totalPages
159 {
160     return CGPDFDocumentGetNumberOfPages([self document]);
161 }
162
163 + (void)setAsPDFDocRepAndView
164 {
165     [WebView _setPDFRepresentationClass:[WebPDFViewPlaceholder class]];
166     [WebView _setPDFViewClass:[WebPDFViewPlaceholder class]];
167 }
168
169 // This is a secret protocol for WebDataSource and WebFrameView to offer us the opportunity to do something different 
170 // depending upon which frame is trying to instantiate a representation for PDF.
171 + (Class)_representationClassForWebFrame:(WebFrame *)webFrame
172 {
173     return [WebPDFView _representationClassForWebFrame:webFrame];
174 }
175
176 - (void)dealloc
177 {
178     if ([self.delegate respondsToSelector:@selector(viewWillClose)])
179         [self.delegate viewWillClose];
180
181     [self setTitle:nil];
182
183     [self setPageRects:nil];
184     [self setPageYOrigins:nil];
185
186     [self setDocument:nil];
187     [super dealloc];
188 }
189
190 #pragma mark WebPDFDocumentView and WebPDFDocumentRepresentation protocols
191
192 + (NSArray *)supportedMIMETypes
193 {
194     return [WebPDFView supportedMIMETypes];
195 }
196
197 #pragma mark WebDocumentView and WebDocumentRepresentation protocols
198
199 - (void)setDataSource:(WebDataSource *)dataSource
200 {
201     [self dataSourceUpdated:dataSource];
202
203     if ([dataSource request])
204         [self _updateTitleForURL:[[dataSource request] URL]];
205
206     WAKView *superview = [self superview];
207     if (superview)
208         [self setBoundsSize:[self convertRect:[superview bounds] fromView:superview].size];
209 }
210
211 #pragma mark WebPDFViewPlaceholderDelegate stuff
212
213 - (void)_notifyDidCompleteLayout
214 {
215     if ([NSThread isMainThread]) {
216         if ([self.delegate respondsToSelector:@selector(didCompleteLayout)])
217             [self.delegate didCompleteLayout];
218     } else
219         [self performSelectorOnMainThread:@selector(_notifyDidCompleteLayout) withObject:nil waitUntilDone:NO];
220 }
221
222 #pragma mark WebDocumentView protocol
223
224 - (void)dataSourceUpdated:(WebDataSource *)dataSource
225 {
226     _dataSource = dataSource;
227 }
228
229 - (void)layout
230 {
231     if (!_didFinishLoad)
232         return;
233
234     if (self.pageRects)
235         return;
236
237     CGSize boundingSize = [self _computePageRects:_document];
238
239     [self setBoundsSize:boundingSize];
240
241     _didCompleteLayout = YES;
242     [self _notifyDidCompleteLayout];
243 }
244
245 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
246 {
247 }
248
249 - (void)viewDidMoveToHostWindow
250 {
251 }
252
253 #pragma mark WebDocumentRepresentation protocol
254
255 - (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource
256 {
257 }
258
259 - (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource
260 {
261 }
262
263 - (void)_doPostLoadOrUnlockTasks
264 {
265     if (!_document)
266         return;
267
268     [self _updateTitleForDocumentIfAvailable];
269     [self _evaluateJSForDocument:_document];
270
271     // Any remaining work on the document should be done before this call to -layout,
272     // which will hand ownership of the document to the delegate (UIWebPDFView) and
273     // release the placeholder's document.
274     [self layout];
275 }
276
277 - (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource
278 {
279     [self dataSourceUpdated:dataSource];
280
281     _didFinishLoad = YES;
282     CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)[dataSource data]);
283     if (!provider)
284         return;
285
286     _document = CGPDFDocumentCreateWithProvider(provider);
287
288     CGDataProviderRelease(provider);
289
290     [self _doPostLoadOrUnlockTasks];
291 }
292
293 - (BOOL)canProvideDocumentSource
294 {
295     return NO;
296 }
297
298 - (NSString *)documentSource
299 {
300     return nil;
301 }
302
303 #pragma mark internal stuff
304
305 - (void)_evaluateJSForDocument:(CGPDFDocumentRef)pdfDocument
306 {
307     if (!pdfDocument || !CGPDFDocumentIsUnlocked(pdfDocument))
308         return;
309
310     NSArray *scripts = allScriptsInPDFDocument(pdfDocument);
311
312     NSUInteger scriptCount = [scripts count];
313     if (scriptCount) {
314
315         JSGlobalContextRef ctx = JSGlobalContextCreate(0);
316         JSObjectRef jsPDFDoc = makeJSPDFDoc(ctx, _dataSource);
317
318         for (NSUInteger i = 0; i < scriptCount; ++i) {
319             JSStringRef script = JSStringCreateWithCFString((CFStringRef)[scripts objectAtIndex:i]);
320             JSEvaluateScript(ctx, script, jsPDFDoc, 0, 0, 0);
321             JSStringRelease(script);
322         }
323
324         JSGlobalContextRelease(ctx);
325     }
326 }
327
328 - (void)_updateTitleForURL:(NSURL *)URL
329 {
330     NSString *titleFromURL = [URL lastPathComponent];
331     if (![titleFromURL length] || [titleFromURL isEqualToString:@"/"])
332         titleFromURL = [[URL _web_hostString] _webkit_decodeHostName];
333
334     [self setTitle:titleFromURL];
335     [[self _frame] _dispatchDidReceiveTitle:titleFromURL];
336 }
337
338 - (void)_updateTitleForDocumentIfAvailable
339 {
340     if (!_document || !CGPDFDocumentIsUnlocked(_document))
341         return;
342
343     NSString *title = nil;
344
345     CGPDFDictionaryRef info = CGPDFDocumentGetInfo(_document);
346     CGPDFStringRef value;
347     if (CGPDFDictionaryGetString(info, "Title", &value))
348         title = (NSString *)CGPDFStringCopyTextString(value);
349
350     if ([title length]) {
351         [self setTitle:title];
352         [[self _frame] _dispatchDidReceiveTitle:title];
353     }
354
355     [title release];
356 }
357
358 - (CGRect)_getPDFPageBounds:(CGPDFPageRef)page
359 {    
360     if (!page)
361         return CGRectZero;
362
363     CGRect bounds = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
364     CGFloat rotation = CGPDFPageGetRotationAngle(page) * (M_PI / 180.0f);
365     if (rotation != 0)
366         bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeRotation(rotation));
367
368     bounds.origin.x     = roundf(bounds.origin.x);
369     bounds.origin.y     = roundf(bounds.origin.y);
370     bounds.size.width   = roundf(bounds.size.width);
371     bounds.size.height  = roundf(bounds.size.height);
372
373     return bounds;
374 }
375
376 - (CGSize)_computePageRects:(CGPDFDocumentRef)pdfDocument
377 {
378     // Do we even need to compute the page rects?
379     if (self.pageRects)
380         return [self bounds].size;
381
382     if (!pdfDocument)
383         return CGSizeZero;
384
385     if (!CGPDFDocumentIsUnlocked(pdfDocument))
386         return CGSizeZero;
387
388     size_t pageCount = CGPDFDocumentGetNumberOfPages(pdfDocument);
389     if (!pageCount)
390         return CGSizeZero;
391
392     NSMutableArray *pageRects = [NSMutableArray array];
393     if (!pageRects)
394         return CGSizeZero;
395
396     NSMutableArray *pageYOrigins = [NSMutableArray array];
397     if (!pageYOrigins)
398         return CGSizeZero;
399
400     // Temporary vector to avoid getting the page rects twice.
401     WTF::Vector<CGRect> pageCropBoxes;
402
403     // First we need to determine the width of the widest page.
404     CGFloat maxPageWidth = 0.0f;
405
406     // CG uses page numbers instead of page indices, so 1 based.
407     for (size_t i = 1; i <= pageCount; i++) {
408         CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, i);
409         if (!page) {
410             // So if there is a missing page, then effectively the document ends here.
411             // See <rdar://problem/10428152> iOS Mail crashes when opening a PDF
412             pageCount = i - 1;
413             break;
414         }
415
416         CGRect pageRect = [self _getPDFPageBounds:page];
417
418         maxPageWidth = std::max<CGFloat>(maxPageWidth, pageRect.size.width);
419
420         pageCropBoxes.append(pageRect);
421     }
422
423     if (!pageCount)
424         return CGSizeZero;
425
426     CGFloat scalingFactor = _containerSize.width / maxPageWidth;
427     if (_containerSize.width < FLT_EPSILON)
428         scalingFactor = 1.0;
429
430     // Including the margins, what is the desired width of the content?
431     CGFloat desiredWidth = (maxPageWidth + (PAGE_WIDTH_INSET * 2.0f)) * scalingFactor;
432     CGFloat desiredHeight = PAGE_HEIGHT_INSET;
433
434     for (size_t i = 0; i < pageCount; i++) {
435
436         CGRect pageRect = pageCropBoxes[i];
437
438         // Apply our scaling to this page's bounds.
439         pageRect.size.width  = roundf(pageRect.size.width * scalingFactor);
440         pageRect.size.height = roundf(pageRect.size.height * scalingFactor);
441
442         // Center this page horizontally and update the starting vertical offset.
443         pageRect.origin.x = roundf((desiredWidth - pageRect.size.width) / 2.0f);
444         pageRect.origin.y = desiredHeight;
445
446         // Save this page's rect and minimum y-offset.
447         [pageRects addObject:[NSValue _web_valueWithCGRect:pageRect]];
448         [pageYOrigins addObject:[NSNumber numberWithFloat:CGRectGetMinY(pageRect)]];
449
450         // Update the next starting vertical offset.
451         desiredHeight += pageRect.size.height + PAGE_HEIGHT_INSET;
452     }
453
454     // Save the resulting page rects array and minimum y-offsets array.
455     self.pageRects = pageRects;
456     self.pageYOrigins = pageYOrigins;
457
458     // Determine the result desired bounds.
459     return CGSizeMake(roundf(desiredWidth), roundf(desiredHeight));
460 }
461
462 - (void)didUnlockDocument
463 {
464     [self _doPostLoadOrUnlockTasks];
465 }
466
467 - (CGRect)rectForPageNumber:(NSUInteger)pageNumber
468 {
469     // Page number is 1-based, not 0 based.
470     if ((!pageNumber) || (pageNumber > [_pageRects count]))
471         return CGRectNull;
472
473     return [[_pageRects objectAtIndex:pageNumber - 1] CGRectValue];
474 }
475
476 - (void)simulateClickOnLinkToURL:(NSURL *)URL
477 {
478     if (!URL)
479         return;
480
481     // Construct an event to simulate a click.
482     RefPtr<Event> event = MouseEvent::create(eventNames().clickEvent, true, true, currentTime(), 0, 1, 0, 0, 0, 0,
483 #if ENABLE(POINTER_LOCK)
484         0, 0,
485 #endif
486         false, false, false, false, 0, 0, 0, 0, 0, true);
487
488     // Call to the frame loader because this is where our security checks are made.
489     Frame* frame = core([_dataSource webFrame]);
490     FrameLoadRequest frameLoadRequest { *frame->document(), frame->document()->securityOrigin(), { URL }, { }, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, ShouldOpenExternalURLsPolicy::ShouldNotAllow,  NavigationInitiatedByMainFrame::Unknown };
491     frame->loader().loadFrameRequest(WTFMove(frameLoadRequest), event.get(), nullptr);
492 }
493
494 @end
495
496 #endif /* PLATFORM(IOS) */