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