blob: 0656e0b40c4eb0b7d217512a499c5881b1a0e207 [file] [log] [blame] [edit]
/*
* 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 "SiteIsolationUtilities.h"
#import "Test.h"
#import "TestNavigationDelegate.h"
#import "TestProtocol.h"
#import "TestUIDelegate.h"
#import "TestURLSchemeHandler.h"
#import "TestWKWebView.h"
#import <WebKit/WKBackForwardListPrivate.h>
#import <WebKit/WKErrorPrivate.h>
#import <WebKit/WKFrameInfoPrivate.h>
#import <WebKit/WKNavigationActionPrivate.h>
#import <WebKit/WKNavigationDelegatePrivate.h>
#import <WebKit/WKNavigationPrivate.h>
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKProcessPoolPrivate.h>
#import <WebKit/WKURLRequest.h>
#import <WebKit/WKWebView.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebpagePreferencesPrivate.h>
#import <WebKit/WKWebsiteDataStorePrivate.h>
#import <WebKit/_WKFeature.h>
#import <WebKit/_WKPageLoadTiming.h>
#import <WebKit/_WKWebsiteDataStoreConfiguration.h>
#import <WebKit/_WKWebsiteDataStoreDelegate.h>
#import <wtf/RetainPtr.h>
#import <wtf/Vector.h>
#import <wtf/text/MakeString.h>
static RetainPtr<WKNavigation> currentNavigation;
static RetainPtr<NSURL> redirectURL;
static NSTimeInterval redirectDelay;
static bool didCancelRedirect;
static bool didReceiveAllowPrivateToken;
@interface NavigationDelegate : NSObject <WKNavigationDelegate, _WKWebsiteDataStoreDelegate>
@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;
}
- (void)websiteDataStore:(WKWebsiteDataStore *)dataStore didAllowPrivateTokenUsageByThirdPartyForTesting:(BOOL)wasAllowed forResourceURL:(NSURL *)resourceURL
{
if ([resourceURL.host isEqualToString:@"site2.example"]) {
if ([resourceURL.path isEqualToString:@"/path2"])
EXPECT_TRUE(wasAllowed);
else
EXPECT_FALSE(wasAllowed);
} else if ([resourceURL.host isEqualToString:@"site3.example"]) {
if ([resourceURL.path isEqualToString:@"/path2"])
EXPECT_FALSE(wasAllowed);
else
EXPECT_TRUE(wasAllowed);
} else if ([resourceURL.host isEqualToString:@"site4.example"])
EXPECT_TRUE(wasAllowed);
else if ([resourceURL.host isEqualToString:@"site5.example"]) {
if ([resourceURL.path isEqualToString:@"/path6"])
EXPECT_FALSE(wasAllowed);
else
EXPECT_TRUE(wasAllowed);
} else
EXPECT_WK_STREQ(resourceURL.absoluteString, @"https://site1.example/path1");
didReceiveAllowPrivateToken = true;
}
@end
TEST(WKNavigation, NavigationDelegate)
{
RetainPtr<WKWebView> 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<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
RetainPtr<NavigationDelegate> delegate = adoptNS([[NavigationDelegate alloc] init]);
[webView setNavigationDelegate:delegate.get()];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]];
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 <WKNavigationDelegate>
- (void)waitForNavigations:(size_t)count;
- (void)clearState;
@property (nonatomic, readonly) NSArray<NSURLRequest *> *requests;
@property (nonatomic, readonly) NSArray<WKFrameInfo *> *frames;
@property (nonatomic, readonly) NSArray<NSString *> *callbacks;
@end
@implementation FrameNavigationDelegate {
RetainPtr<NSMutableArray<NSURLRequest *>> _requests;
RetainPtr<NSMutableArray<WKFrameInfo *>> _frames;
RetainPtr<NSMutableArray<NSString *>> _callbacks;
size_t _navigationCount;
}
- (void)waitForNavigations:(size_t)expectedNavigationCount
{
while (_navigationCount < expectedNavigationCount)
TestWebKitAPI::Util::spinRunLoop();
}
- (NSArray<NSURLRequest *> *)requests
{
return _requests.get();
}
- (NSArray<WKFrameInfo *> *)frames
{
return _frames.get();
}
- (NSArray<NSString *> *)callbacks
{
return _callbacks.get();
}
- (void)clearState
{
_requests = nullptr;
_frames = nullptr;
_callbacks = nullptr;
_navigationCount = 0;
}
- (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
{
EXPECT_TRUE(frame._errorOccurred);
[_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
{
EXPECT_TRUE(frame._errorOccurred);
[_requests addObject:request];
[_frames addObject:frame];
[_callbacks addObject:@"fail"];
_navigationCount++;
}
- (void)_webView:(WKWebView *)webView didFinishLoadWithRequest:(NSURLRequest *)request inFrame:(WKFrameInfo *)frame
{
EXPECT_FALSE(frame._errorOccurred);
[_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<WKURLSchemeTask> task) {
NSString *responseString = nil;
if ([task.request.URL.absoluteString isEqualToString:@"frame://host1/"])
responseString = @"<iframe src='frame://host2/'></iframe>";
else if ([task.request.URL.absoluteString isEqualToString:@"frame://host2/"])
responseString = @"<script>function navigate() { window.location='frame://host3/' }</script><body onload='navigate()'></body>";
else if ([task.request.URL.absoluteString isEqualToString:@"frame://host3/"]) {
[task didFailWithError:[NSError errorWithDomain:@"testErrorDomain" code:42 userInfo:nil]];
return;
} else if ([task.request.URL.absoluteString isEqualToString:@"frame://host4/"])
responseString = @"<p>Hello World</p>";
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<ExpectedStrings> expectedVector) {
NSArray<NSURLRequest *> *requests = delegate.get().requests;
NSArray<WKFrameInfo *> *frames = delegate.get().frames;
NSArray<NSString *> *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/"
}
});
// After the failed navigation, perform another successful navigation that will clear the errorOccurred state.
[delegate clearState];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"frame://host4/"]]];
[delegate waitForNavigations:1];
checkCallbacks({
{
"start provisional",
"frame://host1/",
"host1",
"frame://host4/"
}, {
"commit",
"frame://host4/",
"host4",
"frame://host4/"
}, {
"finish",
"frame://host4/",
"host4",
"frame://host4/"
}
});
}
@interface DidFailProvisionalNavigationDelegate : NSObject <WKNavigationDelegate>
@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<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
RetainPtr<DidFailProvisionalNavigationDelegate> 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 <WKNavigationDelegate>
@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:@"<html>start the web process</html>" baseURL:[NSURL URLWithString:@"https://webkit.org/"]];
[webView _killWebContentProcessAndResetState];
TestWebKitAPI::Util::run(&isDone);
}
@interface DecidePolicyForPageCacheNavigationDelegate : NSObject <WKNavigationDelegate>
@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<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
RetainPtr<DecidePolicyForPageCacheNavigationDelegate> 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 <WKNavigationDelegate> {
@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<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
RetainPtr<NavigationActionHasNavigationDelegate> 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<WKNavigationDelegatePrivate>
@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 <WKNavigationDelegate> {
@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 <NSSecureCoding>)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<WKNavigationDelegatePrivate>
@end
@implementation BackForwardDelegate
- (void)_webView:(WKWebView *)webView willGoToBackForwardListItem:(WKBackForwardListItem *)item inPageCache:(BOOL)inPageCache
{
const char* expectedURL = [[[NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"] 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]);
// FIXME: Page cache is currently disabled under site isolation; see rdar://161762363.
if (isSiteIsolationEnabled(webView.get()))
return;
auto delegate = adoptNS([[BackForwardDelegate alloc] init]);
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]]];
TestWebKitAPI::Util::run(&navigationComplete);
navigationComplete = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]]];
TestWebKitAPI::Util::run(&navigationComplete);
[webView goBack];
TestWebKitAPI::Util::run(&isDone);
}
static bool didRejectNavigation = false;
@interface BackForwardDelegateWithShouldGo : NSObject<WKNavigationDelegatePrivate>
@property (nonatomic, readwrite) BOOL allowNavigation;
@property (nonatomic, retain) WKBackForwardListItem *targetItem;
@end
@implementation BackForwardDelegateWithShouldGo
- (void)_webView:(WKWebView *)webView willGoToBackForwardListItem:(WKBackForwardListItem *)item inPageCache:(BOOL)inPageCache
{
RELEASE_ASSERT_NOT_REACHED();
}
- (void)webView:(WKWebView *)webView shouldGoToBackForwardListItem:(WKBackForwardListItem *)item willUseInstantBack:(BOOL)willUseInstantBack completionHandler:(void (^)(BOOL shouldGoToItem))completionHandler
{
EXPECT_EQ(item, _targetItem);
EXPECT_TRUE(item.title == nil);
EXPECT_TRUE(willUseInstantBack);
completionHandler(_allowNavigation);
if (!_allowNavigation)
didRejectNavigation = true;
}
- (void)_webView:(WKWebView *)webView shouldGoToBackForwardListItem:(WKBackForwardListItem *)item inPageCache:(BOOL)inPageCache completionHandler:(void (^)(BOOL shouldGoToItem))completionHandler
{
// When the API selector exists, it should be preferred over the SPI selector
RELEASE_ASSERT_NOT_REACHED();
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
navigationComplete = true;
}
@end
@interface BackForwardDelegateWithShouldGoSPI : NSObject<WKNavigationDelegatePrivate>
@property (nonatomic, readwrite) BOOL allowNavigation;
@property (nonatomic, retain) WKBackForwardListItem *targetItem;
@end
@implementation BackForwardDelegateWithShouldGoSPI
- (void)_webView:(WKWebView *)webView willGoToBackForwardListItem:(WKBackForwardListItem *)item inPageCache:(BOOL)inPageCache
{
RELEASE_ASSERT_NOT_REACHED();
}
- (void)_webView:(WKWebView *)webView shouldGoToBackForwardListItem:(WKBackForwardListItem *)item inPageCache:(BOOL)inPageCache completionHandler:(void (^)(BOOL shouldGoToItem))completionHandler
{
EXPECT_EQ(item, _targetItem);
EXPECT_TRUE(item.title == nil);
EXPECT_TRUE(inPageCache);
completionHandler(_allowNavigation);
if (!_allowNavigation)
didRejectNavigation = true;
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
navigationComplete = true;
}
@end
TEST(WKNavigation, ShouldGoToBackForwardListItem)
{
auto webView = adoptNS([[WKWebView alloc] init]);
// FIXME: Page cache is currently disabled under site isolation; see rdar://161762363.
// This test relies on the back forward cache. Once it is enabled, remove this early return.
if (isSiteIsolationEnabled(webView.get()))
return;
auto delegate = adoptNS([[BackForwardDelegateWithShouldGo alloc] init]);
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]]];
TestWebKitAPI::Util::run(&navigationComplete);
navigationComplete = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]]];
TestWebKitAPI::Util::run(&navigationComplete);
navigationComplete = false;
delegate.get().targetItem = webView.get().backForwardList.backItem;
delegate.get().allowNavigation = YES;
[webView goBack];
TestWebKitAPI::Util::run(&navigationComplete);
EXPECT_FALSE(didRejectNavigation);
delegate.get().targetItem = webView.get().backForwardList.forwardItem;
delegate.get().allowNavigation = NO;
[webView goForward];
TestWebKitAPI::Util::run(&didRejectNavigation);
navigationComplete = false;
delegate.get().allowNavigation = YES;
[webView goForward];
TestWebKitAPI::Util::run(&navigationComplete);
navigationComplete = false;
didRejectNavigation = false;
auto delegate2 = adoptNS([[BackForwardDelegateWithShouldGoSPI alloc] init]);
[webView setNavigationDelegate:delegate2.get()];
delegate2.get().targetItem = webView.get().backForwardList.backItem;
delegate2.get().allowNavigation = YES;
[webView goBack];
TestWebKitAPI::Util::run(&navigationComplete);
EXPECT_FALSE(didRejectNavigation);
delegate2.get().targetItem = webView.get().backForwardList.forwardItem;
delegate2.get().allowNavigation = NO;
[webView goForward];
TestWebKitAPI::Util::run(&didRejectNavigation);
}
#if PLATFORM(MAC)
RetainPtr<WKBackForwardListItem> firstItem;
RetainPtr<WKBackForwardListItem> secondItem;
@interface ListItemDelegate : NSObject<WKNavigationDelegatePrivate>
@end
@implementation ListItemDelegate
- (void)_webView:(WKWebView *)webView backForwardListItemAdded:(WKBackForwardListItem *)itemAdded removed:(NSArray<WKBackForwardListItem *> *)itemsRemoved
{
NSString *firstURL = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"].absoluteString;
NSString *secondURL = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"].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.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]]];
TestWebKitAPI::Util::run(&navigationComplete);
navigationComplete = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]]];
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, { "<iframe src='frame1.html'></iframe>"_s } },
{ "/frame1.html"_s, { "<a href='frame2.html'>link</a>"_s } },
{ "/frame2.html"_s, { "<script>alert('frame2 loaded')</script>"_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 =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<style>"
"@font-face {"
" font-family: 'WebFont';"
" src: url('Ahem.svg') format('svg');"
"}"
"</style>"
"<script src='scriptsrc.js'></script>"
"</head>"
"<body>"
"<span style=\"font: 100px 'WebFont';\">text</span>"
"<iframe src='iframesrc.html'></iframe>"
"<script>window.location='refresh-nav:///'</script>"
"</body>"
"</html>"_s;
NSString *svg = [NSString stringWithContentsOfURL:[NSBundle.test_resourcesBundle URLForResource:@"AllAhem" withExtension:@"svg"] 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 = "<iframe src='frame.html' sandbox='allow-scripts allow-popups'></iframe>"_s;
constexpr auto frameHTML = "<a id='testLink' href='rdar://84498192'>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>"_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 = "<iframe src='frame.html' sandbox='allow-scripts allow-top-navigation'></iframe>"_s;
constexpr auto frameHTML = "<a id='testLink' href='rdar://84498192'>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>"_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 = "<iframe src='frame.html' sandbox='allow-scripts allow-top-navigation-to-custom-protocols'></iframe>"_s;
constexpr auto frameHTML = "<a id='testLink' href='rdar://84498192'>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>"_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 = "<iframe src='frame.html' sandbox='allow-scripts allow-top-navigation-by-user-activation'></iframe>"_s;
constexpr auto frameHTML = "<a id='testLink' href='rdar://84498192'>Link</a></script>"_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<WKFrameInfo> 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 = "<iframe src='frame.html' sandbox='allow-scripts'></iframe>"_s;
constexpr auto frameHTML = "<a id='testLink' href='rdar://84498192'>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>"_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::runFor(0.5_s);
EXPECT_FALSE(didTryToLoadRadarURL);
}
TEST(WKNavigation, LoadRadarURLFromSandboxedFrameMissingUserGesture)
{
constexpr auto mainHTML = "<iframe src='frame.html' sandbox='allow-scripts allow-top-navigation-by-user-activation'></iframe>"_s;
constexpr auto frameHTML = "<a id='testLink' href='rdar://84498192'>Link</a><script>setTimeout(() => { document.getElementById('testLink').click() }, 0);</script>"_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::runFor(0.5_s);
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<bool> 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<bool> expectedCallbacks { true, false, false };
EXPECT_EQ(finishedSuccessfullyCallbacks, expectedCallbacks);
}
TEST(WKNavigation, HTTPSFirstHTTPDowngrade)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { "Welcome"_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSFirst;
for (_WKFeature *feature in [WKPreferences _features]) {
if ([feature.key isEqualToString:@"HTTPSByDefaultEnabled"]) {
[[configuration preferences] _setEnabled:YES forFeature:feature];
break;
}
}
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
__block bool doneEvaluatingJavaScript { false };
[webView evaluateJavaScript:@"window.location.protocol" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http:", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.toString()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
auto url = [NSURL URLWithString:(NSString *)value];
EXPECT_WK_STREQ(@"http", url.scheme);
EXPECT_WK_STREQ(@"http://site.example/secure", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.hasOwnProperty('crypto')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.crypto.hasOwnProperty('subtle')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_FALSE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSFirstHTTPDowngradeAfterPSON)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "body"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { "body"_s } },
{ "http://site2.example/secure"_s, { "body"_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
for (_WKFeature *feature in [WKPreferences _features]) {
if ([feature.key isEqualToString:@"HTTPSByDefaultEnabled"]) {
[[configuration preferences] _setEnabled:YES forFeature:feature];
break;
}
}
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block bool didFailLoad { false };
__block unsigned loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFailProvisionalLoadWithRequestInFrameWithError = ^(WKWebView *, NSURLRequest *, WKFrameInfo *, NSError *error) {
errorCode = error.code;
didFailLoad = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site2.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didFailNavigation);
EXPECT_FALSE(didFailLoad);
EXPECT_EQ(loadCount, 2u);
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
didFailLoad = false;
loadCount = 0;
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSFirst;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
__block bool doneEvaluatingJavaScript { false };
[webView evaluateJavaScript:@"window.location.protocol" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http:", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.toString()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
auto url = [NSURL URLWithString:(NSString *)value];
EXPECT_WK_STREQ(@"http", url.scheme);
EXPECT_WK_STREQ(@"http://site.example/secure", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.hasOwnProperty('crypto')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.crypto.hasOwnProperty('subtle')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_FALSE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didFailNavigation);
EXPECT_FALSE(didFailLoad);
EXPECT_EQ(loadCount, 2u);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSFirstHTTPDowngradeAndSameSiteNavigation)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "body"_s } },
{ "/secure2"_s, { { }, "body"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { "<body><a href=\"/secure2\">Link</a></body>"_s } },
{ "http://site.example/secure2"_s, { "<body><a href=\"http://site2.example/secure2\">Link</a></body>"_s } },
{ "http://site2.example/secure2"_s, { "<body>Done</body>"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSFirst;
for (_WKFeature *feature in [WKPreferences _features]) {
if ([feature.key isEqualToString:@"HTTPSByDefaultEnabled"]) {
[[configuration preferences] _setEnabled:YES forFeature:feature];
break;
}
}
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block unsigned loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
// Load the site with http, it should be upgrade to https. That should fail, and it will fallback to http
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2u);
__block bool doneEvaluatingJavaScript { false };
[webView evaluateJavaScript:@"window.location.protocol" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http:", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.toString()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
auto url = [NSURL URLWithString:(NSString *)value];
EXPECT_WK_STREQ(@"http", url.scheme);
EXPECT_WK_STREQ(@"http://site.example/secure", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.hasOwnProperty('crypto')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.crypto.hasOwnProperty('subtle')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_FALSE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
finishedSuccessfully = false;
errorCode = 0;
loadCount = 0;
doneEvaluatingJavaScript = false;
// Clicking the link should be a same-site navigation, so we shouldn't attempt upgrading.
[webView evaluateJavaScript:@"document.querySelector(\"a\").click()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1u);
finishedSuccessfully = false;
errorCode = 0;
loadCount = 0;
doneEvaluatingJavaScript = false;
// Clicking the link should be a cross-site navigation, so we should attempt upgrading.
[webView evaluateJavaScript:@"document.querySelector(\"a\").click()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2u);
EXPECT_WK_STREQ(@"http://site2.example/secure2", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSFirstHTTPDowngradeRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { 302, {{ "Location"_s, "https://site.example/secure"_s }}, "redirecting..."_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSFirst;
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 3);
}
TEST(WKNavigation, HTTPSFirstRedirectNoHTTPDowngradeRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/redirect"_s, { 302, {{ "Location"_s, "https://site2.example/page1"_s }}, "redirecting..."_s } },
{ "/page1"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/redirect"_s, { 302, {{ "Location"_s, "https://site.example"_s }}, "redirecting..."_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSFirst;
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
if (!loadCount)
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
else
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
};
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/redirect"]]];
while (!errorCode)
TestWebKitAPI::Util::spinRunLoop(5);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
}
TEST(WKNavigation, HTTPSFirstLocalHostIPAddress)
{
using namespace TestWebKitAPI;
HTTPServer httpServer({
{ "/notsecure"_s, { { }, "not secure page"_s } },
}, HTTPServer::Protocol::Http);
auto configuration = adoptNS([WKWebViewConfiguration new]);
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSFirst;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
__block bool didReceiveAuthenticationChallenge { false };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
didReceiveAuthenticationChallenge = true;
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
RetainPtr url = makeString("http://localhost:"_s, httpServer.port(), "/notsecure"_s).createNSString();
[webView loadRequest:adoptNS([[NSURLRequest alloc] initWithURL:adoptNS([[NSURL alloc] initWithString:url.get()]).get()]).get()];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didReceiveAuthenticationChallenge);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(url.get(), [webView URL].absoluteString);
errorCode = 0;
finishedSuccessfully = false;
didReceiveAuthenticationChallenge = false;
loadCount = 0;
url = makeString("http://127.0.0.1:"_s, httpServer.port(), "/notsecure"_s).createNSString();
[webView loadRequest:adoptNS([[NSURLRequest alloc] initWithURL:adoptNS([[NSURL alloc] initWithString:url.get()]).get()]).get()];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didReceiveAuthenticationChallenge);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(url.get(), [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSOnlyInitialLoad)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "secure page"_s } },
{ "/notsecure"_s, { { }, "notsecure page upgraded to secure page"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/notsecure"_s, { { }, "not secure page"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
__block bool didReceiveAuthenticationChallenge { false };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
didReceiveAuthenticationChallenge = true;
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/notsecure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_TRUE(didReceiveAuthenticationChallenge);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(@"https://site.example/notsecure", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSOnlyNonHTTPSSecureSchemes)
{
using namespace TestWebKitAPI;
constexpr auto httpsOnlyError = 305;
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int errorCode { 0 };
__block bool didReceiveAuthenticationChallenge { false };
NSString *secureScheme = @"secure";
[TestProtocol registerWithScheme:secureScheme];
auto configuration = adoptNS([WKWebViewConfiguration new]);
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
didReceiveAuthenticationChallenge = true;
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
didFailNavigation = true;
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
webView.get().navigationDelegate = delegate.get();
auto url = [NSURL URLWithString:@"secure://bundle-file/simple.html"];
[webView loadRequest:[NSURLRequest requestWithURL:url]];
Util::run(&didFailNavigation);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(errorCode, httpsOnlyError);
EXPECT_FALSE(didReceiveAuthenticationChallenge);
finishedSuccessfully = false;
didFailNavigation = false;
errorCode = 0;
[webView.get().configuration.processPool _registerURLSchemeAsSecure:secureScheme];
[webView loadRequest:[NSURLRequest requestWithURL:url]];
Util::run(&finishedSuccessfully);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(didReceiveAuthenticationChallenge);
EXPECT_WK_STREQ(url.absoluteString, webView.get().URL.absoluteString);
finishedSuccessfully = false;
didFailNavigation = false;
}
#if PLATFORM(MAC)
static void checkTitleAndClick(NSButton *button, const char* expectedTitle)
{
EXPECT_STREQ(button.title.UTF8String, expectedTitle);
[button performClick:nil];
}
#else
static void checkTitleAndClick(UIButton *button, const char* expectedTitle)
{
EXPECT_STREQ([button attributedTitleForState:UIControlStateNormal].string.UTF8String, expectedTitle);
UIView *target = button.superview.superview;
SEL selector = NSSelectorFromString(strcmp(expectedTitle, "Go Back") ? @"continueClicked" : @"goBackClicked");
[target performSelector:selector];
}
#endif
TEST(WKNavigation, HTTPSOnlyHTTPFallbackGoBack)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
failedNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_WK_STREQ([webView title], "This Connection Is Not Secure");
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
Util::run(&failedNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1);
__block bool doneEvaluatingJavaScript { false };
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"about:blank", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSOnlyHTTPFallbackContinue)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "body"_s } },
{ "/page2"_s, { { }, "secure"_s } },
{ "/page3"_s, { { }, "secure"_s } },
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { { }, "not secure"_s } },
{ "http://site2.example/page2"_s, { 302, { { "Location"_s, "https://site.example/page2"_s } }, "not secure"_s } },
{ "http://site.example/page3"_s, { } },
{ "http://site.example/page2"_s, { } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
failedNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_WK_STREQ([webView title], "This Connection Is Not Secure");
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[4], "Continue");
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 2);
__block bool doneEvaluatingJavaScript { false };
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView evaluateJavaScript:@"location = \"http://site.example/page3\"" completionHandler:nil];
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView evaluateJavaScript:@"location = \"http://site.example/page2\"" completionHandler:nil];
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView evaluateJavaScript:@"location = \"http://site.example/page3\"" completionHandler:nil];
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site2.example/page2"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
EXPECT_WK_STREQ([webView title], "This Connection Is Not Secure");
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[4], "Continue");
Util::run(&failedNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/page3", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://site.example/page3", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSOnlyHTTPFallbackBypassEnabledCertificateError)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly | _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnlyExplicitlyBypassedForDomain;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
while (!errorCode)
TestWebKitAPI::Util::spinRunLoop(5);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1);
[webView waitForNextPresentationUpdate];
__block bool doneEvaluatingJavaScript { false };
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"about:blank", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSOnlyWithSameSiteBypass)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } },
{ "/secure2"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { { }, "hi: not secure"_s } },
{ "http://www2.site.example/secure"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure"_s, { { }, "hi: not secure"_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
// Step 1: Attempt https load without implementing didReceiveAuthenticationChallenge
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
EXPECT_NOT_NULL(error.userInfo[NSURLErrorFailingURLErrorKey]);
EXPECT_WK_STREQ(@"https://site.example/secure", ((NSURL *)error.userInfo[NSURLErrorFailingURLErrorKey]).absoluteString);
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1);
// Step 2: Attempt http load with HTTPS-bypass enabled
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy |= _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnlyExplicitlyBypassedForDomain;
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NULL(error);
errorCode = error.code;
didFailNavigation = true;
};
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 1);
__block bool doneEvaluatingJavaScript { false };
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
// Step 3: Attempt http load with same-site HTTPS-bypass enabled
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location = \"http://www2.site.example/secure\"" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://www2.site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 1);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://www2.site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://www2.site.example/secure", [webView URL].absoluteString);
// Step 4: Attempt cross-site http load with HTTPS-bypass enabled
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
EXPECT_NOT_NULL(error.userInfo[NSURLErrorFailingURLErrorKey]);
EXPECT_WK_STREQ(@"https://site2.example/secure", ((NSURL *)error.userInfo[NSURLErrorFailingURLErrorKey]).absoluteString);
errorCode = error.code;
didFailNavigation = true;
};
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy &= ~_WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnlyExplicitlyBypassedForDomain;
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location = \"http://site2.example/secure\"" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site2.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_TRUE(didFailNavigation);
EXPECT_EQ(loadCount, 1);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://www2.site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://www2.site.example/secure", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSOnlyWithHTTPRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { 302, {{ "Location"_s, "http://site.example/secure"_s }}, "redirecting..."_s } },
{ "/secure2"_s, { 302, {{ "Location"_s, "http://site2.example/secure3"_s }}, "redirecting..."_s } },
{ "/secure3"_s, { 302, {{ "Location"_s, "http://site2.example/secure2"_s }}, "redirecting..."_s } },
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure2"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure3"_s, { { }, "hi: not secure"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
EXPECT_NOT_NULL(error.userInfo[NSURLErrorFailingURLErrorKey]);
EXPECT_WK_STREQ(@"https://site.example/secure", ((NSURL *)error.userInfo[NSURLErrorFailingURLErrorKey]).absoluteString);
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, _WKErrorCodeHTTPSUpgradeRedirectLoop);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
EXPECT_NOT_NULL(error.userInfo[NSURLErrorFailingURLErrorKey]);
EXPECT_WK_STREQ(@"https://site2.example/secure2", ((NSURL *)error.userInfo[NSURLErrorFailingURLErrorKey]).absoluteString);
errorCode = error.code;
didFailNavigation = true;
};
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, kCFURLErrorHTTPTooManyRedirects);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 21);
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly | _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnlyExplicitlyBypassedForDomain;
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NULL(error);
if (error)
errorCode = error.code;
};
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 3);
EXPECT_WK_STREQ(@"http://site2.example/secure2", [webView URL].absoluteString);
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSOnly;
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[4], "Continue");
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 23);
[webView evaluateJavaScript:@"location = \"http://site2.example/secure3\";" completionHandler:nil];
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(@"http://site2.example/secure3", [webView URL].absoluteString);
}
TEST(WKNavigation, HTTPSFirstWithHTTPRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { 302, {{ "Location"_s, "http://site.example/secure"_s }}, "redirecting..."_s } },
{ "/secure2"_s, { 302, {{ "Location"_s, "http://site2.example/secure3"_s }}, "redirecting..."_s } },
{ "/secure3"_s, { 302, {{ "Location"_s, "http://site2.example/secure2"_s }}, "redirecting..."_s } },
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure2"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure3"_s, { { }, "hi: not secure"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences._networkConnectionIntegrityPolicy = _WKWebsiteNetworkConnectionIntegrityPolicyHTTPSFirst;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NULL(error);
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_NULL([webView _safeBrowsingWarning]);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 3);
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 23);
EXPECT_WK_STREQ(@"http://site2.example/secure3", [webView URL].absoluteString);
}
TEST(WKNavigation, DISABLED_PreferredHTTPSPolicyAutomaticHTTPFallbackRedirectNo3SecondTimeout)
{
using namespace TestWebKitAPI;
WKURLRequestSetDefaultTimeoutInterval((60_s).value());
auto httpsServer = HTTPServer(HTTPServer::UseCoroutines::Yes, [&](Connection connection) -> ConnectionTask {
while (1) {
auto request = co_await connection.awaitableReceiveHTTPRequest();
auto path = HTTPServer::parsePath(request);
if (path == "/redirect"_s) {
co_await connection.awaitableSend(HTTPResponse(302, { { "Content-Type"_s, "text/html"_s }, { "Location"_s, "https://bar.com/finalTarget"_s } }, "redirecting..."_s).serialize());
continue;
}
if (path == "/finalTarget"_s) {
TestWebKitAPI::Util::runFor(4_s);
co_await connection.awaitableSend(HTTPResponse({ { "Content-Type"_s, "text/html"_s } }, "hello"_s).serialize());
continue;
}
EXPECT_FALSE(true);
}
}, HTTPServer::Protocol::HttpsProxy);
RetainPtr storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
RetainPtr dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
RetainPtr configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int loadCount = 0;
__block int errorCode = 0;
__block bool finished = false;
RetainPtr delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
loadCount++;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
finished = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finished = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://foo.com/redirect"]]];
Util::run(&finished);
EXPECT_EQ(errorCode, 0);
EXPECT_EQ(loadCount, 2);
}
TEST(WKNavigation, PreferredHTTPSPolicyAutomaticHTTPFallbackHTTPDowngrade)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { "Welcome"_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
__block bool doneEvaluatingJavaScript { false };
[webView evaluateJavaScript:@"window.location.protocol" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http:", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.toString()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
auto url = [NSURL URLWithString:(NSString *)value];
EXPECT_WK_STREQ(@"http", url.scheme);
EXPECT_WK_STREQ(@"http://site.example/secure", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.hasOwnProperty('crypto')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.crypto.hasOwnProperty('subtle')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_FALSE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyAutomaticHTTPFallbackHTTPDowngradeAfterPSON)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "body"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { "body"_s } },
{ "http://site2.example/secure"_s, { "body"_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block bool didFailLoad { false };
__block unsigned loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFailProvisionalLoadWithRequestInFrameWithError = ^(WKWebView *, NSURLRequest *, WKFrameInfo *, NSError *error) {
errorCode = error.code;
didFailLoad = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site2.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didFailNavigation);
EXPECT_FALSE(didFailLoad);
EXPECT_EQ(loadCount, 1u);
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
didFailLoad = false;
loadCount = 0;
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
__block bool doneEvaluatingJavaScript { false };
[webView evaluateJavaScript:@"window.location.protocol" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http:", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.toString()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
auto url = [NSURL URLWithString:(NSString *)value];
EXPECT_WK_STREQ(@"http", url.scheme);
EXPECT_WK_STREQ(@"http://site.example/secure", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.hasOwnProperty('crypto')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.crypto.hasOwnProperty('subtle')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_FALSE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didFailNavigation);
EXPECT_FALSE(didFailLoad);
EXPECT_EQ(loadCount, 2u);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyAutomaticHTTPFallbackHTTPDowngradeAndSameSiteNavigation)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "body"_s } },
{ "/secure2"_s, { { }, "body"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { "<body><a href=\"/secure2\">Link</a></body>"_s } },
{ "http://site.example/secure2"_s, { "<body><a href=\"http://site2.example/secure2\">Link</a></body>"_s } },
{ "http://site2.example/secure2"_s, { "<body>Done</body>"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block unsigned loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
// Load the site with http, it should be upgrade to https. That should fail, and it will fallback to http
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2u);
__block bool doneEvaluatingJavaScript { false };
[webView evaluateJavaScript:@"window.location.protocol" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http:", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.toString()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
auto url = [NSURL URLWithString:(NSString *)value];
EXPECT_WK_STREQ(@"http", url.scheme);
EXPECT_WK_STREQ(@"http://site.example/secure", value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.hasOwnProperty('crypto')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.crypto.hasOwnProperty('subtle')" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_FALSE([value boolValue]);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
finishedSuccessfully = false;
errorCode = 0;
loadCount = 0;
doneEvaluatingJavaScript = false;
// Clicking the link should be a same-site navigation, so we shouldn't attempt upgrading.
[webView evaluateJavaScript:@"document.querySelector(\"a\").click()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1u);
finishedSuccessfully = false;
errorCode = 0;
loadCount = 0;
doneEvaluatingJavaScript = false;
// Clicking the link should be a cross-site navigation, so we should attempt upgrading.
[webView evaluateJavaScript:@"document.querySelector(\"a\").click()" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2u);
EXPECT_WK_STREQ(@"http://site2.example/secure2", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyAutomaticHTTPFallbackHTTPDowngradeRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { 302, {{ "Location"_s, "https://site.example/secure"_s }}, "redirecting..."_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/secure"]]];
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 3);
}
TEST(WKNavigation, PreferredHTTPSPolicyAutomaticHTTPFallbackRedirectNoHTTPDowngradeRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/redirect"_s, { 302, {{ "Location"_s, "https://site2.example/page1"_s }}, "redirecting..."_s } },
{ "/page1"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/redirect"_s, { 302, {{ "Location"_s, "https://site.example"_s }}, "redirecting..."_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
if (!loadCount)
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
else
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
};
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/redirect"]]];
while (!errorCode)
TestWebKitAPI::Util::spinRunLoop(5);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
}
TEST(WKNavigation, PreferredHTTPSPolicyAutomaticHTTPFallbackLocalHostIPAddress)
{
using namespace TestWebKitAPI;
HTTPServer httpServer({
{ "/notsecure"_s, { { }, "not secure page"_s } },
}, HTTPServer::Protocol::Http);
auto configuration = adoptNS([WKWebViewConfiguration new]);
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
__block bool didReceiveAuthenticationChallenge { false };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
didReceiveAuthenticationChallenge = true;
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
auto url = makeString("http://localhost:"_s, httpServer.port(), "/notsecure"_s);
[webView loadRequest:adoptNS([[NSURLRequest alloc] initWithURL:adoptNS([[NSURL alloc] initWithString:url.createNSString().get()]).get()]).get()];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didReceiveAuthenticationChallenge);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(url, [webView URL].absoluteString);
errorCode = 0;
finishedSuccessfully = false;
didReceiveAuthenticationChallenge = false;
loadCount = 0;
url = makeString("http://127.0.0.1:"_s, httpServer.port(), "/notsecure"_s);
[webView loadRequest:adoptNS([[NSURLRequest alloc] initWithURL:adoptNS([[NSURL alloc] initWithString:url.createNSString().get()]).get()]).get()];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_FALSE(didReceiveAuthenticationChallenge);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(url, [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyUserMediatedHTTPFallbackInitialLoad)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "secure page"_s } },
{ "/notsecure"_s, { { }, "notsecure page upgraded to secure page"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/notsecure"_s, { { }, "not secure page"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyUserMediatedFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
__block bool didReceiveAuthenticationChallenge { false };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
didReceiveAuthenticationChallenge = true;
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/notsecure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_TRUE(didReceiveAuthenticationChallenge);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(@"https://site.example/notsecure", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyUserMediatedHTTPFallbackHTTPFallbackGoBack)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyUserMediatedFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
failedNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_WK_STREQ([webView title], "This Connection Is Not Secure");
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
Util::run(&failedNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1);
__block bool doneEvaluatingJavaScript { false };
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"about:blank", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyUserMediatedHTTPFallbackHTTPFallbackContinue)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "body"_s } },
{ "/page2"_s, { { }, "secure"_s } },
{ "/page3"_s, { { }, "secure"_s } },
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { { }, "not secure"_s } },
{ "http://site2.example/page2"_s, { 302, { { "Location"_s, "https://site.example/page2"_s } }, "not secure"_s } },
{ "http://site.example/page3"_s, { } },
{ "http://site.example/page2"_s, { } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyUserMediatedFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
failedNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_WK_STREQ([webView title], "This Connection Is Not Secure");
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[4], "Continue");
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 2);
__block bool doneEvaluatingJavaScript { false };
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/secure", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://site.example/secure", [webView URL].absoluteString);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView evaluateJavaScript:@"location = \"http://site.example/page3\"" completionHandler:nil];
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView evaluateJavaScript:@"location = \"http://site.example/page2\"" completionHandler:nil];
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView evaluateJavaScript:@"location = \"http://site.example/page3\"" completionHandler:nil];
Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site2.example/page2"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_EQ(loadCount, 1);
errorCode = 0;
finishedSuccessfully = false;
failedNavigation = false;
loadCount = 0;
EXPECT_WK_STREQ([webView title], "This Connection Is Not Secure");
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[4], "Continue");
Util::run(&failedNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
doneEvaluatingJavaScript = false;
[webView evaluateJavaScript:@"window.location.href" completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(@"http://site.example/page3", (NSString *)value);
doneEvaluatingJavaScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingJavaScript);
EXPECT_WK_STREQ(@"http://site.example/page3", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyUserMediatedHTTPFallbackWithHTTPRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { 302, {{ "Location"_s, "http://site.example/secure"_s }}, "redirecting..."_s } },
{ "/secure2"_s, { 302, {{ "Location"_s, "http://site2.example/secure3"_s }}, "redirecting..."_s } },
{ "/secure3"_s, { 302, {{ "Location"_s, "http://site2.example/secure2"_s }}, "redirecting..."_s } },
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure2"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure3"_s, { { }, "hi: not secure"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyUserMediatedFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
EXPECT_NOT_NULL(error.userInfo[NSURLErrorFailingURLErrorKey]);
EXPECT_WK_STREQ(@"https://site.example/secure", ((NSURL *)error.userInfo[NSURLErrorFailingURLErrorKey]).absoluteString);
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, _WKErrorCodeHTTPSUpgradeRedirectLoop);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
EXPECT_NOT_NULL(error.userInfo[NSURLErrorFailingURLErrorKey]);
EXPECT_WK_STREQ(@"https://site2.example/secure2", ((NSURL *)error.userInfo[NSURLErrorFailingURLErrorKey]).absoluteString);
errorCode = error.code;
didFailNavigation = true;
};
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, kCFURLErrorHTTPTooManyRedirects);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 21);
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyUserMediatedFallbackToHTTP;
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_NOT_NULL([webView _safeBrowsingWarning]);
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[4], "Continue");
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 22);
[webView evaluateJavaScript:@"location = \"http://site2.example/secure3\";" completionHandler:nil];
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(@"http://site2.example/secure3", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyAutomaticHTTPFallbackWithHTTPRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { 302, {{ "Location"_s, "http://site.example/secure"_s }}, "redirecting..."_s } },
{ "/secure2"_s, { 302, {{ "Location"_s, "http://site2.example/secure3"_s }}, "redirecting..."_s } },
{ "/secure3"_s, { 302, {{ "Location"_s, "http://site2.example/secure2"_s }}, "redirecting..."_s } },
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/secure"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure2"_s, { { }, "hi: not secure"_s } },
{ "http://site2.example/secure3"_s, { { }, "hi: not secure"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyAutomaticFallbackToHTTP;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NULL(error);
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure"]]];
EXPECT_NULL([webView _safeBrowsingWarning]);
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_NULL([webView _safeBrowsingWarning]);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 3);
errorCode = 0;
finishedSuccessfully = false;
didFailNavigation = false;
loadCount = 0;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/secure2"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 23);
EXPECT_WK_STREQ(@"http://site2.example/secure3", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyNoFallbackRedirectNoHTTPDowngradeRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/redirect"_s, { 302, {{ "Location"_s, "https://site2.example/page1"_s }}, "redirecting..."_s } },
{ "/page1"_s, { { }, "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/redirect"_s, { 302, {{ "Location"_s, "https://site.example"_s }}, "redirecting..."_s } }
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyErrorOnFailure;
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block int loadCount { 0 };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
if (!loadCount)
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
else
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
};
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
errorCode = error.code;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/redirect"]]];
while (!errorCode)
TestWebKitAPI::Util::spinRunLoop(5);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 2);
}
TEST(WKNavigation, PreferredHTTPSPolicyNoFallbackInitialLoad)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "secure page"_s } },
{ "/notsecure"_s, { { }, "notsecure page upgraded to secure page"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/notsecure"_s, { { }, "not secure page"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyErrorOnFailure;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
__block bool didReceiveAuthenticationChallenge { false };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
didReceiveAuthenticationChallenge = true;
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/notsecure"]]];
TestWebKitAPI::Util::run(&finishedSuccessfully);
EXPECT_EQ(errorCode, 0);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_TRUE(didReceiveAuthenticationChallenge);
EXPECT_FALSE(didFailNavigation);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(@"https://site.example/notsecure", [webView URL].absoluteString);
}
TEST(WKNavigation, PreferredHTTPSPolicyNoFallbackOnCertificateError)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/secure"_s, { { }, "secure page"_s } },
{ "/notsecure"_s, { { }, "notsecure page upgraded to secure page"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/notsecure"_s, { { }, "not secure page"_s } },
}, HTTPServer::Protocol::Http);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
configuration.get().defaultWebpagePreferences.preferredHTTPSNavigationPolicy = WKWebpagePreferencesUpgradeToHTTPSPolicyErrorOnFailure;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block int errorCode { 0 };
__block bool finishedSuccessfully { false };
__block bool didFailNavigation { false };
__block int loadCount { 0 };
__block bool didReceiveAuthenticationChallenge { false };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
++loadCount;
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
didReceiveAuthenticationChallenge = true;
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
};
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *error) {
EXPECT_NOT_NULL(error);
errorCode = error.code;
didFailNavigation = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/notsecure"]]];
TestWebKitAPI::Util::run(&didFailNavigation);
EXPECT_EQ(errorCode, NSURLErrorServerCertificateUntrusted);
EXPECT_TRUE(didFailNavigation);
EXPECT_TRUE(didReceiveAuthenticationChallenge);
EXPECT_FALSE(finishedSuccessfully);
EXPECT_EQ(loadCount, 1);
EXPECT_WK_STREQ(@"", [webView URL].absoluteString);
}
TEST(WKNavigation, LeakCheck)
{
using namespace TestWebKitAPI;
HTTPServer server({
{ "/example"_s, { "hi"_s } },
{ "/webkit"_s, { "hi"_s } }
}, HTTPServer::Protocol::HttpsProxy);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setHTTPSProxy:[NSURL URLWithString:[NSString stringWithFormat:@"https://127.0.0.1:%d/", server.port()]]];
[storeConfiguration setAllowsHSTSWithUntrustedRootCertificate:YES];
auto viewConfiguration = adoptNS([WKWebViewConfiguration new]);
[viewConfiguration setWebsiteDataStore:adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]).get()];
__block __weak WKNavigation *gLastNavigation = nil;
__block bool done = false;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
delegate.get().didStartProvisionalNavigation = ^(WKWebView *, WKNavigation *navigation) {
gLastNavigation = navigation;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *navigation) {
EXPECT_EQ(gLastNavigation, navigation);
done = true;
};
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:viewConfiguration.get()]);
[webView setNavigationDelegate:delegate.get()];
__weak WKNavigation *navigationToExample;
@autoreleasepool {
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]];
Util::run(&done);
navigationToExample = gLastNavigation;
auto examplePID = [webView _webProcessIdentifier];
done = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://webkit.org/webkit"]]];
Util::run(&done);
EXPECT_NE(examplePID, [webView _webProcessIdentifier]);
EXPECT_NE(gLastNavigation, navigationToExample);
[webView _clearBackForwardCache];
}
while (navigationToExample)
Util::spinRunLoop();
}
TEST(WKNavigation, Multiple303Redirects)
{
using namespace TestWebKitAPI;
HTTPServer httpServer({
{ "http://site.example/page1"_s, { 303, {{ "Location"_s, "http://site.example/page2"_s }}, "see other..."_s } },
{ "http://site.example/page2"_s, { 303, {{ "Location"_s, "http://site.example/page3"_s }}, "see other..."_s } },
{ "http://site.example/page3"_s, { "Done."_s } },
});
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
__block bool finishedSuccessfully { false };
__block bool reachedPage3 { false };
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^completionHandler)(WKNavigationActionPolicy)) {
if ([action.request.URL.path isEqual:@"/page1"]) {
EXPECT_WK_STREQ(action.request.URL.path, @"/page1");
EXPECT_WK_STREQ(action.request.HTTPMethod, @"POST");
EXPECT_WK_STREQ(action.request.allHTTPHeaderFields[@"Content-Type"], @"text/plain");
} else if ([action.request.URL.path isEqual:@"/page2"]) {
EXPECT_WK_STREQ(action.request.HTTPMethod, @"GET");
EXPECT_NULL(action.request.allHTTPHeaderFields[@"Content-Type"]);
} else if ([action.request.URL.path isEqual:@"/page3"]) {
EXPECT_WK_STREQ(action.request.HTTPMethod, @"GET");
EXPECT_NULL(action.request.allHTTPHeaderFields[@"Content-Type"]);
reachedPage3 = true;
} else
EXPECT_TRUE(0);
completionHandler(WKNavigationActionPolicyAllow);
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:delegate.get()];
auto request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://site.example/page1"]];
request.HTTPMethod = @"POST";
[request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"0" forHTTPHeaderField:@"Content-Length"];
[webView loadRequest:request];
while (!finishedSuccessfully)
TestWebKitAPI::Util::spinRunLoop(5);
EXPECT_TRUE(finishedSuccessfully);
EXPECT_TRUE(reachedPage3);
}
TEST(WKNavigation, NavigationToUnknownBlankURL)
{
auto viewConfiguration = adoptNS([WKWebViewConfiguration new]);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:viewConfiguration.get()]);
auto delegate = adoptNS([TestNavigationDelegate new]);
[webView setNavigationDelegate:delegate.get()];
__block bool navigationFailed = false;
delegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *navigation, NSError *error) {
navigationFailed = true;
done = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *navigation) {
done = true;
};
done = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:srcdoc"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank#foo"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank?foo=bar"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:srcdoc#foo"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:srcdoc?foo=bar"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:foo"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_TRUE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:google.com"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_TRUE(navigationFailed);
done = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about://newtab/"]]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE(navigationFailed);
}
#if PLATFORM(MAC)
TEST(WKNavigation, GeneratePageLoadTiming)
{
RetainPtr window = adoptNS([[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 400, 400) styleMask:0 backing:NSBackingStoreBuffered defer:NO]);
RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
auto delegate = adoptNS([TestNavigationDelegate new]);
[webView setNavigationDelegate:delegate.get()];
[window.get().contentView addSubview:webView.get()];
[window.get() makeKeyAndOrderFront:nil];
EXPECT_TRUE([window.get() isVisible]);
__block NSDate *beforeStart = [NSDate now];
__block bool done = false;
delegate.get().didGeneratePageLoadTiming = ^(_WKPageLoadTiming *timing) {
NSDate *afterEnd = [NSDate now];
EXPECT_EQ([beforeStart compare:timing.navigationStart], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.firstVisualLayout], NSOrderedAscending);
EXPECT_EQ([timing.firstVisualLayout compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.firstMeaningfulPaint], NSOrderedAscending);
EXPECT_EQ([timing.firstMeaningfulPaint compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.documentFinishedLoading], NSOrderedAscending);
EXPECT_EQ([timing.documentFinishedLoading compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.allSubresourcesFinishedLoading], NSOrderedAscending);
EXPECT_EQ([timing.allSubresourcesFinishedLoading compare:afterEnd], NSOrderedAscending);
done = true;
};
auto request = [NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"multiple-images" withExtension:@"html"]];
[webView loadRequest:request];
TestWebKitAPI::Util::run(&done);
}
TEST(WKNavigation, GeneratePageLoadTimingWithNoSubresources)
{
using namespace TestWebKitAPI;
HTTPServer server({
{ "/index.html"_s, { "Hello world!"_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setHTTPSProxy:[NSURL URLWithString:[NSString stringWithFormat:@"https://127.0.0.1:%d/", server.port()]]];
[storeConfiguration setAllowsHSTSWithUntrustedRootCertificate:YES];
auto viewConfiguration = adoptNS([WKWebViewConfiguration new]);
[viewConfiguration setWebsiteDataStore:adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]).get()];
RetainPtr window = adoptNS([[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 400, 400) styleMask:0 backing:NSBackingStoreBuffered defer:NO]);
RetainPtr webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400) configuration:viewConfiguration.get()]);
auto delegate = adoptNS([TestNavigationDelegate new]);
[webView setNavigationDelegate:delegate.get()];
[window.get().contentView addSubview:webView.get()];
[window.get() makeKeyAndOrderFront:nil];
EXPECT_TRUE([window.get() isVisible]);
__block NSDate *beforeStart = [NSDate now];
__block bool done = false;
delegate.get().didGeneratePageLoadTiming = ^(_WKPageLoadTiming *timing) {
NSDate *afterEnd = [NSDate now];
EXPECT_EQ([beforeStart compare:timing.navigationStart], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.firstVisualLayout], NSOrderedAscending);
EXPECT_EQ([timing.firstVisualLayout compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.firstMeaningfulPaint], NSOrderedAscending);
EXPECT_EQ([timing.firstMeaningfulPaint compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.documentFinishedLoading], NSOrderedAscending);
EXPECT_EQ([timing.documentFinishedLoading compare:afterEnd], NSOrderedAscending);
EXPECT_EQ([timing.navigationStart compare:timing.allSubresourcesFinishedLoading], NSOrderedAscending);
EXPECT_EQ([timing.allSubresourcesFinishedLoading compare:afterEnd], NSOrderedAscending);
done = true;
};
auto request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]];
[webView loadRequest:request];
TestWebKitAPI::Util::run(&done);
}
#endif // PLATFORM(MAC)
struct PrivateTokenTestSetupState {
RetainPtr<WKWebView> webView;
std::unique_ptr<TestWebKitAPI::HTTPServer> server;
RetainPtr<_WKWebsiteDataStoreConfiguration> storeConfiguration;
RetainPtr<WKWebsiteDataStore> dataStore;
RetainPtr<NavigationDelegate> websiteDataStoreDelegate;
RetainPtr<TestNavigationDelegate> navigationDelegate;
};
PrivateTokenTestSetupState setupWebViewForPrivateTokenTests(bool& didDecideServiceWorkerRequest, bool& finishedSuccessfully, bool& failedNavigation)
{
using namespace TestWebKitAPI;
static constexpr auto mainBytes =
"<script>"
"navigator.serviceWorker.register('/service-worker.js').then(function(reg) {"
" fetch('/path5').then(() => location = '/path4' );"
"});"
"</script>"_s;
static constexpr auto serviceWorkerBytes =
"self.addEventListener(\"fetch\", (event) => {"
" event.respondWith(fetch(new Request(event.request, { headers: { Source: \"ServiceWorker\" }})));"
"});"_s;
auto headerFromRequest = [](const Vector<char>& request, const ASCIILiteral& headerPrefix) {
StringView requestView(request.span());
auto headerStart = requestView.find(headerPrefix);
const auto headerLen = strlen(headerPrefix);
if (headerStart == notFound)
return ""_str;
auto headerValueStart = headerStart + headerLen;
auto headerEnd = requestView.find("\r\n"_s, headerValueStart);
if (headerEnd == notFound)
return ""_str;
return requestView.substring(headerValueStart, headerEnd - headerValueStart).toStringWithoutCopying();
};
auto server = makeUnique<HTTPServer>(HTTPServer::UseCoroutines::Yes, [&](Connection connection) -> ConnectionTask {
while (1) {
auto request = co_await connection.awaitableReceiveHTTPRequest();
auto path = HTTPServer::parsePath(request);
if (headerFromRequest(request, "Host: "_s) == "site5.example"_s && (path == "/path1"_s || path == "/path6"_s)) {
auto source = headerFromRequest(request, "Source: "_s);
EXPECT_WK_STREQ(source, "ServiceWorker"_s);
didDecideServiceWorkerRequest = true;
}
if (path == "/path1"_s || path == "/path5"_s || path == "/path6"_s) {
co_await connection.awaitableSend(HTTPResponse("<body>body</body>"_s).serialize());
continue;
}
if (path == "/path2"_s || path == "/path4"_s) {
co_await connection.awaitableSend(HTTPResponse("<script>fetch(\"/path1\").then(r => location = \"/path1\");</script>"_s).serialize());
continue;
}
if (path == "/path7"_s) {
co_await connection.awaitableSend(HTTPResponse("<script>fetch(\"/path6\").then(r => location = \"/path6\");</script>"_s).serialize());
continue;
}
if (path == "/path3"_s) {
co_await connection.awaitableSend(HTTPResponse(mainBytes).serialize());
continue;
}
if (path == "/service-worker.js"_s) {
co_await connection.awaitableSend(HTTPResponse({ { { "Content-Type"_s, "application/javascript"_s } }, serviceWorkerBytes }).serialize());
continue;
}
EXPECT_FALSE(true);
}
}, 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()]);
[dataStore _setPrivateTokenIPCForTesting:true];
RetainPtr<NavigationDelegate> websiteDataStoreDelegate = adoptNS([[NavigationDelegate alloc] init]);
dataStore.get()._delegate = websiteDataStoreDelegate.get();
auto configuration = adoptNS([WKWebViewConfiguration new]);
[configuration setWebsiteDataStore:dataStore.get()];
auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
navigationDelegate.get().didReceiveAuthenticationChallenge = ^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
};
navigationDelegate.get().decidePolicyForNavigationResponse = ^(WKNavigationResponse *response, void (^decisionHandler)(WKNavigationResponsePolicy)) {
decisionHandler(WKNavigationResponsePolicyAllow);
};
navigationDelegate.get().didFailProvisionalNavigation = ^(WKWebView *, WKNavigation *, NSError *) {
failedNavigation = true;
};
navigationDelegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *) {
finishedSuccessfully = true;
};
[webView setNavigationDelegate:navigationDelegate.get()];
return { webView, WTF::move(server), storeConfiguration, dataStore, websiteDataStoreDelegate, navigationDelegate };
}
#if HAVE(ALLOW_PRIVATE_ACCESS_TOKENS_FOR_THIRD_PARTY)
TEST(WKNavigation, FrameNavigationWithPrivateTokenPermission)
{
using namespace TestWebKitAPI;
bool didDecideServiceWorkerRequest { false };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block bool doneEvaluatingJavaScript { false };
didReceiveAllowPrivateToken = false;
auto [webView, server, storeConfiguration, dataStore, websiteDataStoreDelegate, navigationDelegate] = setupWebViewForPrivateTokenTests(didDecideServiceWorkerRequest, finishedSuccessfully, failedNavigation);
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site1.example/path1"]]];
Util::run(&finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
const auto iframeWithoutAllowedPrivateToken = @"iframe = document.createElement(\"iframe\"); iframe.src = \"https://site2.example/path2\"; document.body.appendChild(iframe); true";
[webView evaluateJavaScript:iframeWithoutAllowedPrivateToken completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_WK_STREQ(error.description, @"");
doneEvaluatingJavaScript = true;
}];
Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of fetch request initiated by site2.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
didReceiveAllowPrivateToken = false;
// iframe with allowslist="src"
const auto iframeWithAllowedPrivateToken = @"iframe = document.createElement(\"iframe\"); iframe.src = \"https://site3.example/path2\"; iframe.allow = \"private-token\"; document.body.appendChild(iframe); true";
[webView evaluateJavaScript:iframeWithAllowedPrivateToken completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_WK_STREQ(error.description, @"");
doneEvaluatingJavaScript = true;
}];
Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of fetch request initiated by site3.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
didReceiveAllowPrivateToken = false;
}
TEST(WKNavigation, FrameNavigationWithPrivateTokenPermissionAndAllowSrc)
{
using namespace TestWebKitAPI;
bool didDecideServiceWorkerRequest { false };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block bool doneEvaluatingJavaScript { false };
didReceiveAllowPrivateToken = false;
auto [webView, server, storeConfiguration, dataStore, websiteDataStoreDelegate, navigationDelegate] = setupWebViewForPrivateTokenTests(didDecideServiceWorkerRequest, finishedSuccessfully, failedNavigation);
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site1.example/path1"]]];
Util::run(&finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// iframe with allowslist="src"
const auto iframeWithAllowedPrivateToken = @"iframe = document.createElement(\"iframe\"); iframe.src = \"https://site3.example/path2\"; iframe.allow = \"private-token\"; document.body.appendChild(iframe); true";
[webView evaluateJavaScript:iframeWithAllowedPrivateToken completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_WK_STREQ(error.description, @"");
doneEvaluatingJavaScript = true;
}];
Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of fetch request initiated by site3.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
didReceiveAllowPrivateToken = false;
}
TEST(WKNavigation, FrameNavigationWithPrivateTokenPermissionAndAllowOrigins)
{
using namespace TestWebKitAPI;
bool didDecideServiceWorkerRequest { false };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block bool doneEvaluatingJavaScript { false };
didReceiveAllowPrivateToken = false;
auto [webView, server, storeConfiguration, dataStore, websiteDataStoreDelegate, navigationDelegate] = setupWebViewForPrivateTokenTests(didDecideServiceWorkerRequest, finishedSuccessfully, failedNavigation);
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site1.example/path1"]]];
Util::run(&finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// iframe with allowslist="https://site1.example https://site4.example"
const auto iframeWithAllowedOriginsPrivateToken = @"iframe = document.createElement(\"iframe\"); iframe.src = \"https://site4.example/path2\"; iframe.allow = \"private-token https://site1.example https://site4.example\"; document.body.appendChild(iframe); true";
[webView evaluateJavaScript:iframeWithAllowedOriginsPrivateToken completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_WK_STREQ(error.description, @"");
doneEvaluatingJavaScript = true;
}];
Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of fetch request initiated by site4.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
didReceiveAllowPrivateToken = false;
}
TEST(WKNavigation, FrameNavigationWithPrivateTokenPermissionAndAllowOriginsAndWithServiceWorker)
{
using namespace TestWebKitAPI;
bool didDecideServiceWorkerRequest { false };
__block bool finishedSuccessfully { false };
__block bool failedNavigation { false };
__block bool doneEvaluatingJavaScript { false };
didReceiveAllowPrivateToken = false;
auto [webView, server, storeConfiguration, dataStore, websiteDataStoreDelegate, navigationDelegate] = setupWebViewForPrivateTokenTests(didDecideServiceWorkerRequest, finishedSuccessfully, failedNavigation);
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site1.example/path1"]]];
Util::run(&finishedSuccessfully);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// iframe with allowslist="https://site1.example https://site5.example"
const auto iframeWithServiceWorkerAndAllowedPrivateToken = @"iframe = document.createElement(\"iframe\"); iframe.src = \"https://site5.example/path3\"; iframe.allow = \"private-token https://site1.example https://site5.example\"; document.body.appendChild(iframe); true";
[webView evaluateJavaScript:iframeWithServiceWorkerAndAllowedPrivateToken completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_WK_STREQ(error.description, @"");
doneEvaluatingJavaScript = true;
}];
Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(failedNavigation);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of service worker registration initiated by site5.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of fetch from service worker registration
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of redirect after registering service worker by site5.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of fetch request initiated by site5.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didReceiveAllowPrivateToken = false;
// Result of fetch request initiated by service worker
Util::run(&didReceiveAllowPrivateToken);
EXPECT_TRUE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// iframe without private-token permission and interacting with service worker
const auto iframeWithoutAllowedOriginsPrivateToken = @"iframe = document.createElement(\"iframe\"); iframe.src = \"https://site5.example/path7\"; document.body.appendChild(iframe); true";
[webView evaluateJavaScript:iframeWithoutAllowedOriginsPrivateToken completionHandler:^(id value, NSError *error) {
EXPECT_NULL(error);
EXPECT_WK_STREQ(error.description, @"");
doneEvaluatingJavaScript = true;
}];
Util::run(&doneEvaluatingJavaScript);
doneEvaluatingJavaScript = false;
// Result of loading /path7 iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_TRUE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
// Result of /path6 fetch request initiated by site5.example iframe
Util::run(&didReceiveAllowPrivateToken);
EXPECT_FALSE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didReceiveAllowPrivateToken = false;
didDecideServiceWorkerRequest = false;
// Result of /path6 fetch request initiated by service worker
Util::run(&didReceiveAllowPrivateToken);
EXPECT_TRUE(didDecideServiceWorkerRequest);
EXPECT_TRUE(didReceiveAllowPrivateToken);
finishedSuccessfully = false;
failedNavigation = false;
didDecideServiceWorkerRequest = false;
didReceiveAllowPrivateToken = false;
}
#endif
@interface NSURLSessionTask ()
@property (nonatomic, copy) NSArray<NSHTTPCookie*>* (^_cookieTransformCallback)(NSArray<NSHTTPCookie*>* cookies);
@end
@interface NSURLSessionConfiguration ()
@property BOOL _usesNWLoader;
@end
@interface SessionDelegate : NSObject <NSURLSessionDelegate>
@end
@implementation SessionDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
@end
TEST(Navigation, CookieTransformOnRedirect)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/page1"_s, { 302, {{ "Location"_s, "https://site.example/page2"_s }, { "Set-Cookie"_s, "Test_Redirect=1;"_s }}, "redirecting..."_s } },
{ "/page2"_s, { 200, {{ "Set-Cookie"_s, "Test=1;"_s }}, "body"_s } }
}, HTTPServer::Protocol::HttpsProxy);
HTTPServer httpServer({
{ "http://site.example/page1"_s, { 302, {{ "Location"_s, "http://site.example/page2"_s }, { "Set-Cookie"_s, "Test_Redirect=1;"_s }}, "redirecting..."_s } },
{ "http://site.example/page2"_s, { 200, {{ "Set-Cookie"_s, "Test=1;"_s }}, "body"_s } },
{ "http://site.example/page3"_s, { 302, {{ "Location"_s, "https://site.example/page2"_s }, { "Set-Cookie"_s, "Test_Redirect=1;"_s }}, "redirecting..."_s } }
}, HTTPServer::Protocol::Http);
// Partially copied from SessionSet::initializeEphemeralStatelessSessionIfNeeded.
RetainPtr configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
configuration.get().HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyAlways;
configuration.get().URLCredentialStorage = nil;
configuration.get().URLCache = nil;
configuration.get().connectionProxyDictionary = @{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpServer.port()),
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
};
configuration.get()._usesNWLoader = YES;
RetainPtr delegate = adoptNS([SessionDelegate new]);
RetainPtr session = [NSURLSession sessionWithConfiguration:configuration.get() delegate:delegate.get() delegateQueue:[NSOperationQueue mainQueue]];
__block unsigned transformCallbackCount = 0;
__block bool done = false;
RetainPtr task = [session dataTaskWithURL:[NSURL URLWithString:@"https://site.example/page1"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(data);
EXPECT_NOT_NULL(response);
done = true;
}];
task.get()._cookieTransformCallback = ^(NSArray<NSHTTPCookie*> *cookiesSetInResponse) {
EXPECT_EQ(cookiesSetInResponse.count, 1u);
if (!transformCallbackCount)
EXPECT_WK_STREQ(cookiesSetInResponse[0].name, @"Test_Redirect");
else
EXPECT_WK_STREQ(cookiesSetInResponse[0].name, @"Test");
transformCallbackCount++;
return cookiesSetInResponse;
};
[task resume];
TestWebKitAPI::Util::run(&done);
EXPECT_EQ(transformCallbackCount, 2u);
done = false;
transformCallbackCount = 0;
task = [session dataTaskWithURL:[NSURL URLWithString:@"http://site.example/page1"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(data);
EXPECT_NOT_NULL(response);
done = true;
}];
task.get()._cookieTransformCallback = ^(NSArray<NSHTTPCookie*> *cookiesSetInResponse) {
EXPECT_EQ(cookiesSetInResponse.count, 1u);
if (!transformCallbackCount)
EXPECT_WK_STREQ(cookiesSetInResponse[0].name, @"Test_Redirect");
else
EXPECT_WK_STREQ(cookiesSetInResponse[0].name, @"Test");
transformCallbackCount++;
return cookiesSetInResponse;
};
[task resume];
TestWebKitAPI::Util::run(&done);
EXPECT_EQ(transformCallbackCount, 2u);
done = false;
transformCallbackCount = 0;
task = [session dataTaskWithURL:[NSURL URLWithString:@"http://site.example/page3"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
EXPECT_NULL(error);
EXPECT_NOT_NULL(data);
EXPECT_NOT_NULL(response);
done = true;
}];
task.get()._cookieTransformCallback = ^(NSArray<NSHTTPCookie*> *cookiesSetInResponse) {
EXPECT_EQ(cookiesSetInResponse.count, 1u);
if (!transformCallbackCount)
EXPECT_WK_STREQ(cookiesSetInResponse[0].name, @"Test_Redirect");
else
EXPECT_WK_STREQ(cookiesSetInResponse[0].name, @"Test");
transformCallbackCount++;
return cookiesSetInResponse;
};
[task resume];
TestWebKitAPI::Util::run(&done);
EXPECT_EQ(transformCallbackCount, 2u);
}
TEST(WKNavigation, AllowResourceLoadFromBlockedPortWithCustomScheme)
{
using namespace TestWebKitAPI;
HTTPServer httpsServer({
{ "/page1"_s, { 302, {{ "Location"_s, "custom://site.example:0/"_s } }, "redirecting..."_s } },
}, HTTPServer::Protocol::HttpsProxy);
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto handler = adoptNS([TestURLSchemeHandler new]);
__block unsigned requestsSchemeHandled = 0;
[handler setStartURLSchemeTaskHandler:^(WKWebView *, id<WKURLSchemeTask> task) {
NSString *responseString = nil;
if ([task.request.URL.absoluteString isEqualToString:@"custom://site.example:0/"])
responseString = @"<script src='custom://site.example:0/script.js'></script>";
else if ([task.request.URL.absoluteString isEqualToString:@"custom://site.example:0/script.js"])
responseString = @"alert('This resource was loaded');";
ASSERT(responseString);
requestsSchemeHandled++;
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:@"custom"];
auto storeConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]);
[storeConfiguration setProxyConfiguration:@{
(NSString *)kCFStreamPropertyHTTPSProxyHost: @"127.0.0.1",
(NSString *)kCFStreamPropertyHTTPSProxyPort: @(httpsServer.port())
}];
auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:storeConfiguration.get()]);
[configuration setWebsiteDataStore:dataStore.get()];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
auto navDelegate = adoptNS([TestNavigationDelegate new]);
[navDelegate allowAnyTLSCertificate];
[webView setNavigationDelegate:navDelegate.get()];
auto uiDelegate = adoptNS([TestUIDelegate new]);
[webView setUIDelegate:uiDelegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://site.example/page1"]]];
EXPECT_WK_STREQ([uiDelegate waitForAlert], "This resource was loaded");
EXPECT_EQ(requestsSchemeHandled, 2u);
}