blob: bb5c8725c555287022d55a9cc4d54996a8be4024 [file] [log] [blame]
/*
* Copyright (C) 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 "HTTPServer.h"
#import "PlatformUtilities.h"
#import "TestNavigationDelegate.h"
#import "TestUIDelegate.h"
#import "TestWKWebView.h"
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WebKit.h>
#import <WebKit/_WKFrameHandle.h>
#import <WebKit/_WKResourceLoadDelegate.h>
#import <WebKit/_WKResourceLoadInfo.h>
#import <wtf/RetainPtr.h>
@interface TestResourceLoadDelegate : NSObject <_WKResourceLoadDelegate>
@property (nonatomic, copy) void (^didSendRequest)(WKWebView *, _WKResourceLoadInfo *, NSURLRequest *);
@property (nonatomic, copy) void (^didPerformHTTPRedirection)(WKWebView *, _WKResourceLoadInfo *, NSURLResponse *, NSURLRequest *);
@property (nonatomic, copy) void (^didReceiveChallenge)(WKWebView *, _WKResourceLoadInfo *, NSURLAuthenticationChallenge *);
@property (nonatomic, copy) void (^didReceiveResponse)(WKWebView *, _WKResourceLoadInfo *, NSURLResponse *);
@property (nonatomic, copy) void (^didCompleteWithError)(WKWebView *, _WKResourceLoadInfo *, NSError *, NSURLResponse *);
@end
@implementation TestResourceLoadDelegate
- (void)webView:(WKWebView *)webView resourceLoad:(_WKResourceLoadInfo *)resourceLoad didSendRequest:(NSURLRequest *)request
{
if (_didSendRequest)
_didSendRequest(webView, resourceLoad, request);
}
- (void)webView:(WKWebView *)webView resourceLoad:(_WKResourceLoadInfo *)resourceLoad didPerformHTTPRedirection:(NSURLResponse *)response newRequest:(NSURLRequest *)request
{
if (_didPerformHTTPRedirection)
_didPerformHTTPRedirection(webView, resourceLoad, response, request);
}
- (void)webView:(WKWebView *)webView resourceLoad:(_WKResourceLoadInfo *)resourceLoad didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
{
if (_didReceiveChallenge)
_didReceiveChallenge(webView, resourceLoad, challenge);
}
- (void)webView:(WKWebView *)webView resourceLoad:(_WKResourceLoadInfo *)resourceLoad didReceiveResponse:(NSURLResponse *)response
{
if (_didReceiveResponse)
_didReceiveResponse(webView, resourceLoad, response);
}
- (void)webView:(WKWebView *)webView resourceLoad:(_WKResourceLoadInfo *)resourceLoad didCompleteWithError:(NSError *)error response:(NSURLResponse *)response
{
if (_didCompleteWithError)
_didCompleteWithError(webView, resourceLoad, error, response);
}
@end
TEST(ResourceLoadDelegate, Basic)
{
auto webView = adoptNS([WKWebView new]);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[webView setNavigationDelegate:navigationDelegate.get()];
__block bool done = false;
[navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
done = true;
}];
__block RetainPtr<NSURLRequest> requestFromDelegate;
auto resourceLoadDelegate = adoptNS([TestResourceLoadDelegate new]);
[webView _setResourceLoadDelegate:resourceLoadDelegate.get()];
[resourceLoadDelegate setDidSendRequest:^(WKWebView *, _WKResourceLoadInfo *, NSURLRequest *request) {
requestFromDelegate = request;
}];
RetainPtr<NSURLRequest> requestLoaded = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[webView loadRequest:requestLoaded.get()];
TestWebKitAPI::Util::run(&done);
EXPECT_WK_STREQ(requestLoaded.get().URL.absoluteString, requestFromDelegate.get().URL.absoluteString);
}
TEST(ResourceLoadDelegate, BeaconAndSyncXHR)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { "hello"_s } },
{ "/xhrTarget"_s, { {{ "Content-Type"_s, "text/html"_s }}, "hi"_s } },
{ "/beaconTarget"_s, { "hi"_s } },
});
auto webView = adoptNS([TestWKWebView new]);
[webView synchronouslyLoadRequest:server.request()];
__block RetainPtr<NSURLRequest> requestFromDelegate;
__block bool receivedCallback = false;
auto resourceLoadDelegate = adoptNS([TestResourceLoadDelegate new]);
[webView _setResourceLoadDelegate:resourceLoadDelegate.get()];
[resourceLoadDelegate setDidSendRequest:^(WKWebView *, _WKResourceLoadInfo *info, NSURLRequest *request) {
requestFromDelegate = request;
receivedCallback = true;
EXPECT_TRUE(!!info.frame);
EXPECT_FALSE(!!info.parentFrame);
}];
__block bool receivedAlert = false;
auto uiDelegate = adoptNS([TestUIDelegate new]);
[webView setUIDelegate:uiDelegate.get()];
[uiDelegate setRunJavaScriptAlertPanelWithMessage:^(WKWebView *, NSString *, WKFrameInfo *, void (^completionHandler)(void)) {
receivedAlert = true;
completionHandler();
}];
[webView evaluateJavaScript:@"navigator.sendBeacon('/beaconTarget')" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedCallback);
EXPECT_WK_STREQ("/beaconTarget", requestFromDelegate.get().URL.path);
receivedCallback = false;
[webView evaluateJavaScript:
@"var request = new XMLHttpRequest();"
"var asynchronous = false;"
"request.open('GET', 'xhrTarget', asynchronous);"
"request.send();"
"alert('done');" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedCallback);
EXPECT_WK_STREQ("/xhrTarget", requestFromDelegate.get().URL.path);
TestWebKitAPI::Util::run(&receivedAlert);
}
TEST(ResourceLoadDelegate, Redirect)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { 301, {{ "Location"_s, "/redirectTarget"_s }} } },
{ "/redirectTarget"_s, { "hi"_s } },
});
__block bool done = false;
auto resourceLoadDelegate = adoptNS([TestResourceLoadDelegate new]);
[resourceLoadDelegate setDidPerformHTTPRedirection:^(WKWebView *, _WKResourceLoadInfo *loadInfo, NSURLResponse *response, NSURLRequest *request) {
EXPECT_WK_STREQ(response.URL.path, "/");
EXPECT_WK_STREQ(request.URL.path, "/redirectTarget");
EXPECT_WK_STREQ(loadInfo.originalURL.path, "/");
EXPECT_WK_STREQ(loadInfo.originalHTTPMethod, "GET");
}];
[resourceLoadDelegate setDidCompleteWithError:^(WKWebView *, _WKResourceLoadInfo *loadInfo, NSError *, NSURLResponse *) {
EXPECT_WK_STREQ(loadInfo.originalURL.path, "/");
EXPECT_WK_STREQ(loadInfo.originalHTTPMethod, "GET");
done = true;
}];
auto webView = adoptNS([WKWebView new]);
[webView _setResourceLoadDelegate:resourceLoadDelegate.get()];
[webView loadRequest:server.request()];
TestWebKitAPI::Util::run(&done);
}
TEST(ResourceLoadDelegate, ResourceType)
{
constexpr auto testJS = R"TESTJS(
function loadMoreThingsAfterFetchAndXHR() {
navigator.sendBeacon('beaconTarget');
var img = document.createElement('img');
img.src = 'imageSource';
document.body.appendChild(img);
var style = document.createElement('link');
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = 'styleSource';
document.head.appendChild(style);
}
fetch('fetchTarget', { body: 'a=b&c=d', method: 'post'}).then(()=>{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState == 4)
loadMoreThingsAfterFetchAndXHR();
};
xhr.open('GET', 'xhrTarget');
xhr.send();
})
)TESTJS"_s;
TestWebKitAPI::HTTPServer server({
{ "/"_s, { "<script src='scriptSrc'></script><div>text needing a font</div>"_s } },
{ "/scriptSrc"_s, { {{ "Content-Type"_s, "application/javascript"_s }}, testJS } },
{ "/fetchTarget"_s, { "hi"_s } },
{ "/xhrTarget"_s, { {{ "Content-Type"_s, "application/octet-stream"_s }}, "hi"_s } },
{ "/beaconTarget"_s, { "hi"_s } },
{ "/imageSource"_s, { "not really an image"_s } },
{ "/styleSource"_s, { "@font-face { font-family: TestFontFamily; src: url(fontSource) } div { font-family: TestFontFamily }"_s } },
{ "/fontSource"_s, { "not really a font"_s } },
});
__block Vector<RetainPtr<_WKResourceLoadInfo>> loadInfos;
__block size_t requestCount = 0;
auto delegate = adoptNS([TestResourceLoadDelegate new]);
[delegate setDidSendRequest:^(WKWebView *webView, _WKResourceLoadInfo *loadInfo, NSURLRequest *request) {
loadInfos.append(loadInfo);
requestCount++;
}];
auto webView = adoptNS([WKWebView new]);
[webView _setResourceLoadDelegate:delegate.get()];
[webView loadRequest:server.request()];
Vector<_WKResourceLoadInfoResourceType> expectedTypes {
_WKResourceLoadInfoResourceTypeDocument,
_WKResourceLoadInfoResourceTypeScript,
_WKResourceLoadInfoResourceTypeFetch,
_WKResourceLoadInfoResourceTypeXMLHTTPRequest,
_WKResourceLoadInfoResourceTypeBeacon,
_WKResourceLoadInfoResourceTypeImage,
_WKResourceLoadInfoResourceTypeStylesheet,
_WKResourceLoadInfoResourceTypeFont,
};
while (requestCount < expectedTypes.size())
TestWebKitAPI::Util::spinRunLoop();
for (size_t i = 0; i < expectedTypes.size(); ++i)
EXPECT_EQ(loadInfos[i].get().resourceType, expectedTypes[i]);
}
TEST(ResourceLoadDelegate, LoadInfo)
{
__block bool clearedStore = false;
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
clearedStore = true;
}];
TestWebKitAPI::Util::run(&clearedStore);
TestWebKitAPI::HTTPServer server({
{ "/"_s, { "<iframe src='iframeSrc'></iframe>"_s } },
{ "/iframeSrc"_s, { "<script>fetch('fetchTarget', { body: 'a=b&c=d', method: 'post'})</script>"_s } },
{ "/fetchTarget"_s, { "hi"_s } },
});
enum class Callback {
DidSendRequest,
DidReceiveResponse,
DidCompleteWithError,
};
__block Vector<Callback> callbacks;
__block Vector<RetainPtr<WKWebView>> webViews;
__block Vector<RetainPtr<_WKResourceLoadInfo>> loadInfos;
__block Vector<RetainPtr<id>> otherParameters;
__block size_t resourceCompletionCount = 0;
auto delegate = adoptNS([TestResourceLoadDelegate new]);
[delegate setDidSendRequest:^(WKWebView *webView, _WKResourceLoadInfo *loadInfo, NSURLRequest *request) {
callbacks.append(Callback::DidSendRequest);
webViews.append(webView);
loadInfos.append(loadInfo);
otherParameters.append(request);
}];
[delegate setDidReceiveResponse:^(WKWebView *webView, _WKResourceLoadInfo *loadInfo, NSURLResponse *response) {
callbacks.append(Callback::DidReceiveResponse);
webViews.append(webView);
loadInfos.append(loadInfo);
otherParameters.append(response);
}];
[delegate setDidCompleteWithError:^(WKWebView *webView, _WKResourceLoadInfo *loadInfo, NSError *error, NSURLResponse *response) {
callbacks.append(Callback::DidCompleteWithError);
webViews.append(webView);
loadInfos.append(loadInfo);
otherParameters.append(error);
otherParameters.append(response);
resourceCompletionCount++;
}];
auto webView = adoptNS([WKWebView new]);
[webView _setResourceLoadDelegate:delegate.get()];
[webView loadRequest:server.request()];
while (resourceCompletionCount < 3)
TestWebKitAPI::Util::spinRunLoop();
Vector<Callback> expectedCallbacks {
Callback::DidSendRequest,
Callback::DidReceiveResponse,
Callback::DidCompleteWithError,
Callback::DidSendRequest,
Callback::DidReceiveResponse,
Callback::DidCompleteWithError,
Callback::DidSendRequest,
Callback::DidReceiveResponse,
Callback::DidCompleteWithError
};
EXPECT_EQ(callbacks, expectedCallbacks);
EXPECT_EQ(webViews.size(), 9ull);
for (auto& view : webViews)
EXPECT_EQ(webView.get(), view.get());
EXPECT_EQ(loadInfos.size(), 9ull);
EXPECT_EQ(loadInfos[0].get().resourceLoadID, loadInfos[1].get().resourceLoadID);
EXPECT_EQ(loadInfos[0].get().resourceLoadID, loadInfos[2].get().resourceLoadID);
EXPECT_NE(loadInfos[0].get().resourceLoadID, loadInfos[3].get().resourceLoadID);
EXPECT_EQ(loadInfos[3].get().resourceLoadID, loadInfos[4].get().resourceLoadID);
EXPECT_EQ(loadInfos[3].get().resourceLoadID, loadInfos[5].get().resourceLoadID);
EXPECT_NE(loadInfos[3].get().resourceLoadID, loadInfos[6].get().resourceLoadID);
EXPECT_EQ(loadInfos[6].get().resourceLoadID, loadInfos[7].get().resourceLoadID);
EXPECT_EQ(loadInfos[6].get().resourceLoadID, loadInfos[8].get().resourceLoadID);
EXPECT_NE(loadInfos[6].get().resourceLoadID, loadInfos[0].get().resourceLoadID);
auto checkFrames = ^(size_t index, _WKFrameHandle *expectedFrame, _WKFrameHandle *expectedParent, _WKResourceLoadInfoResourceType expectedType) {
_WKResourceLoadInfo *info = loadInfos[index].get();
EXPECT_EQ(!!info.frame, !!expectedFrame);
EXPECT_EQ(!!info.parentFrame, !!expectedParent);
EXPECT_EQ(info.frame.frameID, expectedFrame.frameID);
EXPECT_EQ(info.parentFrame.frameID, expectedParent.frameID);
EXPECT_EQ(info.resourceType, expectedType);
};
_WKFrameHandle *main = loadInfos[0].get().frame;
_WKFrameHandle *sub = loadInfos[8].get().frame;
EXPECT_TRUE(!!main);
EXPECT_TRUE(!!sub);
EXPECT_TRUE(main.frameID != sub.frameID);
checkFrames(0, main, nil, _WKResourceLoadInfoResourceTypeDocument);
checkFrames(1, main, nil, _WKResourceLoadInfoResourceTypeDocument);
checkFrames(2, main, nil, _WKResourceLoadInfoResourceTypeDocument);
checkFrames(3, sub, main, _WKResourceLoadInfoResourceTypeDocument);
checkFrames(4, sub, main, _WKResourceLoadInfoResourceTypeDocument);
checkFrames(5, sub, main, _WKResourceLoadInfoResourceTypeDocument);
checkFrames(6, sub, main, _WKResourceLoadInfoResourceTypeFetch);
checkFrames(7, sub, main, _WKResourceLoadInfoResourceTypeFetch);
checkFrames(8, sub, main, _WKResourceLoadInfoResourceTypeFetch);
EXPECT_EQ(otherParameters.size(), 12ull);
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[0] class]), "NSMutableURLRequest");
EXPECT_WK_STREQ([otherParameters[0] URL].path, "/");
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[1] class]), "NSHTTPURLResponse");
EXPECT_WK_STREQ([otherParameters[1] URL].path, "/");
EXPECT_EQ(otherParameters[2], nil);
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[3] class]), "NSHTTPURLResponse");
EXPECT_WK_STREQ([otherParameters[3] URL].path, "/");
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[4] class]), "NSMutableURLRequest");
EXPECT_WK_STREQ([otherParameters[4] URL].path, "/iframeSrc");
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[5] class]), "NSHTTPURLResponse");
EXPECT_WK_STREQ([otherParameters[5] URL].path, "/iframeSrc");
EXPECT_EQ(otherParameters[6], nil);
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[7] class]), "NSHTTPURLResponse");
EXPECT_WK_STREQ([otherParameters[7] URL].path, "/iframeSrc");
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[8] class]), "NSMutableURLRequest");
EXPECT_WK_STREQ([otherParameters[8] URL].path, "/fetchTarget");
EXPECT_WK_STREQ(adoptNS([[NSString alloc] initWithData:[otherParameters[8] HTTPBody] encoding:NSUTF8StringEncoding]).get(), "a=b&c=d");
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[9] class]), "NSHTTPURLResponse");
EXPECT_WK_STREQ([otherParameters[9] URL].path, "/fetchTarget");
EXPECT_EQ(otherParameters[10], nil);
EXPECT_WK_STREQ(NSStringFromClass([otherParameters[11] class]), "NSHTTPURLResponse");
EXPECT_WK_STREQ([otherParameters[11] URL].path, "/fetchTarget");
_WKResourceLoadInfo *original = loadInfos[0].get();
NSError *error = nil;
NSData *archiveData = [NSKeyedArchiver archivedDataWithRootObject:original requiringSecureCoding:YES error:&error];
EXPECT_FALSE(error);
_WKResourceLoadInfo *deserialized = [NSKeyedUnarchiver unarchivedObjectOfClass:[_WKResourceLoadInfo class] fromData:archiveData error:&error];
EXPECT_FALSE(error);
EXPECT_TRUE(deserialized.resourceLoadID == original.resourceLoadID);
EXPECT_TRUE(deserialized.frame.frameID == original.frame.frameID);
EXPECT_TRUE(deserialized.parentFrame.frameID == original.parentFrame.frameID);
EXPECT_WK_STREQ(deserialized.originalURL.absoluteString, original.originalURL.absoluteString);
EXPECT_WK_STREQ(deserialized.originalHTTPMethod, original.originalHTTPMethod);
EXPECT_EQ(deserialized.eventTimestamp.timeIntervalSince1970, original.eventTimestamp.timeIntervalSince1970);
}
// FIXME: Add a test for loadedFromCache.
TEST(ResourceLoadDelegate, Challenge)
{
using namespace TestWebKitAPI;
HTTPServer server(HTTPServer::respondWithChallengeThenOK);
auto navigationDelegate = adoptNS([TestNavigationDelegate new]);
[navigationDelegate setDidReceiveAuthenticationChallenge:^(WKWebView *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
EXPECT_WK_STREQ(challenge.protectionSpace.authenticationMethod, NSURLAuthenticationMethodHTTPBasic);
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialWithUser:@"testuser" password:@"testpassword" persistence:NSURLCredentialPersistenceNone]);
}];
__block bool receivedErrorNotification = false;
__block bool receivedChallengeNotificiation = false;
auto resourceLoadDelegate = adoptNS([TestResourceLoadDelegate new]);
[resourceLoadDelegate setDidReceiveChallenge:^(WKWebView *, _WKResourceLoadInfo *, NSURLAuthenticationChallenge *challenge) {
EXPECT_WK_STREQ(challenge.protectionSpace.authenticationMethod, NSURLAuthenticationMethodHTTPBasic);
receivedChallengeNotificiation = true;
}];
[resourceLoadDelegate setDidCompleteWithError:^(WKWebView *, _WKResourceLoadInfo *, NSError *error, NSURLResponse *) {
EXPECT_FALSE(error);
receivedErrorNotification = true;
}];
auto webView = adoptNS([WKWebView new]);
[webView setNavigationDelegate:navigationDelegate.get()];
[webView _setResourceLoadDelegate:resourceLoadDelegate.get()];
[webView loadRequest:server.request()];
TestWebKitAPI::Util::run(&receivedErrorNotification);
EXPECT_TRUE(receivedChallengeNotificiation);
}