2 * Copyright (C) 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import <WebKit/WKFoundation.h>
30 #if PLATFORM(MAC) // No downloading on iOS
32 #import "PlatformUtilities.h"
34 #import "TestProtocol.h"
35 #import "TestWKWebView.h"
36 #import <WebCore/FileSystem.h>
37 #import <WebKit/_WKDownload.h>
38 #import <WebKit/_WKDownloadDelegate.h>
39 #import <WebKit/WKNavigationDelegatePrivate.h>
40 #import <WebKit/WKProcessPoolPrivate.h>
41 #import <WebKit/WKUIDelegatePrivate.h>
42 #import <WebKit/WKWebView.h>
43 #import <WebKit/WKWebViewConfiguration.h>
44 #import <wtf/RetainPtr.h>
45 #import <wtf/mac/AppKitCompatibilityDeclarations.h>
46 #import <wtf/text/WTFString.h>
49 static unsigned redirectCount = 0;
50 static bool hasReceivedResponse;
51 static NSURL *sourceURL = [[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
52 static WKWebView* expectedOriginatingWebView;
53 static bool expectedUserInitiatedState = false;
55 @interface DownloadDelegate : NSObject <_WKDownloadDelegate>
58 @implementation DownloadDelegate {
59 RetainPtr<_WKDownload> _download;
60 String _destinationPath;
61 long long _expectedContentLength;
62 uint64_t _receivedContentLength;
65 - (void)_downloadDidStart:(_WKDownload *)download
67 EXPECT_NULL(_download);
68 EXPECT_NOT_NULL(download);
69 EXPECT_TRUE([[[[download request] URL] path] isEqualToString:[sourceURL path]]);
70 EXPECT_EQ(expectedUserInitiatedState, download.wasUserInitiated);
74 - (void)_download:(_WKDownload *)download didReceiveResponse:(NSURLResponse *)response
76 hasReceivedResponse = true;
77 EXPECT_EQ(_download, download);
78 EXPECT_TRUE(_expectedContentLength == 0);
79 EXPECT_TRUE(_receivedContentLength == 0);
80 EXPECT_TRUE([[[response URL] path] isEqualToString:[sourceURL path]]);
81 _expectedContentLength = [response expectedContentLength];
84 - (void)_download:(_WKDownload *)download didReceiveData:(uint64_t)length
86 EXPECT_EQ(_download, download);
87 _receivedContentLength += length;
90 - (NSString *)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename allowOverwrite:(BOOL *)allowOverwrite
92 EXPECT_TRUE(hasReceivedResponse);
93 EXPECT_EQ(_download, download);
95 WebCore::PlatformFileHandle fileHandle;
96 _destinationPath = WebCore::openTemporaryFile("TestWebKitAPI", fileHandle);
97 EXPECT_TRUE(fileHandle != WebCore::invalidPlatformFileHandle);
98 WebCore::closeFile(fileHandle);
100 *allowOverwrite = YES;
101 return _destinationPath;
104 - (void)_downloadDidFinish:(_WKDownload *)download
106 EXPECT_EQ(_download, download);
107 EXPECT_EQ(expectedUserInitiatedState, download.wasUserInitiated);
108 EXPECT_TRUE(_expectedContentLength == NSURLResponseUnknownLength || static_cast<uint64_t>(_expectedContentLength) == _receivedContentLength);
109 EXPECT_TRUE([[NSFileManager defaultManager] contentsEqualAtPath:_destinationPath andPath:[sourceURL path]]);
110 WebCore::deleteFile(_destinationPath);
116 TEST(_WKDownload, DownloadDelegate)
118 RetainPtr<WKProcessPool> processPool = adoptNS([[WKProcessPool alloc] init]);
119 DownloadDelegate *downloadDelegate = [[DownloadDelegate alloc] init];
120 [processPool _setDownloadDelegate:downloadDelegate];
123 EXPECT_EQ(downloadDelegate, [processPool _downloadDelegate]);
126 [downloadDelegate release];
127 EXPECT_NULL([processPool _downloadDelegate]);
130 static void runTest(id <WKNavigationDelegate> navigationDelegate, id <_WKDownloadDelegate> downloadDelegate, NSURL *url)
132 RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
133 [webView setNavigationDelegate:navigationDelegate];
134 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate];
137 hasReceivedResponse = false;
138 expectedUserInitiatedState = false;
139 [webView loadRequest:[NSURLRequest requestWithURL:url]];
140 TestWebKitAPI::Util::run(&isDone);
143 @interface DownloadNavigationDelegate : NSObject <WKNavigationDelegate>
146 @implementation DownloadNavigationDelegate
147 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
149 decisionHandler(_WKNavigationActionPolicyDownload);
153 TEST(_WKDownload, DownloadRequest)
155 runTest(adoptNS([[DownloadNavigationDelegate alloc] init]).get(), adoptNS([[DownloadDelegate alloc] init]).get(), sourceURL);
158 @interface ConvertResponseToDownloadNavigationDelegate : NSObject <WKNavigationDelegate>
161 @implementation ConvertResponseToDownloadNavigationDelegate
162 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
164 decisionHandler(_WKNavigationResponsePolicyBecomeDownload);
168 TEST(_WKDownload, ConvertResponseToDownload)
170 runTest(adoptNS([[ConvertResponseToDownloadNavigationDelegate alloc] init]).get(), adoptNS([[DownloadDelegate alloc] init]).get(), sourceURL);
173 @interface FailingDownloadDelegate : NSObject <_WKDownloadDelegate>
176 @implementation FailingDownloadDelegate
178 - (void)_downloadDidFinish:(_WKDownload *)download
184 - (void)_download:(_WKDownload *)download didFailWithError:(NSError *)error
189 - (void)_downloadDidCancel:(_WKDownload *)download
197 TEST(_WKDownload, DownloadMissingResource)
199 runTest(adoptNS([[DownloadNavigationDelegate alloc] init]).get(), adoptNS([[FailingDownloadDelegate alloc] init]).get(), [NSURL URLWithString:@"non-existant-scheme://"]);
202 @interface CancelledDownloadDelegate : NSObject <_WKDownloadDelegate>
205 @implementation CancelledDownloadDelegate
207 - (void)_downloadDidStart:(_WKDownload *)download
212 - (void)_downloadDidFinish:(_WKDownload *)download
218 - (void)_download:(_WKDownload *)download didFailWithError:(NSError *)error
224 - (void)_downloadDidCancel:(_WKDownload *)download
231 TEST(_WKDownload, CancelDownload)
233 runTest(adoptNS([[DownloadNavigationDelegate alloc] init]).get(), adoptNS([[CancelledDownloadDelegate alloc] init]).get(), sourceURL);
236 @interface OriginatingWebViewDownloadDelegate : NSObject <_WKDownloadDelegate>
237 - (instancetype)initWithWebView:(WKWebView *)webView;
240 @implementation OriginatingWebViewDownloadDelegate {
241 RetainPtr<WKWebView> _webView;
244 - (instancetype)initWithWebView:(WKWebView *)webView
246 if (!(self = [super init]))
253 - (void)_downloadDidStart:(_WKDownload *)download
256 EXPECT_EQ([download originatingWebView], _webView);
260 EXPECT_NULL([download originatingWebView]);
266 TEST(_WKDownload, OriginatingWebView)
268 RetainPtr<DownloadNavigationDelegate> navigationDelegate = adoptNS([[DownloadNavigationDelegate alloc] init]);
269 RetainPtr<OriginatingWebViewDownloadDelegate> downloadDelegate;
271 RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
272 [webView setNavigationDelegate:navigationDelegate.get()];
273 downloadDelegate = adoptNS([[OriginatingWebViewDownloadDelegate alloc] initWithWebView:webView.get()]);
274 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
275 [webView loadRequest:[NSURLRequest requestWithURL:sourceURL]];
279 TestWebKitAPI::Util::run(&isDone);
282 @interface DownloadRequestOriginalURLDelegate : NSObject <_WKDownloadDelegate>
283 - (instancetype)initWithExpectedOriginalURL:(NSURL *)expectOriginalURL;
286 @implementation DownloadRequestOriginalURLDelegate {
287 NSURL *_expectedOriginalURL;
290 - (instancetype)initWithExpectedOriginalURL:(NSURL *)expectedOriginalURL
292 if (!(self = [super init]))
295 _expectedOriginalURL = expectedOriginalURL;
299 - (void)_downloadDidStart:(_WKDownload *)download
301 if ([_expectedOriginalURL isEqual:sourceURL])
302 EXPECT_TRUE(!download.request.mainDocumentURL);
304 EXPECT_TRUE([_expectedOriginalURL isEqual:download.request.mainDocumentURL]);
310 @interface DownloadRequestOriginalURLNavigationDelegate : NSObject <WKNavigationDelegate>
313 @implementation DownloadRequestOriginalURLNavigationDelegate
314 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
316 if ([navigationAction.request.URL isEqual:sourceURL])
317 decisionHandler(_WKNavigationActionPolicyDownload);
319 decisionHandler(WKNavigationActionPolicyAllow);
323 TEST(_WKDownload, DownloadRequestOriginalURL)
325 NSURL *originalURL = [[NSBundle mainBundle] URLForResource:@"DownloadRequestOriginalURL" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
326 runTest(adoptNS([[DownloadRequestOriginalURLNavigationDelegate alloc] init]).get(), adoptNS([[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:originalURL]).get(), originalURL);
329 TEST(_WKDownload, DownloadRequestOriginalURLFrame)
331 NSURL *originalURL = [[NSBundle mainBundle] URLForResource:@"DownloadRequestOriginalURL2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
332 runTest(adoptNS([[DownloadRequestOriginalURLNavigationDelegate alloc] init]).get(), adoptNS([[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:originalURL]).get(), originalURL);
335 TEST(_WKDownload, DownloadRequestOriginalURLDirectDownload)
337 runTest(adoptNS([[DownloadRequestOriginalURLNavigationDelegate alloc] init]).get(), adoptNS([[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:sourceURL]).get(), sourceURL);
340 TEST(_WKDownload, DownloadRequestOriginalURLDirectDownloadWithLoadedContent)
342 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
343 [webView setNavigationDelegate:[[DownloadRequestOriginalURLNavigationDelegate alloc] init]];
344 [[[webView configuration] processPool] _setDownloadDelegate:[[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:sourceURL]];
346 expectedUserInitiatedState = false;
347 NSURL *contentURL = [[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
348 // Here is to test if the original URL can be set correctly when the current document
349 // is completely unrelated to the download.
350 [webView loadRequest:[NSURLRequest requestWithURL:contentURL]];
351 [webView loadRequest:[NSURLRequest requestWithURL:sourceURL]];
353 TestWebKitAPI::Util::run(&isDone);
356 @interface BlobDownloadDelegate : NSObject <_WKDownloadDelegate>
359 @implementation BlobDownloadDelegate {
360 RetainPtr<_WKDownload> _download;
361 String _destinationPath;
362 long long _expectedContentLength;
363 uint64_t _receivedContentLength;
366 - (void)_downloadDidStart:(_WKDownload *)download
368 EXPECT_NULL(_download);
369 EXPECT_NOT_NULL(download);
370 EXPECT_TRUE([[[[download request] URL] scheme] isEqualToString:@"blob"]);
371 EXPECT_EQ(expectedUserInitiatedState, download.wasUserInitiated);
372 _download = download;
375 - (void)_download:(_WKDownload *)download didReceiveResponse:(NSURLResponse *)response
377 hasReceivedResponse = true;
378 EXPECT_EQ(_download, download);
379 EXPECT_EQ(_expectedContentLength, 0U);
380 EXPECT_EQ(_receivedContentLength, 0U);
381 EXPECT_TRUE([[[response URL] scheme] isEqualToString:@"blob"]);
382 _expectedContentLength = [response expectedContentLength];
385 - (void)_download:(_WKDownload *)download didReceiveData:(uint64_t)length
387 EXPECT_EQ(_download, download);
388 _receivedContentLength += length;
391 - (NSString *)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename allowOverwrite:(BOOL *)allowOverwrite
393 EXPECT_TRUE(hasReceivedResponse);
394 EXPECT_EQ(_download, download);
396 WebCore::PlatformFileHandle fileHandle;
397 _destinationPath = WebCore::openTemporaryFile("TestWebKitAPI", fileHandle);
398 EXPECT_TRUE(fileHandle != WebCore::invalidPlatformFileHandle);
399 WebCore::closeFile(fileHandle);
401 *allowOverwrite = YES;
402 return _destinationPath;
405 - (void)_downloadDidFinish:(_WKDownload *)download
407 EXPECT_EQ(_download, download);
408 EXPECT_EQ(expectedUserInitiatedState, download.wasUserInitiated);
409 EXPECT_TRUE(_expectedContentLength == NSURLResponseUnknownLength || static_cast<uint64_t>(_expectedContentLength) == _receivedContentLength);
410 NSString* expectedContent = @"{\"x\":42,\"s\":\"hello, world\"}";
411 NSData* expectedData = [expectedContent dataUsingEncoding:NSUTF8StringEncoding];
412 EXPECT_TRUE([[[NSFileManager defaultManager] contentsAtPath:_destinationPath] isEqualToData:expectedData]);
413 WebCore::deleteFile(_destinationPath);
419 @interface DownloadBlobURLNavigationDelegate : NSObject <WKNavigationDelegate>
422 @implementation DownloadBlobURLNavigationDelegate
423 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
425 if ([navigationAction.request.URL.scheme isEqualToString:@"blob"])
426 decisionHandler(_WKNavigationActionPolicyDownload);
428 decisionHandler(WKNavigationActionPolicyAllow);
432 TEST(_WKDownload, DownloadRequestBlobURL)
434 NSURL *originalURL = [[NSBundle mainBundle] URLForResource:@"DownloadRequestBlobURL" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
435 runTest(adoptNS([[DownloadBlobURLNavigationDelegate alloc] init]).get(), adoptNS([[BlobDownloadDelegate alloc] init]).get(), originalURL);
438 @interface UIDownloadAsFileTestDelegate : NSObject <WKUIDelegatePrivate>
441 @implementation UIDownloadAsFileTestDelegate
443 - (NSMenu *)_webView:(WKWebView *)webView contextMenu:(NSMenu *)menu forElement:(_WKContextMenuElementInfo *)element
445 static const long downloadLinkedFileTag = 2;
446 auto index = [menu indexOfItemWithTag:downloadLinkedFileTag];
447 [menu performActionForItemAtIndex:index];
453 @interface RedirectedDownloadDelegate : NSObject <_WKDownloadDelegate>
456 @implementation RedirectedDownloadDelegate {
457 String _destinationPath;
460 - (void)_downloadDidStart:(_WKDownload *)download
462 EXPECT_NOT_NULL(download);
463 EXPECT_EQ(expectedOriginatingWebView, download.originatingWebView);
464 EXPECT_EQ(expectedUserInitiatedState, download.wasUserInitiated);
467 - (NSString *)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename allowOverwrite:(BOOL *)allowOverwrite
469 WebCore::PlatformFileHandle fileHandle;
470 _destinationPath = WebCore::openTemporaryFile("TestWebKitAPI", fileHandle);
471 EXPECT_TRUE(fileHandle != WebCore::invalidPlatformFileHandle);
472 WebCore::closeFile(fileHandle);
473 *allowOverwrite = YES;
474 return _destinationPath;
477 - (void)_download:(_WKDownload *)download didReceiveServerRedirectToURL:(NSURL *)url
480 EXPECT_STREQ("http://redirect/?pass", [url.absoluteString UTF8String]);
482 EXPECT_STREQ("http://pass/", [url.absoluteString UTF8String]);
483 ++redirectCount = true;
486 - (void)_downloadDidFinish:(_WKDownload *)download
488 EXPECT_EQ(expectedUserInitiatedState, download.wasUserInitiated);
490 NSArray<NSURL *> *redirectChain = download.redirectChain;
491 EXPECT_EQ(3U, redirectChain.count);
492 if (redirectChain.count > 0)
493 EXPECT_STREQ("http://redirect/?redirect/?pass", [redirectChain[0].absoluteString UTF8String]);
494 if (redirectChain.count > 1)
495 EXPECT_STREQ("http://redirect/?pass", [redirectChain[1].absoluteString UTF8String]);
496 if (redirectChain.count > 2)
497 EXPECT_STREQ("http://pass/", [redirectChain[2].absoluteString UTF8String]);
499 WebCore::deleteFile(_destinationPath);
505 TEST(_WKDownload, RedirectedDownload)
507 [TestProtocol registerWithScheme:@"http"];
512 auto delegate = adoptNS([[UIDownloadAsFileTestDelegate alloc] init]);
513 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
514 auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration.get()]);
515 [webView setUIDelegate:delegate.get()];
516 auto downloadDelegate = adoptNS([[RedirectedDownloadDelegate alloc] init]);
517 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
519 auto window = adoptNS([[NSWindow alloc] initWithContentRect:[webView frame] styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:YES]);
520 [[window contentView] addSubview:webView.get()];
522 // Do 2 loads in the same view to make sure the redirect chain is properly cleared between loads.
523 [webView synchronouslyLoadHTMLString:@"<div>First load</div>"];
524 [webView synchronouslyLoadHTMLString:@"<a style='display: block; height: 100%; width: 100%' href='http://redirect/?redirect/?pass'>test</a>"];
526 expectedOriginatingWebView = webView.get();
527 expectedUserInitiatedState = true;
528 NSPoint clickPoint = NSMakePoint(100, 100);
529 [[webView hitTest:clickPoint] mouseDown:[NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:clickPoint modifierFlags:0 timestamp:0 windowNumber:[window windowNumber] context:nil eventNumber:0 clickCount:1 pressure:1]];
530 [[webView hitTest:clickPoint] mouseUp:[NSEvent mouseEventWithType:NSEventTypeRightMouseUp location:clickPoint modifierFlags:0 timestamp:0 windowNumber:[window windowNumber] context:nil eventNumber:0 clickCount:1 pressure:1]];
533 TestWebKitAPI::Util::run(&isDone);
534 EXPECT_EQ(1U, redirectCount);
536 [TestProtocol unregister];
539 TEST(_WKDownload, RedirectedLoadConvertedToDownload)
541 [TestProtocol registerWithScheme:@"http"];
543 auto navigationDelegate = adoptNS([[ConvertResponseToDownloadNavigationDelegate alloc] init]);
544 auto downloadDelegate = adoptNS([[RedirectedDownloadDelegate alloc] init]);
546 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
547 [webView setNavigationDelegate:navigationDelegate.get()];
548 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
550 expectedOriginatingWebView = webView.get();
551 expectedUserInitiatedState = false;
554 hasReceivedResponse = false;
555 [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://redirect/?redirect/?pass"]]];
556 TestWebKitAPI::Util::run(&isDone);
557 EXPECT_EQ(0U, redirectCount);
559 [TestProtocol unregister];
562 TEST(_WKDownload, RedirectedSubframeLoadConvertedToDownload)
564 [TestProtocol registerWithScheme:@"http"];
566 auto navigationDelegate = adoptNS([[ConvertResponseToDownloadNavigationDelegate alloc] init]);
567 auto downloadDelegate = adoptNS([[RedirectedDownloadDelegate alloc] init]);
569 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
570 [webView setNavigationDelegate:navigationDelegate.get()];
571 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
573 expectedOriginatingWebView = webView.get();
574 expectedUserInitiatedState = false;
577 hasReceivedResponse = false;
578 [webView loadHTMLString:@"<body><iframe src='http://redirect/?redirect/?pass'></iframe></body>" baseURL:nil];
579 TestWebKitAPI::Util::run(&isDone);
580 EXPECT_EQ(0U, redirectCount);
582 [TestProtocol unregister];