/* * Copyright (C) 2014-2020 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "DeprecatedGlobalValues.h" #import "HTTPServer.h" #import "PlatformUtilities.h" #import "Test.h" #import "TestNavigationDelegate.h" #import "TestUIDelegate.h" #import "TestURLSchemeHandler.h" #import #import #import #import #import #import #import #import #import #import static RetainPtr currentNavigation; static RetainPtr redirectURL; static NSTimeInterval redirectDelay; static bool didCancelRedirect; @interface NavigationDelegate : NSObject @end @implementation NavigationDelegate - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { EXPECT_EQ(currentNavigation, navigation); EXPECT_NOT_NULL(navigation._request); } - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { EXPECT_EQ(currentNavigation, navigation); } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { EXPECT_EQ(currentNavigation, navigation); isDone = true; } @end TEST(WKNavigation, NavigationDelegate) { RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); auto delegate = adoptNS([[NavigationDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; @autoreleasepool { EXPECT_EQ(delegate, [webView navigationDelegate]); } delegate = nil; EXPECT_NULL([webView navigationDelegate]); } TEST(WKNavigation, LoadRequest) { RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); RetainPtr delegate = adoptNS([[NavigationDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]; currentNavigation = [webView loadRequest:request]; ASSERT_NOT_NULL(currentNavigation); ASSERT_TRUE([[currentNavigation _request] isEqual:request]); isDone = false; TestWebKitAPI::Util::run(&isDone); } TEST(WKNavigation, HTTPBody) { __block bool done = false; auto delegate = adoptNS([TestNavigationDelegate new]); NSData *testData = [@"testhttpbody" dataUsingEncoding:NSUTF8StringEncoding]; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) { EXPECT_TRUE([action.request.HTTPBody isEqualToData:testData]); decisionHandler(WKNavigationActionPolicyCancel); done = true; }; auto webView = adoptNS([WKWebView new]); [webView setNavigationDelegate:delegate.get()]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"test:///willNotActuallyLoad"]]; [request setHTTPBody:testData]; [webView loadRequest:request]; TestWebKitAPI::Util::run(&done); } TEST(WKNavigation, UserAgentAndAccept) { using namespace TestWebKitAPI; HTTPServer server([](Connection) { }); __block bool done = false; auto delegate = adoptNS([TestNavigationDelegate new]); delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) { EXPECT_WK_STREQ(action.request.allHTTPHeaderFields[@"User-Agent"], "testUserAgent"); EXPECT_WK_STREQ(action.request.allHTTPHeaderFields[@"Accept"], "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); decisionHandler(WKNavigationActionPolicyCancel); done = true; }; auto webView = adoptNS([WKWebView new]); webView.get().customUserAgent = @"testUserAgent"; [webView setNavigationDelegate:delegate.get()]; [webView loadRequest:server.request()]; TestWebKitAPI::Util::run(&done); } @interface FrameNavigationDelegate : NSObject - (void)waitForNavigations:(size_t)count; @property (nonatomic, readonly) NSArray *requests; @property (nonatomic, readonly) NSArray *frames; @property (nonatomic, readonly) NSArray *callbacks; @end @implementation FrameNavigationDelegate { RetainPtr> _requests; RetainPtr> _frames; RetainPtr> _callbacks; size_t _navigationCount; } - (void)waitForNavigations:(size_t)expectedNavigationCount { while (_navigationCount < expectedNavigationCount) TestWebKitAPI::Util::spinRunLoop(); } - (NSArray *)requests { return _requests.get(); } - (NSArray *)frames { return _frames.get(); } - (NSArray *)callbacks { return _callbacks.get(); } - (void)_webView:(WKWebView *)webView didStartProvisionalLoadWithRequest:(NSURLRequest *)request inFrame:(WKFrameInfo *)frame { if (!_requests) _requests = [NSMutableArray array]; [_requests addObject:request]; if (!_frames) _frames = [NSMutableArray array]; [_frames addObject:frame]; if (!_callbacks) _callbacks = [NSMutableArray array]; [_callbacks addObject:@"start provisional"]; } - (void)_webView:(WKWebView *)webView didFailProvisionalLoadWithRequest:(NSURLRequest *)request inFrame:(WKFrameInfo *)frame withError:(NSError *)error { [_requests addObject:request]; [_frames addObject:frame]; [_callbacks addObject:@"fail provisional"]; _navigationCount++; } - (void)_webView:(WKWebView *)webView didCommitLoadWithRequest:(NSURLRequest *)request inFrame:(WKFrameInfo *)frame { [_requests addObject:request]; [_frames addObject:frame]; [_callbacks addObject:@"commit"]; } - (void)_webView:(WKWebView *)webView didFailLoadWithRequest:(NSURLRequest *)request inFrame:(WKFrameInfo *)frame withError:(NSError *)error { [_requests addObject:request]; [_frames addObject:frame]; [_callbacks addObject:@"fail"]; _navigationCount++; } - (void)_webView:(WKWebView *)webView didFinishLoadWithRequest:(NSURLRequest *)request inFrame:(WKFrameInfo *)frame { [_requests addObject:request]; [_frames addObject:frame]; [_callbacks addObject:@"finish"]; _navigationCount++; } @end TEST(WKNavigation, Frames) { auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); auto handler = adoptNS([TestURLSchemeHandler new]); [handler setStartURLSchemeTaskHandler:^(WKWebView *, id task) { NSString *responseString = nil; if ([task.request.URL.absoluteString isEqualToString:@"frame://host1/"]) responseString = @""; else if ([task.request.URL.absoluteString isEqualToString:@"frame://host2/"]) responseString = @""; else if ([task.request.URL.absoluteString isEqualToString:@"frame://host3/"]) { [task didFailWithError:[NSError errorWithDomain:@"testErrorDomain" code:42 userInfo:nil]]; return; } ASSERT(responseString); auto response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:responseString.length textEncodingName:nil]); [task didReceiveResponse:response.get()]; [task didReceiveData:[responseString dataUsingEncoding:NSUTF8StringEncoding]]; [task didFinish]; }]; [configuration setURLSchemeHandler:handler.get() forURLScheme:@"frame"]; auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); auto delegate = adoptNS([FrameNavigationDelegate new]); webView.get().navigationDelegate = delegate.get(); [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"frame://host1/"]]]; [delegate waitForNavigations:3]; struct ExpectedStrings { const char* callback; const char* frameRequest; const char* frameSecurityOriginHost; const char* request; }; auto checkCallbacks = [delegate] (Vector expectedVector) { NSArray *requests = delegate.get().requests; NSArray *frames = delegate.get().frames; NSArray *callbacks = delegate.get().callbacks; EXPECT_EQ(requests.count, expectedVector.size()); EXPECT_EQ(frames.count, expectedVector.size()); EXPECT_EQ(callbacks.count, expectedVector.size()); auto checkCallback = [] (NSString *callback, WKFrameInfo *frame, NSURLRequest *request, const ExpectedStrings& expected) { EXPECT_WK_STREQ(callback, expected.callback); EXPECT_WK_STREQ(frame.request.URL.absoluteString, expected.frameRequest); EXPECT_WK_STREQ(frame.securityOrigin.host, expected.frameSecurityOriginHost); EXPECT_WK_STREQ(request.URL.absoluteString, expected.request); }; for (size_t i = 0; i < expectedVector.size(); ++i) checkCallback(callbacks[i], frames[i], requests[i], expectedVector[i]); }; checkCallbacks({ { "start provisional", "", "", "frame://host1/" }, { "commit", "frame://host1/", "host1", "frame://host1/" }, { "start provisional", "", "host1", "frame://host2/" }, { "commit", "frame://host2/", "host2", "frame://host2/" }, { "finish", "frame://host2/", "host2", "frame://host2/" }, { "finish", "frame://host1/", "host1", "frame://host1/" }, { "start provisional", "frame://host2/", "host2", "frame://host3/" }, { "fail provisional", "frame://host2/", "host2", "frame://host3/" } }); } @interface DidFailProvisionalNavigationDelegate : NSObject @end @implementation DidFailProvisionalNavigationDelegate - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { EXPECT_EQ(currentNavigation, navigation); EXPECT_NOT_NULL(navigation._request); } - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { EXPECT_EQ(currentNavigation, navigation); EXPECT_NOT_NULL(navigation._request); EXPECT_TRUE([error.domain isEqualToString:NSURLErrorDomain]); EXPECT_EQ(NSURLErrorUnsupportedURL, error.code); isDone = true; } - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { decisionHandler(WKNavigationActionPolicyAllow); } @end TEST(WKNavigation, DidFailProvisionalNavigation) { RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); RetainPtr delegate = adoptNS([[DidFailProvisionalNavigationDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"non-existant-scheme://"]]; currentNavigation = [webView loadRequest:request]; ASSERT_NOT_NULL(currentNavigation); ASSERT_TRUE([[currentNavigation _request] isEqual:request]); isDone = false; TestWebKitAPI::Util::run(&isDone); } @interface CrashReasonDelegate : NSObject @end @implementation CrashReasonDelegate - (void)_webView:(WKWebView *)webView webContentProcessDidTerminateWithReason:(_WKProcessTerminationReason)reason { EXPECT_EQ(reason, _WKProcessTerminationReasonRequestedByClient); isDone = true; } @end TEST(WKNavigation, CrashReason) { auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); auto delegate = adoptNS([[CrashReasonDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; [webView loadHTMLString:@"start the web process" baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; [webView _killWebContentProcessAndResetState]; TestWebKitAPI::Util::run(&isDone); } @interface DecidePolicyForPageCacheNavigationDelegate : NSObject @property (nonatomic) BOOL decidedPolicyForBackForwardNavigation; @end @implementation DecidePolicyForPageCacheNavigationDelegate - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { isDone = true; } - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { if (navigationAction.navigationType == WKNavigationTypeBackForward) _decidedPolicyForBackForwardNavigation = YES; decisionHandler(WKNavigationActionPolicyAllow); } @end TEST(WKNavigation, DecidePolicyForPageCacheNavigation) { RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); RetainPtr delegate = adoptNS([[DecidePolicyForPageCacheNavigationDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,1"]]; isDone = false; [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,2"]]; isDone = false; [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); isDone = false; [webView goBack]; TestWebKitAPI::Util::run(&isDone); ASSERT_TRUE([delegate decidedPolicyForBackForwardNavigation]); } @interface NavigationActionHasNavigationDelegate : NSObject { @public WKNavigation *navigation; } @end @implementation NavigationActionHasNavigationDelegate - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { isDone = true; } - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { EXPECT_TRUE(!!navigationAction._mainFrameNavigation); EXPECT_EQ(navigationAction._mainFrameNavigation, navigation); decisionHandler(WKNavigationActionPolicyAllow); } @end TEST(WKNavigation, NavigationActionHasNavigation) { RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); RetainPtr delegate = adoptNS([[NavigationActionHasNavigationDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,1"]]; isDone = false; delegate->navigation = [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); } @interface ClientRedirectNavigationDelegate : NSObject @end @implementation ClientRedirectNavigationDelegate - (void)_webView:(WKWebView *)webView willPerformClientRedirectToURL:(NSURL *)URL delay:(NSTimeInterval)delay { redirectURL = URL; redirectDelay = delay; } - (void)_webViewDidCancelClientRedirect:(WKWebView *)webView { didCancelRedirect = true; } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { isDone = true; } @end TEST(WKNavigation, WebViewWillPerformClientRedirect) { auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); configuration.get()._allowTopNavigationToDataURLs = YES; auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); auto delegate = adoptNS([[ClientRedirectNavigationDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; auto request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,%3Cmeta%20http-equiv=%22refresh%22%20content=%22123;URL=data:text/html,Page1%22%3E"]]; isDone = false; redirectURL = nil; redirectDelay = 0; [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); ASSERT_DOUBLE_EQ(redirectDelay, 123); ASSERT_STREQ(redirectURL.get().absoluteString.UTF8String, "data:text/html,Page1"); request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,%3Cscript%3Ewindow.location=%22data:text/html,Page2%22;%3C/script%3E"]]; isDone = false; redirectURL = nil; redirectDelay = NSTimeIntervalSince1970; // Use any non-zero value, we will test that the delegate receives a delay of 0. [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); ASSERT_DOUBLE_EQ(redirectDelay, 0); ASSERT_STREQ(redirectURL.get().absoluteString.UTF8String, "data:text/html,Page2"); } TEST(WKNavigation, WebViewDidCancelClientRedirect) { auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); configuration.get()._allowTopNavigationToDataURLs = YES; auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); auto delegate = adoptNS([[ClientRedirectNavigationDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; // Test 1: During a navigation that is not a client redirect, -_webViewDidCancelClientRedirect: should not be called. auto request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,Page1"]]; isDone = false; didCancelRedirect = false; [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); ASSERT_FALSE(didCancelRedirect); // Test 2: When a client redirect does happen, -_webViewDidCancelClientRedirect: should still be called. It essentially // is called whenever the web view transitions from "expecting a redirect" to "not expecting a redirect". request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,%3Cscript%3Ewindow.location=%22data:text/html,Page2%22;%3C/script%3E"]]; isDone = false; didCancelRedirect = false; [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); ASSERT_FALSE(didCancelRedirect); isDone = false; TestWebKitAPI::Util::run(&isDone); ASSERT_TRUE(didCancelRedirect); // Test 3: When another navigation begins while a client redirect is scheduled, -_webViewDidCancelClientRedirect: // should be called. request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,%3Cmeta%20http-equiv=%22refresh%22%20content=%2210000;URL=data:text/html,Page3%22%3E"]]; isDone = false; didCancelRedirect = false; [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); ASSERT_FALSE(didCancelRedirect); request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,Page4"]]; isDone = false; [webView loadRequest:request]; TestWebKitAPI::Util::run(&isDone); ASSERT_TRUE(didCancelRedirect); } @interface NavigationActionSPIDelegate : NSObject { @public BOOL _spiCalled; } - (BOOL)spiCalled; @end @implementation NavigationActionSPIDelegate - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { isDone = true; } -(void)_webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction preferences:(WKWebpagePreferences *)preferences userInfo:(id )userInfo decisionHandler:(void (^)(WKNavigationActionPolicy, WKWebpagePreferences *))decisionHandler { _spiCalled = TRUE; decisionHandler(WKNavigationActionPolicyAllow, preferences); } - (BOOL)spiCalled { return _spiCalled; } @end TEST(WKNavigation, NavigationActionSPI) { auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); auto delegate = adoptNS([[NavigationActionSPIDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"data:text/html,1"]]]; TestWebKitAPI::Util::run(&isDone); EXPECT_TRUE([delegate spiCalled]); } static bool navigationComplete; @interface BackForwardDelegate : NSObject @end @implementation BackForwardDelegate - (void)_webView:(WKWebView *)webView willGoToBackForwardListItem:(WKBackForwardListItem *)item inPageCache:(BOOL)inPageCache { const char* expectedURL = [[[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"] absoluteString] UTF8String]; EXPECT_STREQ(item.URL.absoluteString.UTF8String, expectedURL); EXPECT_TRUE(item.title == nil); EXPECT_STREQ(item.initialURL.absoluteString.UTF8String, expectedURL); EXPECT_TRUE(inPageCache); isDone = true; } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { navigationComplete = true; } @end TEST(WKNavigation, WillGoToBackForwardListItem) { auto webView = adoptNS([[WKWebView alloc] init]); auto delegate = adoptNS([[BackForwardDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; [webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]]; TestWebKitAPI::Util::run(&navigationComplete); navigationComplete = false; [webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]]; TestWebKitAPI::Util::run(&navigationComplete); [webView goBack]; TestWebKitAPI::Util::run(&isDone); } #if PLATFORM(MAC) RetainPtr firstItem; RetainPtr secondItem; @interface ListItemDelegate : NSObject @end @implementation ListItemDelegate - (void)_webView:(WKWebView *)webView backForwardListItemAdded:(WKBackForwardListItem *)itemAdded removed:(NSArray *)itemsRemoved { NSString *firstURL = [[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"].absoluteString; NSString *secondURL = [[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"].absoluteString; if (!firstItem) { EXPECT_NULL(firstItem); EXPECT_NULL(secondItem); EXPECT_NULL(itemsRemoved); EXPECT_NOT_NULL(itemAdded); EXPECT_STREQ(firstURL.UTF8String, itemAdded.URL.absoluteString.UTF8String); firstItem = itemAdded; } else if (!secondItem) { EXPECT_NOT_NULL(firstItem); EXPECT_NULL(secondItem); EXPECT_NULL(itemsRemoved); EXPECT_NOT_NULL(itemAdded); EXPECT_STREQ(secondURL.UTF8String, itemAdded.URL.absoluteString.UTF8String); secondItem = itemAdded; } else { EXPECT_NOT_NULL(firstItem); EXPECT_NOT_NULL(secondItem); EXPECT_NOT_NULL(itemsRemoved); EXPECT_NULL(itemAdded); EXPECT_EQ([itemsRemoved count], 2u); EXPECT_STREQ(firstURL.UTF8String, [itemsRemoved objectAtIndex:0].URL.absoluteString.UTF8String); EXPECT_STREQ(secondURL.UTF8String, [itemsRemoved objectAtIndex:1].URL.absoluteString.UTF8String); isDone = true; } } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { navigationComplete = true; } @end TEST(WKNavigation, ListItemAddedRemoved) { auto webView = adoptNS([[WKWebView alloc] init]); auto delegate = adoptNS([[ListItemDelegate alloc] init]); [webView setNavigationDelegate:delegate.get()]; [webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]]; TestWebKitAPI::Util::run(&navigationComplete); navigationComplete = false; [webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]]; TestWebKitAPI::Util::run(&navigationComplete); [[webView backForwardList] _removeAllItems]; TestWebKitAPI::Util::run(&isDone); } #endif // PLATFORM(MAC) @interface LoadingObserver : NSObject @property (nonatomic, readonly) size_t changesObserved; @end @implementation LoadingObserver { size_t _changesObserved; } - (size_t)changesObserved { return _changesObserved; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { EXPECT_WK_STREQ(keyPath, "loading"); _changesObserved++; } @end TEST(WKNavigation, FrameBackLoading) { using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { ""_s } }, { "/frame1.html"_s, { "link"_s } }, { "/frame2.html"_s, { ""_s } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestUIDelegate new]); auto observer = adoptNS([LoadingObserver new]); [webView setUIDelegate:delegate.get()]; [webView addObserver:observer.get() forKeyPath:@"loading" options:NSKeyValueObservingOptionNew context:nil]; EXPECT_FALSE([webView isLoading]); EXPECT_EQ([observer changesObserved], 0u); [webView loadRequest:server.request()]; EXPECT_TRUE([webView isLoading]); EXPECT_EQ([observer changesObserved], 1u); while ([observer changesObserved] < 2u) Util::spinRunLoop(); EXPECT_FALSE([webView isLoading]); EXPECT_EQ([observer changesObserved], 2u); EXPECT_FALSE([webView canGoBack]); [webView evaluateJavaScript:@"document.querySelector('iframe').contentWindow.document.querySelector('a').click()" completionHandler:nil]; EXPECT_WK_STREQ([delegate waitForAlert], "frame2 loaded"); EXPECT_EQ([observer changesObserved], 2u); EXPECT_TRUE([webView canGoBack]); [webView goBack]; while ([observer changesObserved] < 3) Util::spinRunLoop(); EXPECT_TRUE([webView isLoading]); while ([observer changesObserved] < 4) Util::spinRunLoop(); EXPECT_FALSE([webView isLoading]); [webView removeObserver:observer.get() forKeyPath:@"loading"]; } TEST(WKNavigation, SimultaneousNavigationWithFontsFinishes) { constexpr auto mainHTML = "" "" "" "" "" "" "" "text" "" "" "" ""_s; NSString *svg = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"AllAhem" withExtension:@"svg" subdirectory:@"TestWebKitAPI.resources"] encoding:NSUTF8StringEncoding error:nil]; using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { mainHTML } }, { "/Ahem.svg"_s, { svg } }, { "/scriptsrc.js"_s, { "/* js content */"_s } }, { "/iframesrc.html"_s, { "frame content"_s } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestNavigationDelegate new]); [webView setNavigationDelegate:delegate.get()]; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) { if ([action.request.URL.scheme isEqualToString:@"refresh-nav"]) completionHandler(WKNavigationActionPolicyCancel); else completionHandler(WKNavigationActionPolicyAllow); }; __block bool finishedNavigation = false; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedNavigation = true; }; [webView loadRequest:server.request()]; Util::run(&finishedNavigation); } TEST(WKNavigation, LoadRadarURLFromSandboxedFrameAllowPopups) { constexpr auto mainHTML = ""_s; constexpr auto frameHTML = "Link"_s; using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { mainHTML } }, { "/frame.html"_s, { frameHTML } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestNavigationDelegate new]); [webView setNavigationDelegate:delegate.get()]; __block bool didTryToLoadRadarURL = false; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) { if ([action.request.URL.scheme isEqualToString:@"rdar"]) { didTryToLoadRadarURL = true; completionHandler(WKNavigationActionPolicyCancel); } else completionHandler(WKNavigationActionPolicyAllow); }; __block bool finishedNavigation = false; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedNavigation = true; }; [webView loadRequest:server.request()]; Util::run(&finishedNavigation); Util::run(&didTryToLoadRadarURL); } TEST(WKNavigation, LoadRadarURLFromSandboxedFrameAllowTopNavigation) { constexpr auto mainHTML = ""_s; constexpr auto frameHTML = "Link"_s; using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { mainHTML } }, { "/frame.html"_s, { frameHTML } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestNavigationDelegate new]); [webView setNavigationDelegate:delegate.get()]; __block bool didTryToLoadRadarURL = false; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) { if ([action.request.URL.scheme isEqualToString:@"rdar"]) { didTryToLoadRadarURL = true; completionHandler(WKNavigationActionPolicyCancel); } else completionHandler(WKNavigationActionPolicyAllow); }; __block bool finishedNavigation = false; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedNavigation = true; }; [webView loadRequest:server.request()]; Util::run(&finishedNavigation); Util::run(&didTryToLoadRadarURL); } TEST(WKNavigation, LoadRadarURLFromSandboxedFrameAllowCustomProtocolsNavigation) { constexpr auto mainHTML = ""_s; constexpr auto frameHTML = "Link"_s; using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { mainHTML } }, { "/frame.html"_s, { frameHTML } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestNavigationDelegate new]); [webView setNavigationDelegate:delegate.get()]; __block bool didTryToLoadRadarURL = false; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) { if ([action.request.URL.scheme isEqualToString:@"rdar"]) { didTryToLoadRadarURL = true; completionHandler(WKNavigationActionPolicyCancel); } else completionHandler(WKNavigationActionPolicyAllow); }; __block bool finishedNavigation = false; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedNavigation = true; }; [webView loadRequest:server.request()]; Util::run(&finishedNavigation); Util::run(&didTryToLoadRadarURL); } TEST(WKNavigation, LoadRadarURLFromSandboxedFrameWithUserGesture) { constexpr auto mainHTML = ""_s; constexpr auto frameHTML = "Link"_s; using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { mainHTML } }, { "/frame.html"_s, { frameHTML } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestNavigationDelegate new]); [webView setNavigationDelegate:delegate.get()]; __block RetainPtr iframe; __block bool didTryToLoadRadarURL = false; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) { if (!action.targetFrame.isMainFrame) iframe = action.targetFrame; if ([action.request.URL.scheme isEqualToString:@"rdar"]) { didTryToLoadRadarURL = true; completionHandler(WKNavigationActionPolicyCancel); } else completionHandler(WKNavigationActionPolicyAllow); }; __block bool finishedNavigation = false; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedNavigation = true; }; [webView loadRequest:server.request()]; Util::run(&finishedNavigation); ASSERT_TRUE(!!iframe); // Running Javascript simulates a user gesture so the navigation should be permitted due to 'allow-top-navigation-by-user-activation'. [webView evaluateJavaScript:@"document.getElementById('testLink').click()" inFrame:iframe.get() inContentWorld:WKContentWorld.pageWorld completionHandler:nil]; Util::run(&didTryToLoadRadarURL); } TEST(WKNavigation, LoadRadarURLFromSandboxedFrame) { constexpr auto mainHTML = ""_s; constexpr auto frameHTML = "Link"_s; using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { mainHTML } }, { "/frame.html"_s, { frameHTML } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestNavigationDelegate new]); [webView setNavigationDelegate:delegate.get()]; __block bool didTryToLoadRadarURL = false; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) { if ([action.request.URL.scheme isEqualToString:@"rdar"]) { didTryToLoadRadarURL = true; completionHandler(WKNavigationActionPolicyCancel); } else completionHandler(WKNavigationActionPolicyAllow); }; __block bool finishedNavigation = false; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedNavigation = true; }; [webView loadRequest:server.request()]; Util::run(&finishedNavigation); Util::sleep(0.5); EXPECT_FALSE(didTryToLoadRadarURL); } TEST(WKNavigation, LoadRadarURLFromSandboxedFrameMissingUserGesture) { constexpr auto mainHTML = ""_s; constexpr auto frameHTML = "Link"_s; using namespace TestWebKitAPI; HTTPServer server({ { "/"_s, { mainHTML } }, { "/frame.html"_s, { frameHTML } }, }); auto webView = adoptNS([WKWebView new]); auto delegate = adoptNS([TestNavigationDelegate new]); [webView setNavigationDelegate:delegate.get()]; __block bool didTryToLoadRadarURL = false; delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) { if ([action.request.URL.scheme isEqualToString:@"rdar"]) { didTryToLoadRadarURL = true; completionHandler(WKNavigationActionPolicyCancel); } else completionHandler(WKNavigationActionPolicyAllow); }; __block bool finishedNavigation = false; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedNavigation = true; }; [webView loadRequest:server.request()]; Util::run(&finishedNavigation); Util::sleep(0.5); EXPECT_FALSE(didTryToLoadRadarURL); } TEST(WKNavigation, CrossOriginCOOPCancelResponseFailProvisionalNavigationCallback) { using namespace TestWebKitAPI; HTTPServer server({ { "/path1"_s, { "hi"_s } }, { "/path2"_s, { "hi"_s } }, { "/path3"_s, { { { "Cross-Origin-Opener-Policy"_s, "same-origin"_s } }, "hi"_s } } }, HTTPServer::Protocol::HttpsProxy); auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]); [storeConfiguration setProxyConfiguration:@{ (NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1", (NSString *)kCFStreamPropertyHTTPSProxyPort: @(server.port()) }]; auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]); auto configuration = adoptNS([WKWebViewConfiguration new]); [configuration setWebsiteDataStore:dataStore.get()]; auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); __block Vector finishedSuccessfullyCallbacks; auto loadWithResponsePolicy = ^(WKWebView *webView, NSString *url, WKNavigationResponsePolicy responsePolicy) { auto callbacksSizeBefore = finishedSuccessfullyCallbacks.size(); auto delegate = adoptNS([TestNavigationDelegate new]); delegate.get().decidePolicyForNavigationResponse = ^(WKNavigationResponse *response, void (^decisionHandler)(WKNavigationResponsePolicy)) { decisionHandler(responsePolicy); }; delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); }; delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *) { finishedSuccessfullyCallbacks.append(false); }; delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) { finishedSuccessfullyCallbacks.append(true); }; [webView setNavigationDelegate:delegate.get()]; [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]]; while (finishedSuccessfullyCallbacks.size() == callbacksSizeBefore) TestWebKitAPI::Util::spinRunLoop(10); }; loadWithResponsePolicy(webView.get(), @"https://webkit.org/path1", WKNavigationResponsePolicyAllow); loadWithResponsePolicy(webView.get(), @"https://webkit.org/path2", WKNavigationResponsePolicyCancel); loadWithResponsePolicy(webView.get(), @"https://example.com/path3", WKNavigationResponsePolicyCancel); Vector expectedCallbacks { true, false, false }; EXPECT_EQ(finishedSuccessfullyCallbacks, expectedCallbacks); }