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