/* * 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 #import #import #import #import #import @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 requestFromDelegate; auto resourceLoadDelegate = adoptNS([TestResourceLoadDelegate new]); [webView _setResourceLoadDelegate:resourceLoadDelegate.get()]; [resourceLoadDelegate setDidSendRequest:^(WKWebView *, _WKResourceLoadInfo *, NSURLRequest *request) { requestFromDelegate = request; }]; RetainPtr 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 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, { "
text needing a font
"_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> 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, { ""_s } }, { "/iframeSrc"_s, { ""_s } }, { "/fetchTarget"_s, { "hi"_s } }, }); enum class Callback { DidSendRequest, DidReceiveResponse, DidCompleteWithError, }; __block Vector callbacks; __block Vector> webViews; __block Vector> loadInfos; __block Vector> 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 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); }