blob: 4d004c84fc4b7c412fd16132c35a77ee4a873b8a [file] [log] [blame]
/*
* Copyright (C) 2014 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 "PlatformUtilities.h"
#import "Test.h"
#import "TestNavigationDelegate.h"
#import "TestUIDelegate.h"
#import "TestWKWebView.h"
#import <WebKit/WKContentWorld.h>
#import <WebKit/WKProcessPoolPrivate.h>
#import <WebKit/WKScriptMessage.h>
#import <WebKit/WKScriptMessageHandlerWithReply.h>
#import <WebKit/WKUserContentControllerPrivate.h>
#import <WebKit/WKUserScript.h>
#import <WebKit/WKUserScriptPrivate.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WebKit.h>
#import <WebKit/_WKProcessPoolConfiguration.h>
#import <WebKit/_WKUserContentWorld.h>
#import <WebKit/_WKUserStyleSheet.h>
#import <wtf/RetainPtr.h>
#import <wtf/Vector.h>
#import <wtf/WeakObjCPtr.h>
static bool isDoneWithNavigation;
@interface SimpleNavigationDelegate : NSObject <WKNavigationDelegate>
@end
@implementation SimpleNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
isDoneWithNavigation = true;
}
@end
static Vector<RetainPtr<WKScriptMessage>> scriptMessagesVector;
@interface ScriptMessageHandler : NSObject <WKScriptMessageHandler>
@end
@implementation ScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
receivedScriptMessage = true;
scriptMessagesVector.append(message);
}
@end
TEST(WKUserContentController, ScriptMessageHandlerBasicPost)
{
scriptMessagesVector.clear();
receivedScriptMessage = false;
RetainPtr<ScriptMessageHandler> handler = adoptNS([[ScriptMessageHandler alloc] init]);
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[webView loadRequest:request];
[webView _test_waitForDidFinishNavigation];
[webView evaluateJavaScript:@"window.webkit.messageHandlers.testHandler.postMessage('Hello')" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
EXPECT_WK_STREQ(@"Hello", (NSString *)[scriptMessagesVector[0] body]);
}
TEST(WKUserContentController, ScriptMessageHandlerBasicPostIsolatedWorld)
{
scriptMessagesVector.clear();
receivedScriptMessage = false;
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<ScriptMessageHandler> handler = adoptNS([[ScriptMessageHandler alloc] init]);
RetainPtr<WKUserScript> userScript = adoptNS([[WKUserScript alloc] _initWithSource:@"window.webkit.messageHandlers.testHandler.postMessage('Hello')" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:nil contentWorld:world.get() deferRunningUntilNotification:NO]);
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[[configuration userContentController] _addScriptMessageHandler:handler.get() name:@"testHandler" contentWorld:world.get()];
[[configuration userContentController] addUserScript:userScript.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
RetainPtr<SimpleNavigationDelegate> delegate = adoptNS([[SimpleNavigationDelegate alloc] init]);
[webView setNavigationDelegate:delegate.get()];
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
isDoneWithNavigation = false;
[webView loadRequest:request];
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
EXPECT_WK_STREQ(@"Hello", (NSString *)[scriptMessagesVector[0] body]);
if (!isDoneWithNavigation)
TestWebKitAPI::Util::run(&isDoneWithNavigation);
__block bool isDoneEvaluatingScript = false;
__block RetainPtr<NSString> resultValue = @"";
[webView evaluateJavaScript:
@"var result;"
"if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.testHandler) {"
" result = { 'result': 'FAIL' };"
"} else {"
" result = { 'result': 'PASS' };"
"} "
"result;"
completionHandler:^(id value, NSError *error) {
resultValue = ((NSDictionary *)value)[@"result"];
isDoneEvaluatingScript = true;
}];
TestWebKitAPI::Util::run(&isDoneEvaluatingScript);
EXPECT_WK_STREQ(@"PASS", resultValue.get());
}
TEST(WKUserContentController, ScriptMessageHandlerBasicRemove)
{
scriptMessagesVector.clear();
receivedScriptMessage = false;
RetainPtr<ScriptMessageHandler> handler = adoptNS([[ScriptMessageHandler alloc] init]);
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<WKUserContentController> userContentController = [configuration userContentController];
[userContentController addScriptMessageHandler:handler.get() name:@"handlerToRemove"];
[userContentController addScriptMessageHandler:handler.get() name:@"handlerToPost"];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[webView loadRequest:request];
[webView _test_waitForDidFinishNavigation];
// Test that handlerToRemove was succesfully added.
[webView evaluateJavaScript:
@"if (window.webkit.messageHandlers.handlerToRemove) {"
" window.webkit.messageHandlers.handlerToPost.postMessage('PASS');"
"} else {"
" window.webkit.messageHandlers.handlerToPost.postMessage('FAIL');"
"}" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
EXPECT_WK_STREQ(@"PASS", (NSString *)[scriptMessagesVector[0] body]);
[userContentController removeScriptMessageHandlerForName:@"handlerToRemove"];
// Test that handlerToRemove has been removed.
[webView evaluateJavaScript:
@"if (window.webkit.messageHandlers.handlerToRemove) {"
" window.webkit.messageHandlers.handlerToPost.postMessage('FAIL');"
"} else {"
" window.webkit.messageHandlers.handlerToPost.postMessage('PASS');"
"}" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
EXPECT_WK_STREQ(@"PASS", (NSString *)[scriptMessagesVector[0] body]);
}
TEST(WKUserContentController, ScriptMessageHandlerCallRemovedHandler)
{
receivedScriptMessage = false;
RetainPtr<ScriptMessageHandler> handler = adoptNS([[ScriptMessageHandler alloc] init]);
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<WKUserContentController> userContentController = [configuration userContentController];
[userContentController addScriptMessageHandler:handler.get() name:@"handlerToRemove"];
[userContentController addScriptMessageHandler:handler.get() name:@"handlerToPost"];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[webView loadRequest:request];
[webView _test_waitForDidFinishNavigation];
[webView evaluateJavaScript:@"var handlerToRemove = window.webkit.messageHandlers.handlerToRemove;" completionHandler:nil];
[userContentController removeScriptMessageHandlerForName:@"handlerToRemove"];
__block bool done = false;
// Test that we throw an exception if you try to use a message handler that has been removed.
[webView callAsyncJavaScript:@"return handlerToRemove.postMessage('FAIL')" arguments:nil inFrame:nil inContentWorld:[WKContentWorld pageWorld] completionHandler:^ (id value, NSError * error) {
EXPECT_NULL(value);
EXPECT_NOT_NULL(error);
EXPECT_TRUE([[error description] containsString:@"InvalidAccessError"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
receivedScriptMessage = false;
}
static RetainPtr<WKWebView> webViewForScriptMessageHandlerMultipleHandlerRemovalTest(WKWebViewConfiguration *configuration)
{
receivedScriptMessage = false;
RetainPtr<ScriptMessageHandler> handler = adoptNS([[ScriptMessageHandler alloc] init]);
RetainPtr<WKWebViewConfiguration> configurationCopy = adoptNS([configuration copy]);
[configurationCopy setUserContentController:adoptNS([[WKUserContentController alloc] init]).get()];
[[configurationCopy userContentController] addScriptMessageHandler:handler.get() name:@"handlerToRemove"];
[[configurationCopy userContentController] addScriptMessageHandler:handler.get() name:@"handlerToPost"];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configurationCopy.get()]);
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[webView loadRequest:request];
[webView _test_waitForDidFinishNavigation];
return webView;
}
TEST(WKUserContentController, ScriptMessageHandlerMultipleHandlerRemoval)
{
scriptMessagesVector.clear();
receivedScriptMessage = false;
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<_WKProcessPoolConfiguration> processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
[configuration setProcessPool:adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]).get()];
RetainPtr<WKWebView> webView = webViewForScriptMessageHandlerMultipleHandlerRemovalTest(configuration.get());
RetainPtr<WKWebView> webView2 = webViewForScriptMessageHandlerMultipleHandlerRemovalTest(configuration.get());
[[[webView configuration] userContentController] removeScriptMessageHandlerForName:@"handlerToRemove"];
[[[webView2 configuration] userContentController] removeScriptMessageHandlerForName:@"handlerToRemove"];
[webView evaluateJavaScript:
@"try {"
" handlerToRemove.postMessage('FAIL');"
"} catch (e) {"
" window.webkit.messageHandlers.handlerToPost.postMessage('PASS');"
"}" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
EXPECT_WK_STREQ(@"PASS", (NSString *)[scriptMessagesVector[0] body]);
}
#if !PLATFORM(IOS_FAMILY) // FIXME: hangs in the iOS simulator
TEST(WKUserContentController, ScriptMessageHandlerWithNavigation)
{
scriptMessagesVector.clear();
receivedScriptMessage = false;
RetainPtr<ScriptMessageHandler> handler = adoptNS([[ScriptMessageHandler alloc] init]);
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[webView loadRequest:request];
[webView _test_waitForDidFinishNavigation];
[webView evaluateJavaScript:@"window.webkit.messageHandlers.testHandler.postMessage('First Message')" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
EXPECT_WK_STREQ(@"First Message", (NSString *)[scriptMessagesVector[0] body]);
receivedScriptMessage = false;
[webView loadRequest:request];
[webView _test_waitForDidFinishNavigation];
[webView evaluateJavaScript:@"window.webkit.messageHandlers.testHandler.postMessage('Second Message')" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
EXPECT_WK_STREQ(@"Second Message", (NSString *)[scriptMessagesVector[1] body]);
}
#endif
TEST(WKUserContentController, ScriptMessageHandlerReplaceWithSameName)
{
scriptMessagesVector.clear();
receivedScriptMessage = false;
RetainPtr<ScriptMessageHandler> handler = adoptNS([[ScriptMessageHandler alloc] init]);
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<WKUserContentController> userContentController = [configuration userContentController];
[userContentController addScriptMessageHandler:handler.get() name:@"handlerToReplace"];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[webView loadRequest:request];
[webView _test_waitForDidFinishNavigation];
// Test that handlerToReplace was succesfully added.
[webView evaluateJavaScript:@"window.webkit.messageHandlers.handlerToReplace.postMessage('PASS1');" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
EXPECT_WK_STREQ(@"PASS1", (NSString *)[scriptMessagesVector[0] body]);
[userContentController removeScriptMessageHandlerForName:@"handlerToReplace"];
[userContentController addScriptMessageHandler:handler.get() name:@"handlerToReplace"];
// Test that handlerToReplace still works.
[webView evaluateJavaScript:@"window.webkit.messageHandlers.handlerToReplace.postMessage('PASS2');" completionHandler:nil];
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
EXPECT_WK_STREQ(@"PASS2", (NSString *)[scriptMessagesVector[1] body]);
}
static NSString *styleSheetSource = @"body { background-color: green !important; }";
static NSString *backgroundColorScript = @"window.getComputedStyle(document.body, null).getPropertyValue('background-color')";
static NSString *frameBackgroundColorScript = @"window.getComputedStyle(document.getElementsByTagName('iframe')[0].contentDocument.body, null).getPropertyValue('background-color')";
static NSString *styleSheetSourceForSpecificityLevelTests = @"#body { background-color: green; }";
static const char* greenInRGB = "rgb(0, 128, 0)";
static const char* redInRGB = "rgb(255, 0, 0)";
static const char* whiteInRGB = "rgba(0, 0, 0, 0)";
static const char* blueInRGB = "rgb(0, 0, 255)";
static void expectScriptEvaluatesToColor(WKWebView *webView, NSString *script, const char* color)
{
static bool didCheckBackgroundColor;
[webView evaluateJavaScript:script completionHandler:^ (id value, NSError * error) {
EXPECT_TRUE([value isKindOfClass:[NSString class]]);
EXPECT_WK_STREQ(color, value);
didCheckBackgroundColor = true;
}];
TestWebKitAPI::Util::run(&didCheckBackgroundColor);
didCheckBackgroundColor = false;
}
TEST(WKUserContentController, AddUserStyleSheetBeforeCreatingView)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:nil forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleUserLevel contentWorld:nil]);
[[configuration userContentController] _addUserStyleSheet:styleSheet.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
}
TEST(WKUserContentController, NonCanonicalizedURL)
{
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:nil forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:adoptNS([[NSURL alloc] initWithString:@"http://CamelCase/"]).get() level:_WKUserStyleUserLevel contentWorld:world.get()]);
}
TEST(WKUserContentController, AddUserStyleSheetAfterCreatingView)
{
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:YES]);
[[webView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingOnlyMainFrame)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:YES]);
[[configuration userContentController] _addUserStyleSheet:styleSheet.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView loadHTMLString:@"<body style='background-color: red;'><iframe></iframe></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
// The main frame should be affected.
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
// The subframe shouldn't be affected.
expectScriptEvaluatesToColor(webView.get(), frameBackgroundColorScript, whiteInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingAllFrames)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]);
[[configuration userContentController] _addUserStyleSheet:styleSheet.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView loadHTMLString:@"<body style='background-color: red;'><iframe></iframe></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
// The main frame should be affected.
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
// The subframe should also be affected.
expectScriptEvaluatesToColor(webView.get(), frameBackgroundColorScript, greenInRGB);
}
TEST(WKUserContentController, UserStyleSheetRemove)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]);
[userContentController _addUserStyleSheet:styleSheet.get()];
EXPECT_EQ(1u, [userContentController _userStyleSheets].count);
EXPECT_EQ(styleSheet.get(), [userContentController _userStyleSheets][0]);
[userContentController _removeUserStyleSheet:styleSheet.get()];
EXPECT_EQ(0u, [userContentController _userStyleSheets].count);
}
TEST(WKUserContentController, UserStyleSheetRemoveAll)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]);
RetainPtr<_WKUserStyleSheet> styleSheetAssociatedWithWorld = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:nil forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:nil level:_WKUserStyleUserLevel contentWorld:world.get()]);
[userContentController _addUserStyleSheet:styleSheet.get()];
[userContentController _addUserStyleSheet:styleSheetAssociatedWithWorld.get()];
EXPECT_EQ(2u, [userContentController _userStyleSheets].count);
EXPECT_EQ(styleSheet.get(), [userContentController _userStyleSheets][0]);
EXPECT_EQ(styleSheetAssociatedWithWorld.get(), [userContentController _userStyleSheets][1]);
[userContentController _removeAllUserStyleSheets];
EXPECT_EQ(0u, [userContentController _userStyleSheets].count);
}
TEST(WKUserContentController, UserStyleSheetRemoveAllByWorld)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]);
RetainPtr<_WKUserStyleSheet> styleSheetAssociatedWithWorld = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:nil forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:nil level:_WKUserStyleUserLevel contentWorld:world.get()]);
RetainPtr<_WKUserStyleSheet> styleSheetAssociatedWithWorld2 = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:nil forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:nil level:_WKUserStyleUserLevel contentWorld:world.get()]);
[userContentController _addUserStyleSheet:styleSheet.get()];
[userContentController _addUserStyleSheet:styleSheetAssociatedWithWorld.get()];
[userContentController _addUserStyleSheet:styleSheetAssociatedWithWorld2.get()];
EXPECT_EQ(3u, [userContentController _userStyleSheets].count);
EXPECT_EQ(styleSheet.get(), [userContentController _userStyleSheets][0]);
EXPECT_EQ(styleSheetAssociatedWithWorld.get(), [userContentController _userStyleSheets][1]);
EXPECT_EQ(styleSheetAssociatedWithWorld2.get(), [userContentController _userStyleSheets][2]);
[userContentController _removeAllUserStyleSheetsAssociatedWithContentWorld:world.get()];
EXPECT_EQ(1u, [userContentController _userStyleSheets].count);
EXPECT_EQ(styleSheet.get(), [userContentController _userStyleSheets][0]);
}
TEST(WKUserContentController, UserStyleSheetRemoveAllByNormalWorld)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]);
RetainPtr<_WKUserStyleSheet> styleSheetAssociatedWithWorld = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:nil forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:nil level:_WKUserStyleUserLevel contentWorld:world.get()]);
RetainPtr<_WKUserStyleSheet> styleSheetAssociatedWithWorld2 = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:nil forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:nil level:_WKUserStyleUserLevel contentWorld:world.get()]);
[userContentController _addUserStyleSheet:styleSheet.get()];
[userContentController _addUserStyleSheet:styleSheetAssociatedWithWorld.get()];
[userContentController _addUserStyleSheet:styleSheetAssociatedWithWorld2.get()];
EXPECT_EQ(3u, [userContentController _userStyleSheets].count);
EXPECT_EQ(styleSheet.get(), [userContentController _userStyleSheets][0]);
EXPECT_EQ(styleSheetAssociatedWithWorld.get(), [userContentController _userStyleSheets][1]);
EXPECT_EQ(styleSheetAssociatedWithWorld2.get(), [userContentController _userStyleSheets][2]);
[userContentController _removeAllUserStyleSheetsAssociatedWithContentWorld:[WKContentWorld pageWorld]];
EXPECT_EQ(2u, [userContentController _userStyleSheets].count);
EXPECT_EQ(styleSheetAssociatedWithWorld.get(), [userContentController _userStyleSheets][0]);
EXPECT_EQ(styleSheetAssociatedWithWorld2.get(), [userContentController _userStyleSheets][1]);
}
TEST(WKUserContentController, UserStyleSheetAffectingOnlySpecificWebView)
{
RetainPtr<WKWebView> targetWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
RetainPtr<WKWebView> otherWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[targetWebView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[targetWebView _test_waitForDidFinishNavigation];
[otherWebView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[otherWebView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(targetWebView.get(), backgroundColorScript, redInRGB);
expectScriptEvaluatesToColor(otherWebView.get(), backgroundColorScript, redInRGB);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
auto styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:targetWebView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[targetWebView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(targetWebView.get(), backgroundColorScript, greenInRGB);
expectScriptEvaluatesToColor(otherWebView.get(), backgroundColorScript, redInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingSpecificWebViewInjectionImmediatelyAfterCreation)
{
RetainPtr<WKWebView> targetWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[targetWebView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:targetWebView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[targetWebView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
[targetWebView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(targetWebView.get(), backgroundColorScript, greenInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingSpecificWebViewInjectionAndRemovalImmediatelyAfterCreation)
{
RetainPtr<WKWebView> targetWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[targetWebView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:targetWebView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[targetWebView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
[[targetWebView configuration].userContentController _removeUserStyleSheet:styleSheet.get()];
[targetWebView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(targetWebView.get(), backgroundColorScript, redInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingOnlySpecificWebViewSharedConfiguration)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<WKWebView> targetWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
RetainPtr<WKWebView> otherWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[targetWebView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[targetWebView _test_waitForDidFinishNavigation];
[otherWebView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[otherWebView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(targetWebView.get(), backgroundColorScript, redInRGB);
expectScriptEvaluatesToColor(otherWebView.get(), backgroundColorScript, redInRGB);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:targetWebView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[targetWebView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(targetWebView.get(), backgroundColorScript, greenInRGB);
expectScriptEvaluatesToColor(otherWebView.get(), backgroundColorScript, redInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingOnlySpecificWebViewRemoval)
{
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:webView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[webView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
[[webView configuration].userContentController _removeUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingOnlySpecificWebViewRemovalAfterMultipleAdditions)
{
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:webView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[webView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
[[webView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
[[webView configuration].userContentController _removeUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
}
TEST(WKUserContentController, UserStyleSheetAffectingOnlySpecificWebViewRemovalAfterNavigation)
{
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:webView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[webView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
[webView loadHTMLString:@"<body style='background-color: blue;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, blueInRGB);
}
static RetainPtr<_WKProcessPoolConfiguration> psonProcessPoolConfiguration()
{
auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
processPoolConfiguration.get().processSwapsOnNavigation = YES;
processPoolConfiguration.get().usesWebProcessCache = YES;
processPoolConfiguration.get().prewarmsProcessesAutomatically = YES;
return processPoolConfiguration;
}
TEST(WKUserContentController, UserStyleSheetAffectingOnlySpecificWebViewRemovalAfterNavigationPSON)
{
auto processPoolConfiguration = psonProcessPoolConfiguration();
auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
[webViewConfiguration setProcessPool:processPool.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
[webView loadHTMLString:@"<body style='background-color: red;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forWKWebView:webView.get() forMainFrameOnly:YES includeMatchPatternStrings:nil excludeMatchPatternStrings:nil baseURL:nil level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[webView configuration].userContentController _addUserStyleSheet:styleSheet.get()];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
[webView loadHTMLString:@"<body style='background-color: blue;'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, blueInRGB);
}
TEST(WKUserContentController, UserStyleSheetSpecificityLevelNotSpecified)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSourceForSpecificityLevelTests forMainFrameOnly:YES]);
[[configuration userContentController] _addUserStyleSheet:styleSheet.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView loadHTMLString:@"<style>body { background-color: red }</style><body id='body'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
// By default, the injected style sheet will not be able to override due to the "user" level being used.
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
}
TEST(WKUserContentController, UserStyleSheetSpecificityLevelUser)
{
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSourceForSpecificityLevelTests forWKWebView:nil forMainFrameOnly:YES includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:[[NSURL alloc] initWithString:@"http://example.com/"] level:_WKUserStyleUserLevel contentWorld:world.get()]);
[[configuration userContentController] _addUserStyleSheet:styleSheet.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView loadHTMLString:@"<style>body { background-color: red }</style><body id='body'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
// The injected style sheet will not be able to override due to the "user" level being used.
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, redInRGB);
}
TEST(WKUserContentController, UserStyleSheetSpecificityLevelAuthor)
{
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<_WKUserStyleSheet> styleSheet = adoptNS([[_WKUserStyleSheet alloc] initWithSource:styleSheetSourceForSpecificityLevelTests forWKWebView:nil forMainFrameOnly:YES includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] baseURL:[[NSURL alloc] initWithString:@"http://example.com/"] level:_WKUserStyleAuthorLevel contentWorld:world.get()]);
[[configuration userContentController] _addUserStyleSheet:styleSheet.get()];
RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView loadHTMLString:@"<style>body { background-color: red }</style><body id='body'></body>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
// The injected style sheet *will* be able to override due to the "author" level being used.
expectScriptEvaluatesToColor(webView.get(), backgroundColorScript, greenInRGB);
}
TEST(WKUserContentController, UserScriptRemove)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<WKUserScript> userScript = adoptNS([[WKUserScript alloc] initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]);
[userContentController addUserScript:userScript.get()];
EXPECT_EQ(1u, [userContentController userScripts].count);
EXPECT_EQ(userScript.get(), [userContentController userScripts][0]);
[userContentController _removeUserScript:userScript.get()];
EXPECT_EQ(0u, [userContentController userScripts].count);
}
TEST(WKUserContentController, UserScriptRemoveAll)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<WKUserScript> userScript = adoptNS([[WKUserScript alloc] initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]);
auto userScriptAssociatedWithWorld = adoptNS([[WKUserScript alloc] _initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:nil contentWorld:world.get() deferRunningUntilNotification:NO]);
[userContentController addUserScript:userScript.get()];
[userContentController addUserScript:userScriptAssociatedWithWorld.get()];
EXPECT_EQ(2u, [userContentController userScripts].count);
EXPECT_EQ(userScript.get(), [userContentController userScripts][0]);
EXPECT_EQ(userScriptAssociatedWithWorld.get(), [userContentController userScripts][1]);
[userContentController removeAllUserScripts];
EXPECT_EQ(0u, [userContentController userScripts].count);
}
TEST(WKUserContentController, UserScriptRemoveAllByWorld)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<WKUserScript> userScript = adoptNS([[WKUserScript alloc] initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]);
RetainPtr<WKUserScript> userScriptAssociatedWithWorld = adoptNS([[WKUserScript alloc] _initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:nil contentWorld:world.get() deferRunningUntilNotification:NO]);
RetainPtr<WKUserScript> userScriptAssociatedWithWorld2 = adoptNS([[WKUserScript alloc] _initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:nil contentWorld:world.get() deferRunningUntilNotification:NO]);
[userContentController addUserScript:userScript.get()];
[userContentController addUserScript:userScriptAssociatedWithWorld.get()];
[userContentController addUserScript:userScriptAssociatedWithWorld2.get()];
EXPECT_EQ(3u, [userContentController userScripts].count);
EXPECT_EQ(userScript.get(), [userContentController userScripts][0]);
EXPECT_EQ(userScriptAssociatedWithWorld.get(), [userContentController userScripts][1]);
EXPECT_EQ(userScriptAssociatedWithWorld2.get(), [userContentController userScripts][2]);
[userContentController _removeAllUserScriptsAssociatedWithContentWorld:world.get()];
EXPECT_EQ(1u, [userContentController userScripts].count);
EXPECT_EQ(userScript.get(), [userContentController userScripts][0]);
}
TEST(WKUserContentController, UserScriptRemoveAllByNormalWorld)
{
RetainPtr<WKUserContentController> userContentController = adoptNS([[WKUserContentController alloc] init]);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"TestWorld"];
RetainPtr<WKUserScript> userScript = adoptNS([[WKUserScript alloc] initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]);
RetainPtr<WKUserScript> userScriptAssociatedWithWorld = adoptNS([[WKUserScript alloc] _initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:nil contentWorld:world.get() deferRunningUntilNotification:NO]);
RetainPtr<WKUserScript> userScriptAssociatedWithWorld2 = adoptNS([[WKUserScript alloc] _initWithSource:@"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:nil contentWorld:world.get() deferRunningUntilNotification:NO]);
[userContentController addUserScript:userScript.get()];
[userContentController addUserScript:userScriptAssociatedWithWorld.get()];
[userContentController addUserScript:userScriptAssociatedWithWorld2.get()];
EXPECT_EQ(3u, [userContentController userScripts].count);
EXPECT_EQ(userScript.get(), [userContentController userScripts][0]);
EXPECT_EQ(userScriptAssociatedWithWorld.get(), [userContentController userScripts][1]);
EXPECT_EQ(userScriptAssociatedWithWorld2.get(), [userContentController userScripts][2]);
[userContentController _removeAllUserScriptsAssociatedWithContentWorld:[WKContentWorld pageWorld]];
EXPECT_EQ(2u, [userContentController userScripts].count);
EXPECT_EQ(userScriptAssociatedWithWorld.get(), [userContentController userScripts][0]);
EXPECT_EQ(userScriptAssociatedWithWorld2.get(), [userContentController userScripts][1]);
}
static void waitForMessages(size_t expectedCount)
{
while (scriptMessagesVector.size() < expectedCount) {
TestWebKitAPI::Util::run(&receivedScriptMessage);
receivedScriptMessage = false;
}
}
static void compareMessages(Vector<const char*>&& expectedMessages)
{
EXPECT_EQ(expectedMessages.size(), scriptMessagesVector.size());
for (size_t i = 0; i < expectedMessages.size(); ++i)
EXPECT_STREQ([[scriptMessagesVector[i] body] UTF8String], expectedMessages[i]);
}
TEST(WKUserContentController, InjectUserScriptImmediately)
{
scriptMessagesVector.clear();
receivedScriptMessage = false;
auto handler = adoptNS([[ScriptMessageHandler alloc] init]);
auto startAllFrames = adoptNS([[WKUserScript alloc] initWithSource:@"window.webkit.messageHandlers.testHandler.postMessage('start all')" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]);
auto endMainFrameOnly = adoptNS([[WKUserScript alloc] initWithSource:@"window.webkit.messageHandlers.testHandler.postMessage('end main')" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]);
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
auto delegate = adoptNS([[SimpleNavigationDelegate alloc] init]);
[webView setNavigationDelegate:delegate.get()];
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple-iframe" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
isDoneWithNavigation = false;
[webView loadRequest:request];
TestWebKitAPI::Util::run(&isDoneWithNavigation);
receivedScriptMessage = false;
[[configuration userContentController] _addUserScriptImmediately:startAllFrames.get()];
// simple-iframe.html has a main frame and one iframe.
waitForMessages(2);
[[configuration userContentController] _addUserScriptImmediately:endMainFrameOnly.get()];
waitForMessages(3);
[webView reload];
waitForMessages(6);
compareMessages({"start all", "start all", "end main", "start all", "end main", "start all"});
}
TEST(WKUserContentController, UserScriptNotification)
{
auto waitsForNotification = adoptNS([[WKUserScript alloc] _initWithSource:@"alert('waited for notification')" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES includeMatchPatternStrings:@[] excludeMatchPatternStrings:@[] associatedURL:[NSURL URLWithString:@"test:///script"] contentWorld:[WKContentWorld defaultClientWorld] deferRunningUntilNotification:YES]);
auto documentEnd = adoptNS([[WKUserScript alloc] initWithSource:@"alert('document parsing ended')" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]);
auto webView1 = adoptNS([TestWKWebView new]);
EXPECT_TRUE([webView1 _deferrableUserScriptsNeedNotification]);
[[webView1 configuration].userContentController addUserScript:waitsForNotification.get()];
[[webView1 configuration].userContentController addUserScript:documentEnd.get()];
auto delegate = adoptNS([TestUIDelegate new]);
[webView1 setUIDelegate:delegate.get()];
[webView1 loadTestPageNamed:@"simple"];
EXPECT_WK_STREQ([delegate waitForAlert], "document parsing ended");
EXPECT_TRUE([webView1 _deferrableUserScriptsNeedNotification]);
[webView1 _notifyUserScripts];
EXPECT_FALSE([webView1 _deferrableUserScriptsNeedNotification]);
EXPECT_WK_STREQ([delegate waitForAlert], "waited for notification");
[webView1 _killWebContentProcessAndResetState];
[webView1 reload];
EXPECT_WK_STREQ([delegate waitForAlert], "document parsing ended");
EXPECT_TRUE([webView1 _deferrableUserScriptsNeedNotification]);
[webView1 _notifyUserScripts];
EXPECT_FALSE([webView1 _deferrableUserScriptsNeedNotification]);
EXPECT_WK_STREQ([delegate waitForAlert], "waited for notification");
[webView1 reload];
EXPECT_FALSE([webView1 _deferrableUserScriptsNeedNotification]);
EXPECT_WK_STREQ([delegate waitForAlert], "waited for notification");
EXPECT_WK_STREQ([delegate waitForAlert], "document parsing ended");
auto configuration = adoptNS([WKWebViewConfiguration new]);
EXPECT_TRUE([configuration _deferrableUserScriptsShouldWaitUntilNotification]);
configuration.get()._deferrableUserScriptsShouldWaitUntilNotification = NO;
auto webView2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
EXPECT_FALSE([webView2 _deferrableUserScriptsNeedNotification]);
[[webView2 configuration].userContentController addUserScript:waitsForNotification.get()];
[[webView2 configuration].userContentController addUserScript:documentEnd.get()];
[webView2 setUIDelegate:delegate.get()];
[webView2 loadTestPageNamed:@"simple"];
EXPECT_WK_STREQ([delegate waitForAlert], "waited for notification");
EXPECT_WK_STREQ([delegate waitForAlert], "document parsing ended");
auto webView3 = adoptNS([TestWKWebView new]);
EXPECT_TRUE([webView3 _deferrableUserScriptsNeedNotification]);
[[webView3 configuration].userContentController addUserScript:waitsForNotification.get()];
[[webView3 configuration].userContentController addUserScript:documentEnd.get()];
[webView3 setUIDelegate:delegate.get()];
[webView3 loadTestPageNamed:@"simple"];
[webView3 _notifyUserScripts];
EXPECT_FALSE([webView3 _deferrableUserScriptsNeedNotification]);
EXPECT_WK_STREQ([delegate waitForAlert], "waited for notification");
EXPECT_WK_STREQ([delegate waitForAlert], "document parsing ended");
auto webView4 = adoptNS([TestWKWebView new]);
EXPECT_TRUE([webView4 _deferrableUserScriptsNeedNotification]);
[[webView4 configuration].userContentController addUserScript:waitsForNotification.get()];
[[webView4 configuration].userContentController addUserScript:documentEnd.get()];
[webView4 setUIDelegate:delegate.get()];
[webView4 loadTestPageNamed:@"simple-iframe"];
[webView4 _notifyUserScripts];
// If this is broken, two alerts would appear back-to-back with the same text due to the frame.
EXPECT_WK_STREQ([delegate waitForAlert], "waited for notification");
EXPECT_WK_STREQ([delegate waitForAlert], "document parsing ended");
}
@interface AsyncScriptMessageHandler : NSObject <WKScriptMessageHandlerWithReply>
@end
@implementation AsyncScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message replyHandler:(void (^)(id, NSString *errorMessage))replyHandler
{
if ([message.name isEqualToString:@"otherWorldHandler"])
EXPECT_TRUE(message.world != nil);
if ([message.body isKindOfClass:[NSString class]]) {
if ([message.body isEqualToString:@"Fulfill"]) {
replyHandler(@"Fulfilled!", nil);
return;
}
if ([message.body isEqualToString:@"Reject"]) {
replyHandler(nil, @"Rejected!");
return;
}
if ([message.body isEqualToString:@"Undefined"]) {
replyHandler(nil, nil);
bool caught = false;
@try {
replyHandler(nil, nil);
} @catch (NSException *exception) {
caught = true;
}
EXPECT_TRUE(caught);
return;
}
if ([message.body isEqualToString:@"Do nothing"]) {
// Drop the reply handler without responding to see what happens
return;
}
if ([message.body isEqualToString:@"Invalid reply"]) {
replyHandler(adoptNS([[NSData alloc] init]).get(), nil);
return;
}
}
// All other inputs should just be round tripped back to the message handler
replyHandler(message.body, nil);
}
@end
TEST(WKUserContentController, MessageHandlerAPI)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto handler = adoptNS([[AsyncScriptMessageHandler alloc] init]);
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:WKContentWorld.pageWorld name:@"testHandler1"];
bool hadException = false;
@try {
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:WKContentWorld.pageWorld name:@"testHandler1"];
} @catch (NSException *exception) {
hadException = true;
}
EXPECT_TRUE(hadException);
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:WKContentWorld.defaultClientWorld name:@"testHandler2"];
auto *world = [WKContentWorld worldWithName:@"otherWorld"];
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:world name:@"testHandler3"];
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:world name:@"testHandler4"];
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:world name:@"testHandler5"];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
bool done = false;
NSString *functionBody = @"var p = window.webkit.messageHandlers[handler].postMessage(arg); await p; return p;";
// pageWorld is where testhandler1 lives
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler1", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// Trying to find testHandler1 in the defaultClientWorld should fail
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler1", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_NOT_NULL(error);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// defaultClientWorld is where testhandler2 lives
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler2", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// But if we remvoe it, it should no longer live there, and using it should cause an error.
[[configuration userContentController] removeScriptMessageHandlerForName:@"testHandler2" contentWorld:WKContentWorld.defaultClientWorld];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler2", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_NOT_NULL(error);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// Verify handlers 3, 4, and 5 are all in the custom world.
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
}];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
}];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// Remove 3 from the wrong world, verify it is still there in the custom world.
[[configuration userContentController] removeScriptMessageHandlerForName:@"testHandler3" contentWorld:WKContentWorld.defaultClientWorld];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// Remove 3 from the correct world, verify it is gone, but 4 and 5 are still there.
[[configuration userContentController] removeScriptMessageHandlerForName:@"testHandler3" contentWorld:world];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_NOT_NULL(error);
}];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
}];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isEqualToNumber:@1]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// Remove "all" in the custom world, verify 4 and 5 are now gone.
[[configuration userContentController] removeAllScriptMessageHandlersFromContentWorld:world];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_NOT_NULL(error);
}];
[webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_NOT_NULL(error);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
}
TEST(WKUserContentController, AsyncScriptMessageHandlerBasicPost)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto handler = adoptNS([[AsyncScriptMessageHandler alloc] init]);
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:WKContentWorld.pageWorld name:@"testHandler"];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
bool done = false;
NSString *functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Fulfill'); await p; return p;";
[webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isKindOfClass:[NSString class]]);
EXPECT_TRUE([result isEqualToString:@"Fulfilled!"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Reject'); await p; return p;";
[webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_TRUE(!!error);
EXPECT_TRUE([[error description] containsString:@"Rejected!"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Undefined'); var result = await p; return result == undefined ? 'Yes' : 'No'";
[webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isKindOfClass:[NSString class]]);
EXPECT_TRUE([result isEqualToString:@"Yes"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Do nothing'); await p; return p;";
[webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_TRUE(!!error);
EXPECT_TRUE([[error description] containsString:@"did not respond to this postMessage"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Invalid reply'); await p; return p;";
[webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(result);
EXPECT_TRUE(!!error);
EXPECT_TRUE([[error description] containsString:@"unable to be serialized"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
}
TEST(WKUserContentController, WorldLifetime)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto handler = adoptNS([[AsyncScriptMessageHandler alloc] init]);
RetainPtr<WKContentWorld> world = [WKContentWorld worldWithName:@"otherWorld"];
[[configuration userContentController] addScriptMessageHandlerWithReply:handler.get() contentWorld:world.get() name:@"testHandler"];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
// Set a variable in the world.
bool done = false;
[webView evaluateJavaScript:@"var foo = 'bar'" inFrame:nil inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// Have the message handler bounce back that value.
NSString *functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage(foo); await p; return p;";
[webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isKindOfClass:[NSString class]]);
EXPECT_TRUE([result isEqualToString:@"bar"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
// Remove the message handler, which used to cause the world to be destroyed in the web process.
// But by evaluating JS make sure the value is still there.
[[configuration userContentController] removeAllScriptMessageHandlersFromContentWorld:world.get()];
[webView evaluateJavaScript:@"foo" inFrame:nil inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
EXPECT_NULL(error);
EXPECT_TRUE([result isKindOfClass:[NSString class]]);
EXPECT_TRUE([result isEqualToString:@"bar"]);
done = true;
}];
TestWebKitAPI::Util::run(&done);
done = false;
}
TEST(WKUserContentController, RemoveAllScriptMessageHandlers)
{
WeakObjCPtr<ScriptMessageHandler> weakHandler;
auto handler = adoptNS([ScriptMessageHandler new]);
weakHandler = handler.get();
auto controller = adoptNS([WKUserContentController new]);
[controller addScriptMessageHandler:handler.get() name:@"testname"];
handler = nullptr;
EXPECT_NOT_NULL(weakHandler.get());
[controller removeAllScriptMessageHandlers];
EXPECT_NULL(weakHandler.get());
}