1029c1dd1cb23a2f8567821dbdac7aaff3f9661f
[WebKit-https.git] / Source / WebKitLegacy / mac / WebView / WebDataSource.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007, 2008, 2012 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "WebDataSource.h"
30
31 #import "WebArchive.h"
32 #import "WebArchiveInternal.h"
33 #import "WebDataSourceInternal.h"
34 #import "WebDocument.h"
35 #import "WebDocumentLoaderMac.h"
36 #import "WebFrameInternal.h"
37 #import "WebFrameLoadDelegate.h"
38 #import "WebFrameLoaderClient.h"
39 #import "WebFrameViewInternal.h"
40 #import "WebHTMLRepresentation.h"
41 #import "WebKitErrorsPrivate.h"
42 #import "WebKitLogging.h"
43 #import "WebKitNSStringExtras.h"
44 #import "WebKitStatisticsPrivate.h"
45 #import "WebNSURLExtras.h"
46 #import "WebNSURLRequestExtras.h"
47 #import "WebPDFRepresentation.h"
48 #import "WebResourceInternal.h"
49 #import "WebResourceLoadDelegate.h"
50 #import "WebViewInternal.h"
51 #import <JavaScriptCore/InitializeThreading.h>
52 #import <WebCore/ApplicationCacheStorage.h>
53 #import <WebCore/FrameLoader.h>
54 #import <WebCore/LegacyWebArchive.h>
55 #import <WebCore/MIMETypeRegistry.h>
56 #import <WebCore/ResourceRequest.h>
57 #import <WebCore/SharedBuffer.h>
58 #import <WebCore/WebCoreObjCExtras.h>
59 #import <WebCore/WebCoreURLResponse.h>
60 #import <WebKitLegacy/DOMHTML.h>
61 #import <WebKitLegacy/DOMPrivate.h>
62 #import <wtf/Assertions.h>
63 #import <wtf/MainThread.h>
64 #import <wtf/RefPtr.h>
65 #import <wtf/RetainPtr.h>
66 #import <wtf/RunLoop.h>
67 #import <wtf/URL.h>
68
69 #if PLATFORM(IOS_FAMILY)
70 #import "WebPDFViewIOS.h"
71 #endif
72
73 #if USE(QUICK_LOOK)
74 #import <WebCore/PreviewLoaderClient.h>
75 #import <WebCore/QuickLook.h>
76 #endif
77
78 class WebDataSourcePrivate
79 {
80 public:
81     WebDataSourcePrivate(Ref<WebDocumentLoaderMac>&& loader)
82         : loader(WTFMove(loader))
83         , representationFinishedLoading(NO)
84         , includedInWebKitStatistics(NO)
85 #if PLATFORM(IOS_FAMILY)
86         , _dataSourceDelegate(nil)
87 #endif
88     {
89     }
90     ~WebDataSourcePrivate()
91     {
92         // We might run in to infinite recursion if we're stopping loading as the result of detaching from the frame.
93         // Therefore, DocumentLoader::detachFromFrame() did some smart things to stop the recursion.
94         // As a result of breaking the resursion, DocumentLoader::m_subresourceLoader
95         // and DocumentLoader::m_plugInStreamLoaders might not be empty at this time.
96         // See <rdar://problem/9673866> for more details.
97         ASSERT(!loader->isLoading() || loader->isStopping());
98         loader->detachDataSource();
99     }
100
101     Ref<WebDocumentLoaderMac> loader;
102     RetainPtr<id<WebDocumentRepresentation> > representation;
103     BOOL representationFinishedLoading;
104     BOOL includedInWebKitStatistics;
105 #if PLATFORM(IOS_FAMILY)
106     NSObject<WebDataSourcePrivateDelegate> *_dataSourceDelegate;
107 #endif
108 #if USE(QUICK_LOOK)
109     RetainPtr<NSDictionary> _quickLookContent;
110     RefPtr<WebCore::PreviewLoaderClient> _quickLookPreviewLoaderClient;
111 #endif
112 };
113
114 static inline WebDataSourcePrivate* toPrivate(void* privateAttribute)
115 {
116     return reinterpret_cast<WebDataSourcePrivate*>(privateAttribute);
117 }
118
119 @interface WebDataSource (WebFileInternal)
120 @end
121
122 @implementation WebDataSource (WebFileInternal)
123
124 - (void)_setRepresentation:(id<WebDocumentRepresentation>)representation
125 {
126     toPrivate(_private)->representation = representation;
127     toPrivate(_private)->representationFinishedLoading = NO;
128 }
129
130 void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes)
131 {
132     NSEnumerator *enumerator = [supportTypes objectEnumerator];
133     ASSERT(enumerator != nil);
134     NSString *mime = nil;
135     while ((mime = [enumerator nextObject]) != nil) {
136         // Don't clobber previously-registered classes.
137         if ([allTypes objectForKey:mime] == nil)
138             [allTypes setObject:objCClass forKey:mime];
139     }
140 }
141
142 + (Class)_representationClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins
143 {
144     Class repClass;
145     return [WebView _viewClass:nil andRepresentationClass:&repClass forMIMEType:MIMEType allowingPlugins:allowPlugins] ? repClass : nil;
146 }
147 @end
148
149 @implementation WebDataSource (WebPrivate)
150
151 + (void)initialize
152 {
153     if (self == [WebDataSource class]) {
154 #if !PLATFORM(IOS_FAMILY)
155         JSC::initializeThreading();
156         RunLoop::initializeMainRunLoop();
157 #endif
158     }
159 }
160
161 - (NSError *)_mainDocumentError
162 {
163     return toPrivate(_private)->loader->mainDocumentError();
164 }
165
166 - (void)_addSubframeArchives:(NSArray *)subframeArchives
167 {
168     // FIXME: This SPI is poor, poor design. Can we come up with another solution for those who need it?
169     for (WebArchive *archive in subframeArchives)
170         toPrivate(_private)->loader->addAllArchiveResources(*[archive _coreLegacyWebArchive]);
171 }
172
173 #if !PLATFORM(IOS_FAMILY)
174
175 - (NSFileWrapper *)_fileWrapperForURL:(NSURL *)URL
176 {
177     if ([URL isFileURL])
178         return [[[NSFileWrapper alloc] initWithURL:[URL URLByResolvingSymlinksInPath] options:0 error:nullptr] autorelease];
179
180     if (auto resource = [self subresourceForURL:URL])
181         return [resource _fileWrapperRepresentation];
182
183     if (auto cachedResponse = [[self _webView] _cachedResponseForURL:URL]) {
184         NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease];
185         [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]];
186         return wrapper;
187     }
188     
189     return nil;
190 }
191
192 #endif
193
194 - (NSString *)_responseMIMEType
195 {
196     return [[self response] MIMEType];
197 }
198
199 - (void)_setDeferMainResourceDataLoad:(BOOL)flag
200 {
201     toPrivate(_private)->loader->setDeferMainResourceDataLoad(flag);
202 }
203
204 #if PLATFORM(IOS_FAMILY)
205 - (void)_setOverrideTextEncodingName:(NSString *)encoding
206 {
207     toPrivate(_private)->loader->setOverrideEncoding([encoding UTF8String]);
208 }
209 #endif
210
211 - (void)_setAllowToBeMemoryMapped
212 {
213 }
214
215 - (void)setDataSourceDelegate:(NSObject<WebDataSourcePrivateDelegate> *)delegate
216 {
217 }
218
219 - (NSObject<WebDataSourcePrivateDelegate> *)dataSourceDelegate
220 {
221     return nullptr;
222 }
223
224 #if PLATFORM(IOS_FAMILY)
225 - (NSDictionary *)_quickLookContent
226 {
227 #if USE(QUICK_LOOK)
228     return toPrivate(_private)->_quickLookContent.get();
229 #else
230     return nil;
231 #endif
232 }
233 #endif
234
235 @end
236
237 @implementation WebDataSource (WebInternal)
238
239 - (void)_finishedLoading
240 {
241     toPrivate(_private)->representationFinishedLoading = YES;
242     [[self representation] finishedLoadingWithDataSource:self];
243 }
244
245 - (void)_receivedData:(NSData *)data
246 {
247     // protect self temporarily, as the bridge receivedData call could remove our last ref
248     RetainPtr<WebDataSource> protect(self);
249     
250     [[self representation] receivedData:data withDataSource:self];
251     [[[[self webFrame] frameView] documentView] dataSourceUpdated:self];
252 }
253
254 - (void)_setMainDocumentError:(NSError *)error
255 {
256     if (!toPrivate(_private)->representationFinishedLoading) {
257         toPrivate(_private)->representationFinishedLoading = YES;
258         [[self representation] receivedError:error withDataSource:self];
259     }
260 }
261
262 - (void)_revertToProvisionalState
263 {
264     [self _setRepresentation:nil];
265 }
266
267 + (NSMutableDictionary *)_repTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission
268 {
269     static NSMutableDictionary *repTypes = nil;
270     static BOOL addedImageTypes = NO;
271     
272     if (!repTypes) {
273         repTypes = [[NSMutableDictionary alloc] init];
274         addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedNonImageMIMETypes]);
275         addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedMediaMIMETypes]);
276         
277         // Since this is a "secret default" we don't both registering it.
278         BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"];
279         if (!omitPDFSupport)
280 #if PLATFORM(IOS_FAMILY)
281 #define WebPDFRepresentation ([WebView _getPDFRepresentationClass])
282 #endif
283             addTypesFromClass(repTypes, [WebPDFRepresentation class], [WebPDFRepresentation supportedMIMETypes]);
284 #if PLATFORM(IOS_FAMILY)
285 #undef WebPDFRepresentation
286 #endif
287     }
288     
289     if (!addedImageTypes && !allowImageTypeOmission) {
290         addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedImageMIMETypes]);
291         addedImageTypes = YES;
292     }
293     
294     return repTypes;
295 }
296
297 - (void)_replaceSelectionWithArchive:(WebArchive *)archive selectReplacement:(BOOL)selectReplacement
298 {
299     DOMDocumentFragment *fragment = [self _documentFragmentWithArchive:archive];
300     if (fragment)
301         [[self webFrame] _replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:NO matchStyle:NO];
302 }
303
304 // FIXME: There are few reasons why this method and many of its related methods can't be pushed entirely into WebCore in the future.
305 - (DOMDocumentFragment *)_documentFragmentWithArchive:(WebArchive *)archive
306 {
307     ASSERT(archive);
308     WebResource *mainResource = [archive mainResource];
309     if (mainResource) {
310         NSString *MIMEType = [mainResource MIMEType];
311         if ([WebView canShowMIMETypeAsHTML:MIMEType]) {
312             NSString *markupString = [[NSString alloc] initWithData:[mainResource data] encoding:NSUTF8StringEncoding];
313
314             // FIXME: seems poor form to do this as a side effect of getting a document fragment
315             toPrivate(_private)->loader->addAllArchiveResources(*[archive _coreLegacyWebArchive]);
316
317             DOMDocumentFragment *fragment = [[self webFrame] _documentFragmentWithMarkupString:markupString baseURLString:[[mainResource URL] _web_originalDataAsString]];
318             [markupString release];
319             return fragment;
320         } else if (WebCore::MIMETypeRegistry::isSupportedImageMIMEType(MIMEType)) {
321             return [self _documentFragmentWithImageResource:mainResource];
322             
323         }
324     }
325     return nil;
326 }
327
328 - (DOMDocumentFragment *)_documentFragmentWithImageResource:(WebResource *)resource
329 {
330     DOMElement *imageElement = [self _imageElementWithImageResource:resource];
331     if (!imageElement)
332         return nil;
333     DOMDocumentFragment *fragment = [[[self webFrame] DOMDocument] createDocumentFragment];
334     [fragment appendChild:imageElement];
335     return fragment;
336 }
337
338 - (DOMElement *)_imageElementWithImageResource:(WebResource *)resource
339 {
340     if (!resource)
341         return 0;
342     
343     [self addSubresource:resource];
344     
345     DOMElement *imageElement = [[[self webFrame] DOMDocument] createElement:@"img"];
346     
347     // FIXME: calling _web_originalDataAsString on a file URL returns an absolute path. Workaround this.
348     NSURL *URL = [resource URL];
349     [imageElement setAttribute:@"src" value:[URL isFileURL] ? [URL absoluteString] : [URL _web_originalDataAsString]];
350     
351     return imageElement;
352 }
353
354 // May return nil if not initialized with a URL.
355 - (NSURL *)_URL
356 {
357     const URL& url = toPrivate(_private)->loader->url();
358     if (url.isEmpty())
359         return nil;
360     return url;
361 }
362
363 - (WebView *)_webView
364 {
365     return [[self webFrame] webView];
366 }
367
368 - (BOOL)_isDocumentHTML
369 {
370     NSString *MIMEType = [self _responseMIMEType];
371     return [WebView canShowMIMETypeAsHTML:MIMEType];
372 }
373
374 - (void)_makeRepresentation
375 {
376     Class repClass = [[self class] _representationClassForMIMEType:[self _responseMIMEType] allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]];
377
378 #if PLATFORM(IOS_FAMILY)
379     if ([repClass respondsToSelector:@selector(_representationClassForWebFrame:)])
380         repClass = [repClass performSelector:@selector(_representationClassForWebFrame:) withObject:[self webFrame]];
381 #endif
382
383     // Check if the data source was already bound?
384     if (![[self representation] isKindOfClass:repClass]) {
385         id newRep = repClass != nil ? [(NSObject *)[repClass alloc] init] : nil;
386         [self _setRepresentation:(id <WebDocumentRepresentation>)newRep];
387         [newRep release];
388     }
389
390     id<WebDocumentRepresentation> representation = toPrivate(_private)->representation.get();
391     [representation setDataSource:self];
392 #if PLATFORM(IOS_FAMILY)
393     toPrivate(_private)->loader->setResponseMIMEType([self _responseMIMEType]);
394 #endif
395 }
396
397 - (WebCore::DocumentLoader*)_documentLoader
398 {
399     return toPrivate(_private)->loader.ptr();
400 }
401
402 - (id)_initWithDocumentLoader:(Ref<WebDocumentLoaderMac>&&)loader
403 {
404     self = [super init];
405     if (!self)
406         return nil;
407
408     _private = static_cast<void*>(new WebDataSourcePrivate(WTFMove(loader)));
409         
410     LOG(Loading, "creating datasource for %@", static_cast<NSURL *>(toPrivate(_private)->loader->request().url()));
411
412     if ((toPrivate(_private)->includedInWebKitStatistics = [[self webFrame] _isIncludedInWebKitStatistics]))
413         ++WebDataSourceCount;
414
415     return self;
416 }
417
418 #if USE(QUICK_LOOK)
419 - (WebCore::PreviewLoaderClient*)_quickLookPreviewLoaderClient
420 {
421     return toPrivate(_private)->_quickLookPreviewLoaderClient.get();
422 }
423
424 - (void)_setQuickLookContent:(NSDictionary *)quickLookContent
425 {
426     toPrivate(_private)->_quickLookContent = adoptNS([quickLookContent copy]);
427 }
428
429 - (void)_setQuickLookPreviewLoaderClient:(WebCore::PreviewLoaderClient*)quickLookPreviewLoaderClient
430 {
431     toPrivate(_private)->_quickLookPreviewLoaderClient = quickLookPreviewLoaderClient;
432 }
433 #endif
434
435 @end
436
437 @implementation WebDataSource
438
439 - (instancetype)initWithRequest:(NSURLRequest *)request
440 {
441     return [self _initWithDocumentLoader:WebDocumentLoaderMac::create(request, WebCore::SubstituteData())];
442 }
443
444 - (void)dealloc
445 {
446     if (WebCoreObjCScheduleDeallocateOnMainThread([WebDataSource class], self))
447         return;
448
449     if (toPrivate(_private) && toPrivate(_private)->includedInWebKitStatistics)
450         --WebDataSourceCount;
451
452 #if USE(QUICK_LOOK)
453     // Added in -[WebCoreResourceHandleAsDelegate connection:didReceiveResponse:].
454     if (NSURL *url = [[self response] URL])
455         WebCore::removeQLPreviewConverterForURL(url);
456 #endif
457
458     delete toPrivate(_private);
459
460     [super dealloc];
461 }
462
463 - (NSData *)data
464 {
465     RefPtr<WebCore::SharedBuffer> mainResourceData = toPrivate(_private)->loader->mainResourceData();
466     if (!mainResourceData)
467         return nil;
468     return mainResourceData->createNSData().autorelease();
469 }
470
471 - (id <WebDocumentRepresentation>)representation
472 {
473     return toPrivate(_private)->representation.get();
474 }
475
476 - (WebFrame *)webFrame
477 {
478     if (auto* frame = toPrivate(_private)->loader->frame())
479         return kit(frame);
480
481     return nil;
482 }
483
484 - (NSURLRequest *)initialRequest
485 {
486     return toPrivate(_private)->loader->originalRequest().nsURLRequest(WebCore::HTTPBodyUpdatePolicy::UpdateHTTPBody);
487 }
488
489 - (NSMutableURLRequest *)request
490 {
491     auto* frameLoader = toPrivate(_private)->loader->frameLoader();
492     if (!frameLoader || !frameLoader->frameHasLoaded())
493         return nil;
494
495     // FIXME: this cast is dubious
496     return (NSMutableURLRequest *)toPrivate(_private)->loader->request().nsURLRequest(WebCore::HTTPBodyUpdatePolicy::UpdateHTTPBody);
497 }
498
499 - (NSURLResponse *)response
500 {
501     return toPrivate(_private)->loader->response().nsURLResponse();
502 }
503
504 - (NSString *)textEncodingName
505 {
506     NSString *textEncodingName = toPrivate(_private)->loader->overrideEncoding();
507     if (!textEncodingName)
508         textEncodingName = [[self response] textEncodingName];
509     return textEncodingName;
510 }
511
512 - (BOOL)isLoading
513 {
514     return toPrivate(_private)->loader->isLoadingInAPISense();
515 }
516
517 // Returns nil or the page title.
518 - (NSString *)pageTitle
519 {
520     return [[self representation] title];
521 }
522
523 - (NSURL *)unreachableURL
524 {
525     const URL& unreachableURL = toPrivate(_private)->loader->unreachableURL();
526     if (unreachableURL.isEmpty())
527         return nil;
528     return unreachableURL;
529 }
530
531 - (WebArchive *)webArchive
532 {
533     // it makes no sense to grab a WebArchive from an uncommitted document.
534     if (!toPrivate(_private)->loader->isCommitted())
535         return nil;
536         
537     return [[[WebArchive alloc] _initWithCoreLegacyWebArchive:WebCore::LegacyWebArchive::create(*core([self webFrame]))] autorelease];
538 }
539
540 - (WebResource *)mainResource
541 {
542     auto coreResource = toPrivate(_private)->loader->mainResource();
543     if (!coreResource)
544         return nil;
545     return [[[WebResource alloc] _initWithCoreResource:coreResource.releaseNonNull()] autorelease];
546 }
547
548 - (NSArray *)subresources
549 {
550     auto coreSubresources = toPrivate(_private)->loader->subresources();
551     auto subresources = adoptNS([[NSMutableArray alloc] initWithCapacity:coreSubresources.size()]);
552     for (auto& coreSubresource : coreSubresources) {
553         if (auto resource = adoptNS([[WebResource alloc] _initWithCoreResource:coreSubresource.copyRef()]))
554             [subresources addObject:resource.get()];
555     }
556     return subresources.autorelease();
557 }
558
559 - (WebResource *)subresourceForURL:(NSURL *)URL
560 {
561     auto subresource = toPrivate(_private)->loader->subresource(URL);
562     return subresource ? [[[WebResource alloc] _initWithCoreResource:subresource.releaseNonNull()] autorelease] : nil;
563 }
564
565 - (void)addSubresource:(WebResource *)subresource
566 {    
567     toPrivate(_private)->loader->addArchiveResource([subresource _coreResource]);
568 }
569
570 @end