/* * 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 "DeprecatedGlobalValues.h" #import "RemoteObjectRegistry.h" #import "TestWKWebView.h" #import "Utilities.h" #import #import #import #import #import #import #import #import static bool didCrash = false; static RetainPtr alertMessage; static RetainPtr promptDefault; static RetainPtr promptResult; @interface IPCTestingAPIDelegate : NSObject - (BOOL)sayHelloWasCalled; @end @implementation IPCTestingAPIDelegate { BOOL _didCallSayHello; } - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler { alertMessage = message; done = true; completionHandler(); } - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler { promptDefault = defaultText; done = true; completionHandler(promptResult.get()); } - (void)_webView:(WKWebView *)webView webContentProcessDidTerminateWithReason:(_WKProcessTerminationReason)reason { didCrash = false; done = true; } - (void)sayHello:(NSString *)hello completionHandler:(void (^)(NSString *))completionHandler { _didCallSayHello = YES; } - (BOOL)sayHelloWasCalled { return _didCallSayHello; } @end TEST(IPCTestingAPI, IsDisabledByDefault) { auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "undefined"); } // Note: There are more IPC tests using IPC testing API in `LayoutTests/ipc`. #if ENABLE(IPC_TESTING_API) static RetainPtr createWebViewWithIPCTestingAPI() { RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]); for (_WKInternalDebugFeature *feature in [WKPreferences _internalDebugFeatures]) { if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) { [[configuration preferences] _setEnabled:YES forInternalDebugFeature:feature]; break; } } return adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get()]); } #if !ASSERT_ENABLED TEST(IPCTestingAPI, CanDetectNilReplyBlocks) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; _WKRemoteObjectInterface *interface = remoteObjectInterface(); [[webView _remoteObjectRegistry] remoteObjectProxyWithInterface:interface]; [[webView _remoteObjectRegistry] registerExportedObject:delegate.get() interface:interface]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::runFor(&done, 1); // Make sure sayHello was not called, as the reply block was nil. EXPECT_FALSE([delegate.get() sayHelloWasCalled]); } #endif TEST(IPCTestingAPI, CanSendAlert) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "hi"); } TEST(IPCTestingAPI, AlertIsSyncMessage) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "true"); } TEST(IPCTestingAPI, CanSendInvalidAsyncMessageToUIProcessWithoutTermination) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "hi"); } TEST(IPCTestingAPI, CanSendInvalidSyncMessageToUIProcessWithoutTermination) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "hi"); } #if ENABLE(GPU_PROCESS) TEST(IPCTestingAPI, CanSendSyncMessageToGPUProcess) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_TRUE([alertMessage boolValue]); } TEST(IPCTestingAPI, CanSendAsyncMessageToGPUProcess) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_TRUE([alertMessage boolValue]); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"result.arguments[0].type"].UTF8String, "bool"); EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"result.arguments[0].value"].boolValue); } TEST(IPCTestingAPI, CanSendInvalidAsyncMessageToGPUProcessWithoutTermination) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_TRUE([alertMessage boolValue]); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"result.arguments[0].type"].UTF8String, "bool"); EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"result.arguments[0].value"].boolValue); } #endif // ENABLE(GPU_PROCESS) TEST(IPCTestingAPI, CanCreateSharedMemory) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "[object SharedMemory]"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"new Int8Array(sharedMemory.readBytes(0))[0]"].intValue, 0); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"sharedMemory.writeBytes(new Int8Array([1, 2, 4, 8, 16, 32]))"].intValue, 0); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"Array.from(new Int8Array(sharedMemory.readBytes(1, 3))).toString()"].UTF8String, "2,4,8"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"sharedMemory.writeBytes(new Int8Array([101, 102, 103, 104, 105, 106]), 2, 3)"].intValue, 0); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"Array.from(new Int8Array(sharedMemory.readBytes())).toString()"].UTF8String, "1,2,101,102,103,32,0,0"); } #if PLATFORM(COCOA) TEST(IPCTestingAPI, CanSendSharedMemory) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; auto* html = @R"HTML( )HTML"; done = false; [webView synchronouslyLoadHTMLString:html]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "1:{\"type\":\"int64_t\",\"value\":0}"); } #endif TEST(IPCTestingAPI, DecodesReplyArgumentsForPrompt) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; promptResult = @"foo"; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([promptDefault UTF8String], "bar"); EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"JSON.stringify(result.arguments)"] UTF8String], "[{\"type\":\"String\",\"value\":\"foo\"}]"); } #if ENABLE(INTELLIGENT_TRACKING_PREVENTION) TEST(IPCTestingAPI, DecodesReplyArgumentsForAsyncMessage) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; promptResult = @"foo"; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "[{\"type\":\"bool\",\"value\":false}]"); } #endif TEST(IPCTestingAPI, DescribesArguments) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"args.length"] UTF8String], "3"); EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"args[0].type"] UTF8String], "WebCore::FrameIdentifier"); EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"args[1].type"] UTF8String], "WebKit::FrameInfoData"); EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"args[2].name"] UTF8String], "message"); EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"args[2].type"] UTF8String], "String"); } TEST(IPCTestingAPI, CanInterceptAlert) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "ok"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages = messages.filter((message) => message.name == IPC.messages.WebPageProxy_RunJavaScriptAlert.name); messages.length"].UTF8String, "1"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages[0].description"].UTF8String, "WebPageProxy_RunJavaScriptAlert"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args = messages[0].arguments; args.length"].intValue, 3); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[0].type"].UTF8String, "uint64_t"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[0].value"].intValue, [webView stringByEvaluatingJavaScript:@"IPC.frameID.toString()"].intValue); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[1] instanceof ArrayBuffer"].boolValue, YES); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[2].type"].UTF8String, "String"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[2].value"].UTF8String, "ok"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"typeof(messages[0].syncRequestID)"].UTF8String, "number"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"messages[0].destinationID"].intValue, [webView stringByEvaluatingJavaScript:@"IPC.webPageProxyID.toString()"].intValue); } #if ENABLE(INTELLIGENT_TRACKING_PREVENTION) TEST(IPCTestingAPI, CanInterceptHasStorageAccess) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; done = false; promptResult = @"foo"; [webView synchronouslyLoadHTMLString:@""]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([alertMessage UTF8String], "[{\"type\":\"bool\",\"value\":false}]"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.description"].UTF8String, "NetworkConnectionToWebProcess_HasStorageAccess"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments.length"].intValue, 4); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[0].type"].UTF8String, "RegistrableDomain"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[0].value"].UTF8String, "ipctestingapi.com"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[1].type"].UTF8String, "RegistrableDomain"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[1].value"].UTF8String, "webkit.org"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[2].type"].UTF8String, "uint64_t"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[2].value"].UTF8String, [webView stringByEvaluatingJavaScript:@"IPC.frameID.toString()"].UTF8String); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[3].type"].UTF8String, "uint64_t"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[3].value"].intValue, [webView stringByEvaluatingJavaScript:@"IPC.pageID.toString()"].intValue); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"typeof(targetMessage.syncRequestID)"].UTF8String, "undefined"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"targetMessage.destinationID"].intValue, 0); } #endif TEST(IPCTestingAPI, CanInterceptFindString) { auto webView = createWebViewWithIPCTestingAPI(); auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); [webView setUIDelegate:delegate.get()]; [webView synchronouslyLoadHTMLString:@"

hello

"]; done = false; auto findConfiguration = adoptNS([[WKFindConfiguration alloc] init]); [webView findString:@"hello" withConfiguration:findConfiguration.get() completionHandler:^(WKFindResult *result) { EXPECT_TRUE(result.matchFound); EXPECT_TRUE([webView selectionRangeHasStartOffset:0 endOffset:5]); done = true; }]; TestWebKitAPI::Util::run(&done); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages = messages.filter((message) => message.name == IPC.messages.WebPage_FindString.name); messages.length"].UTF8String, "1"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"messages[0].description"].UTF8String, "WebPage_FindString"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args = messages[0].arguments; args.length"].intValue, 3); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[0].type"].UTF8String, "String"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[0].value"].UTF8String, "hello"); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[1].type"].UTF8String, "uint16_t"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[1].value"].intValue, 0x11); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[1].isOptionSet"].boolValue, YES); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"args[2].type"].UTF8String, "uint32_t"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"args[2].value"].intValue, 1); EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"typeof(messages[0].syncRequestID)"].UTF8String, "undefined"); EXPECT_EQ([webView stringByEvaluatingJavaScript:@"messages[0].destinationID"].intValue, [webView stringByEvaluatingJavaScript:@"IPC.webPageProxyID.toString()"].intValue); } #endif TEST(IPCTestingAPI, CGColorInNSSecureCoding) { auto archiver = adoptNS([[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]); RetainPtr> delegate = adoptNS([[NSClassFromString(@"WKSecureCodingArchivingDelegate") alloc] init]); archiver.get().delegate = delegate.get(); NSString *key = @"SomeString"; auto value = adoptCF(CGColorCreateSRGB(0.2, 0.3, 0.4, 0.5)); auto payload = @{ key : static_cast(value.get()) }; [archiver encodeObject:payload forKey:NSKeyedArchiveRootObjectKey]; [archiver finishEncoding]; [archiver setDelegate:nil]; auto data = [archiver encodedData]; auto unarchiver = adoptNS([[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nullptr]); unarchiver.get().decodingFailurePolicy = NSDecodingFailurePolicyRaiseException; unarchiver.get().delegate = delegate.get(); auto allowedClassSet = adoptNS([NSMutableSet new]); [allowedClassSet addObject:NSDictionary.class]; [allowedClassSet addObject:NSString.class]; [allowedClassSet addObject:NSClassFromString(@"WKSecureCodingCGColorWrapper")]; NSDictionary *result = [unarchiver decodeObjectOfClasses:allowedClassSet.get() forKey:NSKeyedArchiveRootObjectKey]; // Round-tripping the color can slightly change the representation, causing [payload isEqual:result] to report NO. EXPECT_EQ(result.count, static_cast(1)); NSString *resultKey = result.allKeys[0]; EXPECT_TRUE([key isEqual:resultKey]); CGColorRef resultValue = static_cast(result.allValues[0]); ASSERT_EQ(CFGetTypeID(resultValue), CGColorGetTypeID()); auto resultValueColorSpace = adoptCF(CGColorGetColorSpace(resultValue)); auto resultValueColorSpaceName = adoptCF(CGColorSpaceCopyName(resultValueColorSpace.get())); EXPECT_NE(CFStringFind(resultValueColorSpaceName.get(), CFSTR("SRGB"), 0).location, kCFNotFound); ASSERT_EQ(CGColorGetNumberOfComponents(resultValue), CGColorGetNumberOfComponents(value.get())); for (size_t i = 0; i < CGColorGetNumberOfComponents(resultValue); ++i) EXPECT_EQ(CGColorGetComponents(resultValue)[i], CGColorGetComponents(value.get())[i]); [unarchiver finishDecoding]; unarchiver.get().delegate = nil; }