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;
54 @interface DownloadDelegate : NSObject <_WKDownloadDelegate>
57 @implementation DownloadDelegate {
58 RetainPtr<_WKDownload> _download;
59 String _destinationPath;
60 long long _expectedContentLength;
61 uint64_t _receivedContentLength;
64 - (void)_downloadDidStart:(_WKDownload *)download
66 EXPECT_NULL(_download);
67 EXPECT_NOT_NULL(download);
68 EXPECT_TRUE([[[[download request] URL] path] isEqualToString:[sourceURL path]]);
72 - (void)_download:(_WKDownload *)download didReceiveResponse:(NSURLResponse *)response
74 hasReceivedResponse = true;
75 EXPECT_EQ(_download, download);
76 EXPECT_TRUE(_expectedContentLength == 0);
77 EXPECT_TRUE(_receivedContentLength == 0);
78 EXPECT_TRUE([[[response URL] path] isEqualToString:[sourceURL path]]);
79 _expectedContentLength = [response expectedContentLength];
82 - (void)_download:(_WKDownload *)download didReceiveData:(uint64_t)length
84 EXPECT_EQ(_download, download);
85 _receivedContentLength += length;
88 - (NSString *)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename allowOverwrite:(BOOL *)allowOverwrite
90 EXPECT_TRUE(hasReceivedResponse);
91 EXPECT_EQ(_download, download);
93 WebCore::PlatformFileHandle fileHandle;
94 _destinationPath = WebCore::openTemporaryFile("TestWebKitAPI", fileHandle);
95 EXPECT_TRUE(fileHandle != WebCore::invalidPlatformFileHandle);
96 WebCore::closeFile(fileHandle);
98 *allowOverwrite = YES;
99 return _destinationPath;
102 - (void)_downloadDidFinish:(_WKDownload *)download
104 EXPECT_EQ(_download, download);
105 EXPECT_TRUE(_expectedContentLength == NSURLResponseUnknownLength || static_cast<uint64_t>(_expectedContentLength) == _receivedContentLength);
106 EXPECT_TRUE([[NSFileManager defaultManager] contentsEqualAtPath:_destinationPath andPath:[sourceURL path]]);
107 WebCore::deleteFile(_destinationPath);
113 TEST(_WKDownload, DownloadDelegate)
115 RetainPtr<WKProcessPool> processPool = adoptNS([[WKProcessPool alloc] init]);
116 DownloadDelegate *downloadDelegate = [[DownloadDelegate alloc] init];
117 [processPool _setDownloadDelegate:downloadDelegate];
120 EXPECT_EQ(downloadDelegate, [processPool _downloadDelegate]);
123 [downloadDelegate release];
124 EXPECT_NULL([processPool _downloadDelegate]);
127 static void runTest(id <WKNavigationDelegate> navigationDelegate, id <_WKDownloadDelegate> downloadDelegate, NSURL *url)
129 RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
130 [webView setNavigationDelegate:navigationDelegate];
131 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate];
134 hasReceivedResponse = false;
135 [webView loadRequest:[NSURLRequest requestWithURL:url]];
136 TestWebKitAPI::Util::run(&isDone);
139 @interface DownloadNavigationDelegate : NSObject <WKNavigationDelegate>
142 @implementation DownloadNavigationDelegate
143 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
145 decisionHandler(_WKNavigationActionPolicyDownload);
149 TEST(_WKDownload, DownloadRequest)
151 runTest(adoptNS([[DownloadNavigationDelegate alloc] init]).get(), adoptNS([[DownloadDelegate alloc] init]).get(), sourceURL);
154 @interface ConvertResponseToDownloadNavigationDelegate : NSObject <WKNavigationDelegate>
157 @implementation ConvertResponseToDownloadNavigationDelegate
158 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
160 decisionHandler(_WKNavigationResponsePolicyBecomeDownload);
164 TEST(_WKDownload, ConvertResponseToDownload)
166 runTest(adoptNS([[ConvertResponseToDownloadNavigationDelegate alloc] init]).get(), adoptNS([[DownloadDelegate alloc] init]).get(), sourceURL);
169 @interface FailingDownloadDelegate : NSObject <_WKDownloadDelegate>
172 @implementation FailingDownloadDelegate
174 - (void)_downloadDidFinish:(_WKDownload *)download
180 - (void)_download:(_WKDownload *)download didFailWithError:(NSError *)error
185 - (void)_downloadDidCancel:(_WKDownload *)download
193 TEST(_WKDownload, DownloadMissingResource)
195 runTest(adoptNS([[DownloadNavigationDelegate alloc] init]).get(), adoptNS([[FailingDownloadDelegate alloc] init]).get(), [NSURL URLWithString:@"non-existant-scheme://"]);
198 @interface CancelledDownloadDelegate : NSObject <_WKDownloadDelegate>
201 @implementation CancelledDownloadDelegate
203 - (void)_downloadDidStart:(_WKDownload *)download
208 - (void)_downloadDidFinish:(_WKDownload *)download
214 - (void)_download:(_WKDownload *)download didFailWithError:(NSError *)error
220 - (void)_downloadDidCancel:(_WKDownload *)download
227 TEST(_WKDownload, CancelDownload)
229 runTest(adoptNS([[DownloadNavigationDelegate alloc] init]).get(), adoptNS([[CancelledDownloadDelegate alloc] init]).get(), sourceURL);
232 @interface OriginatingWebViewDownloadDelegate : NSObject <_WKDownloadDelegate>
233 - (instancetype)initWithWebView:(WKWebView *)webView;
236 @implementation OriginatingWebViewDownloadDelegate {
237 RetainPtr<WKWebView> _webView;
240 - (instancetype)initWithWebView:(WKWebView *)webView
242 if (!(self = [super init]))
249 - (void)_downloadDidStart:(_WKDownload *)download
252 EXPECT_EQ([download originatingWebView], _webView);
256 EXPECT_NULL([download originatingWebView]);
262 TEST(_WKDownload, OriginatingWebView)
264 RetainPtr<DownloadNavigationDelegate> navigationDelegate = adoptNS([[DownloadNavigationDelegate alloc] init]);
265 RetainPtr<OriginatingWebViewDownloadDelegate> downloadDelegate;
267 RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
268 [webView setNavigationDelegate:navigationDelegate.get()];
269 downloadDelegate = adoptNS([[OriginatingWebViewDownloadDelegate alloc] initWithWebView:webView.get()]);
270 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
271 [webView loadRequest:[NSURLRequest requestWithURL:sourceURL]];
275 TestWebKitAPI::Util::run(&isDone);
278 @interface DownloadRequestOriginalURLDelegate : NSObject <_WKDownloadDelegate>
279 - (instancetype)initWithExpectedOriginalURL:(NSURL *)expectOriginalURL;
282 @implementation DownloadRequestOriginalURLDelegate {
283 NSURL *_expectedOriginalURL;
286 - (instancetype)initWithExpectedOriginalURL:(NSURL *)expectedOriginalURL
288 if (!(self = [super init]))
291 _expectedOriginalURL = expectedOriginalURL;
295 - (void)_downloadDidStart:(_WKDownload *)download
297 if ([_expectedOriginalURL isEqual:sourceURL])
298 EXPECT_TRUE(!download.request.mainDocumentURL);
300 EXPECT_TRUE([_expectedOriginalURL isEqual:download.request.mainDocumentURL]);
306 @interface DownloadRequestOriginalURLNavigationDelegate : NSObject <WKNavigationDelegate>
309 @implementation DownloadRequestOriginalURLNavigationDelegate
310 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
312 if ([navigationAction.request.URL isEqual:sourceURL])
313 decisionHandler(_WKNavigationActionPolicyDownload);
315 decisionHandler(WKNavigationActionPolicyAllow);
319 TEST(_WKDownload, DownloadRequestOriginalURL)
321 NSURL *originalURL = [[NSBundle mainBundle] URLForResource:@"DownloadRequestOriginalURL" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
322 runTest(adoptNS([[DownloadRequestOriginalURLNavigationDelegate alloc] init]).get(), adoptNS([[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:originalURL]).get(), originalURL);
325 TEST(_WKDownload, DownloadRequestOriginalURLFrame)
327 NSURL *originalURL = [[NSBundle mainBundle] URLForResource:@"DownloadRequestOriginalURL2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
328 runTest(adoptNS([[DownloadRequestOriginalURLNavigationDelegate alloc] init]).get(), adoptNS([[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:originalURL]).get(), originalURL);
331 TEST(_WKDownload, DownloadRequestOriginalURLDirectDownload)
333 runTest(adoptNS([[DownloadRequestOriginalURLNavigationDelegate alloc] init]).get(), adoptNS([[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:sourceURL]).get(), sourceURL);
336 TEST(_WKDownload, DownloadRequestOriginalURLDirectDownloadWithLoadedContent)
338 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
339 [webView setNavigationDelegate:[[DownloadRequestOriginalURLNavigationDelegate alloc] init]];
340 [[[webView configuration] processPool] _setDownloadDelegate:[[DownloadRequestOriginalURLDelegate alloc] initWithExpectedOriginalURL:sourceURL]];
342 NSURL *contentURL = [[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
343 // Here is to test if the original URL can be set correctly when the current document
344 // is completely unrelated to the download.
345 [webView loadRequest:[NSURLRequest requestWithURL:contentURL]];
346 [webView loadRequest:[NSURLRequest requestWithURL:sourceURL]];
348 TestWebKitAPI::Util::run(&isDone);
351 @interface BlobDownloadDelegate : NSObject <_WKDownloadDelegate>
354 @implementation BlobDownloadDelegate {
355 RetainPtr<_WKDownload> _download;
356 String _destinationPath;
357 long long _expectedContentLength;
358 uint64_t _receivedContentLength;
361 - (void)_downloadDidStart:(_WKDownload *)download
363 EXPECT_NULL(_download);
364 EXPECT_NOT_NULL(download);
365 EXPECT_TRUE([[[[download request] URL] scheme] isEqualToString:@"blob"]);
366 _download = download;
369 - (void)_download:(_WKDownload *)download didReceiveResponse:(NSURLResponse *)response
371 hasReceivedResponse = true;
372 EXPECT_EQ(_download, download);
373 EXPECT_EQ(_expectedContentLength, 0U);
374 EXPECT_EQ(_receivedContentLength, 0U);
375 EXPECT_TRUE([[[response URL] scheme] isEqualToString:@"blob"]);
376 _expectedContentLength = [response expectedContentLength];
379 - (void)_download:(_WKDownload *)download didReceiveData:(uint64_t)length
381 EXPECT_EQ(_download, download);
382 _receivedContentLength += length;
385 - (NSString *)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename allowOverwrite:(BOOL *)allowOverwrite
387 EXPECT_TRUE(hasReceivedResponse);
388 EXPECT_EQ(_download, download);
390 WebCore::PlatformFileHandle fileHandle;
391 _destinationPath = WebCore::openTemporaryFile("TestWebKitAPI", fileHandle);
392 EXPECT_TRUE(fileHandle != WebCore::invalidPlatformFileHandle);
393 WebCore::closeFile(fileHandle);
395 *allowOverwrite = YES;
396 return _destinationPath;
399 - (void)_downloadDidFinish:(_WKDownload *)download
401 EXPECT_EQ(_download, download);
402 EXPECT_TRUE(_expectedContentLength == NSURLResponseUnknownLength || static_cast<uint64_t>(_expectedContentLength) == _receivedContentLength);
403 NSString* expectedContent = @"{\"x\":42,\"s\":\"hello, world\"}";
404 NSData* expectedData = [expectedContent dataUsingEncoding:NSUTF8StringEncoding];
405 EXPECT_TRUE([[[NSFileManager defaultManager] contentsAtPath:_destinationPath] isEqualToData:expectedData]);
406 WebCore::deleteFile(_destinationPath);
412 @interface DownloadBlobURLNavigationDelegate : NSObject <WKNavigationDelegate>
415 @implementation DownloadBlobURLNavigationDelegate
416 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
418 if ([navigationAction.request.URL.scheme isEqualToString:@"blob"])
419 decisionHandler(_WKNavigationActionPolicyDownload);
421 decisionHandler(WKNavigationActionPolicyAllow);
425 TEST(_WKDownload, DownloadRequestBlobURL)
427 NSURL *originalURL = [[NSBundle mainBundle] URLForResource:@"DownloadRequestBlobURL" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
428 runTest(adoptNS([[DownloadBlobURLNavigationDelegate alloc] init]).get(), adoptNS([[BlobDownloadDelegate alloc] init]).get(), originalURL);
431 @interface UIDownloadAsFileTestDelegate : NSObject <WKUIDelegatePrivate>
434 @implementation UIDownloadAsFileTestDelegate
436 - (NSMenu *)_webView:(WKWebView *)webView contextMenu:(NSMenu *)menu forElement:(_WKContextMenuElementInfo *)element
438 static const long downloadLinkedFileTag = 2;
439 auto index = [menu indexOfItemWithTag:downloadLinkedFileTag];
440 [menu performActionForItemAtIndex:index];
446 @interface RedirectedDownloadDelegate : NSObject <_WKDownloadDelegate>
449 @implementation RedirectedDownloadDelegate {
450 String _destinationPath;
453 - (void)_downloadDidStart:(_WKDownload *)download
455 EXPECT_NOT_NULL(download);
456 EXPECT_EQ(expectedOriginatingWebView, download.originatingWebView);
459 - (NSString *)_download:(_WKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename allowOverwrite:(BOOL *)allowOverwrite
461 WebCore::PlatformFileHandle fileHandle;
462 _destinationPath = WebCore::openTemporaryFile("TestWebKitAPI", fileHandle);
463 EXPECT_TRUE(fileHandle != WebCore::invalidPlatformFileHandle);
464 WebCore::closeFile(fileHandle);
465 *allowOverwrite = YES;
466 return _destinationPath;
469 - (void)_download:(_WKDownload *)download didReceiveServerRedirectToURL:(NSURL *)url
472 EXPECT_STREQ("http://redirect/?pass", [url.absoluteString UTF8String]);
474 EXPECT_STREQ("http://pass/", [url.absoluteString UTF8String]);
475 ++redirectCount = true;
478 - (void)_downloadDidFinish:(_WKDownload *)download
480 NSArray<NSURL *> *redirectChain = download.redirectChain;
481 EXPECT_EQ(3U, redirectChain.count);
482 if (redirectChain.count > 0)
483 EXPECT_STREQ("http://redirect/?redirect/?pass", [redirectChain[0].absoluteString UTF8String]);
484 if (redirectChain.count > 1)
485 EXPECT_STREQ("http://redirect/?pass", [redirectChain[1].absoluteString UTF8String]);
486 if (redirectChain.count > 2)
487 EXPECT_STREQ("http://pass/", [redirectChain[2].absoluteString UTF8String]);
489 WebCore::deleteFile(_destinationPath);
495 TEST(_WKDownload, RedirectedDownload)
497 [TestProtocol registerWithScheme:@"http"];
502 auto delegate = adoptNS([[UIDownloadAsFileTestDelegate alloc] init]);
503 auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
504 auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration.get()]);
505 [webView setUIDelegate:delegate.get()];
506 auto downloadDelegate = adoptNS([[RedirectedDownloadDelegate alloc] init]);
507 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
509 auto window = adoptNS([[NSWindow alloc] initWithContentRect:[webView frame] styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:YES]);
510 [[window contentView] addSubview:webView.get()];
512 // Do 2 loads in the same view to make sure the redirect chain is properly cleared between loads.
513 [webView synchronouslyLoadHTMLString:@"<div>First load</div>"];
514 [webView synchronouslyLoadHTMLString:@"<a style='display: block; height: 100%; width: 100%' href='http://redirect/?redirect/?pass'>test</a>"];
516 expectedOriginatingWebView = webView.get();
517 NSPoint clickPoint = NSMakePoint(100, 100);
518 [[webView hitTest:clickPoint] mouseDown:[NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:clickPoint modifierFlags:0 timestamp:0 windowNumber:[window windowNumber] context:nil eventNumber:0 clickCount:1 pressure:1]];
519 [[webView hitTest:clickPoint] mouseUp:[NSEvent mouseEventWithType:NSEventTypeRightMouseUp location:clickPoint modifierFlags:0 timestamp:0 windowNumber:[window windowNumber] context:nil eventNumber:0 clickCount:1 pressure:1]];
522 TestWebKitAPI::Util::run(&isDone);
523 EXPECT_EQ(1U, redirectCount);
525 [TestProtocol unregister];
528 // FIXME: Re-enable once it passes on El Capitan (https://bugs.webkit.org/show_bug.cgi?id=177321).
529 TEST(_WKDownload, DISABLED_RedirectedLoadConvertedToDownload)
531 [TestProtocol registerWithScheme:@"http"];
533 auto navigationDelegate = adoptNS([[ConvertResponseToDownloadNavigationDelegate alloc] init]);
534 auto downloadDelegate = adoptNS([[RedirectedDownloadDelegate alloc] init]);
536 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
537 [webView setNavigationDelegate:navigationDelegate.get()];
538 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
540 expectedOriginatingWebView = webView.get();
543 hasReceivedResponse = false;
544 [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://redirect/?redirect/?pass"]]];
545 TestWebKitAPI::Util::run(&isDone);
546 EXPECT_EQ(0U, redirectCount);
548 [TestProtocol unregister];
551 // FIXME: Re-enable once it passes on El Capitan (https://bugs.webkit.org/show_bug.cgi?id=177321).
552 TEST(_WKDownload, DISABLED_RedirectedSubframeLoadConvertedToDownload)
554 [TestProtocol registerWithScheme:@"http"];
556 auto navigationDelegate = adoptNS([[ConvertResponseToDownloadNavigationDelegate alloc] init]);
557 auto downloadDelegate = adoptNS([[RedirectedDownloadDelegate alloc] init]);
559 auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
560 [webView setNavigationDelegate:navigationDelegate.get()];
561 [[[webView configuration] processPool] _setDownloadDelegate:downloadDelegate.get()];
563 expectedOriginatingWebView = webView.get();
566 hasReceivedResponse = false;
567 [webView loadHTMLString:@"<body><iframe src='http://redirect/?redirect/?pass'></iframe></body>" baseURL:nil];
568 TestWebKitAPI::Util::run(&isDone);
569 EXPECT_EQ(0U, redirectCount);
571 [TestProtocol unregister];