| /* |
| * 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 "PlatformUtilities.h" |
| #import "RemoteObjectRegistry.h" |
| #import "TestWKWebView.h" |
| #import "Utilities.h" |
| #import <WebKit/WKNavigationDelegatePrivate.h> |
| #import <WebKit/WKPreferencesPrivate.h> |
| #import <WebKit/WKWebView.h> |
| #import <WebKit/WKWebViewPrivate.h> |
| #import <WebKit/_WKFeature.h> |
| #import <WebKit/_WKRemoteObjectInterface.h> |
| #import <WebKit/_WKRemoteObjectRegistry.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/cocoa/TypeCastsCocoa.h> |
| |
| static bool didCrash = false; |
| static RetainPtr<NSString> alertMessage; |
| static RetainPtr<NSString> promptDefault; |
| static RetainPtr<NSString> promptResult; |
| |
| @interface IPCTestingAPIDelegate : NSObject <WKUIDelegate, WKNavigationDelegate> |
| - (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:@"<!DOCTYPE html><script>alert(typeof(IPC));</script>"]; |
| 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<TestWKWebView> createWebViewWithIPCTestingAPI() |
| { |
| RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| for (_WKFeature *feature in [WKPreferences _features]) { |
| if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) { |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| break; |
| } |
| } |
| return adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get()]); |
| } |
| |
| static RetainPtr<TestWKWebView> createWebViewWithIPCTestingAPIAndLockdownMode(bool lockdownModeEnabled) |
| { |
| RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| |
| for (_WKFeature *feature in [WKPreferences _features]) { |
| if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) { |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| break; |
| } |
| } |
| |
| if (lockdownModeEnabled) { |
| [WKProcessPool _setCaptivePortalModeEnabledGloballyForTesting:YES]; |
| |
| RetainPtr<WKWebpagePreferences> webpagePreferences = adoptNS([[WKWebpagePreferences alloc] init]); |
| [webpagePreferences setLockdownModeEnabled:YES]; |
| [configuration setDefaultWebpagePreferences:webpagePreferences.get()]; |
| } else { |
| [WKProcessPool _setCaptivePortalModeEnabledGloballyForTesting:NO]; |
| |
| RetainPtr<WKWebpagePreferences> webpagePreferences = adoptNS([[WKWebpagePreferences alloc] init]); |
| [webpagePreferences setLockdownModeEnabled:NO]; |
| [configuration setDefaultWebpagePreferences:webpagePreferences.get()]; |
| } |
| |
| return adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get()]); |
| } |
| |
| // FIX ME: Re-enable this test once https://bugs.webkit.org/show_bug.cgi?id=300930 is resolved |
| #if PLATFORM(MAC) && CPU(X86_64) && !defined(NDEBUG) |
| TEST(IPCTestingAPI, DISABLED_CanDetectNilReplyBlocks) |
| #else |
| TEST(IPCTestingAPI, CanDetectNilReplyBlocks) |
| #endif |
| { |
| 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:@"<!DOCTYPE html><script>buf = new Uint8Array([" |
| // Strings in this buffer are encoded as follows: |
| // string length, 3 NUL bytes, 0x1 byte, then string contents |
| // For example, this string is 0x14 length (20 bytes), 3 NUL bytes + 0x1, then "RemoteObjectProtocol" |
| "0x14,0x0,0x0,0x0,0x1,0x52,0x65,0x6d,0x6f,0x74,0x65,0x4f,0x62,0x6a,0x65,0x63,0x74,0x50,0x72,0x6f,0x74,0x6f,0x63,0x6f,0x6c," |
| // padding + "invocation" |
| "0x0,0x0,0x0,0x9,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa,0x0,0x0,0x0,0x1,0x69,0x6e,0x76,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e," |
| // a serialized object + "typeString" |
| "0x0,0x9,0x0,0x0,0x0,0xf5,0xeb,0x54,0xa9,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa,0x0,0x0,0x0,0x1,0x74,0x79,0x70,0x65,0x53,0x74,0x72,0x69,0x6e,0x67,0x0," |
| // a zeroed object + "$string" |
| "0x9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x1,0x24,0x73,0x74,0x72,0x69,0x6e,0x67,0x15,0x0,0x0,0x0," |
| // "v@:@.@.?" (an objective-C method signature) + "class" |
| "0x6,0x0,0x0,0x0,0x1,0x76,0x40,0x3a,0x40,0x40,0x3f,0x0,0x6,0x0,0x0,0x0,0x1,0x24,0x63,0x6c,0x61,0x73,0x73,0x0," |
| // "NSString" + "selector" |
| "0x15,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x1,0x4e,0x53,0x53,0x74,0x72,0x69,0x6e,0x67,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x1,0x73,0x65,0x6c,0x65,0x63,0x74,0x6f,0x72,0x0,0x0,0x0," |
| // a zeroed object + "$string" |
| "0x9,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x1,0x24,0x73,0x74,0x72,0x69,0x6e,0x67,0x15,0x0,0x0,0x0," |
| // "sayHello:completionHandler:" (method name we're trying to call) |
| "0x1b,0x0,0x0,0x0,0x1,0x73,0x61,0x79,0x48,0x65,0x6c,0x6c,0x6f,0x3a,0x63,0x6f,0x6d,0x70,0x6c,0x65,0x74,0x69,0x6f,0x6e,0x48,0x61,0x6e,0x64,0x6c,0x65,0x72,0x3a," |
| // "$class" + "NSString" |
| "0x6,0x0,0x0,0x0,0x1,0x24,0x63,0x6c,0x61,0x73,0x73,0x0,0x15,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x1,0x4e,0x53,0x53,0x74,0x72,0x69,0x6e,0x67,0x0,0x0,0x0," |
| // "$class" + "NSInvocation" |
| "0x6,0x0,0x0,0x0,0x1,0x24,0x63,0x6c,0x61,0x73,0x73,0x0,0x15,0x0,0x0,0x0,0xc,0x0,0x0,0x0,0x1,0x4e,0x53,0x49,0x6e,0x76,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x0,0x0,0x0," |
| // "$objectStam" + zero object |
| "0xd,0x0,0x0,0x0,0x1,0x24,0x6f,0x62,0x6a,0x65,0x63,0x74,0x53,0x74,0x61,0x6d,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0," |
| // zeroed objects + ".NS.uuidbytes" |
| "0x9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc,0x0,0x0,0x0,0x91,0x4e,0x53,0x2e,0x75,0x75,0x69,0x64,0x62,0x79,0x74,0x65,0x73,0x0,0x0,0x0," |
| // some zeroed objects |
| "0x8,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x29,0xc5,0x6d,0x2,0x13,0xa,0x4e,0xe7,0xaa,0xac,0x8,0x55,0xf2,0x66,0x2c,0x7c," |
| // "$class" + "NSUUID" |
| "0x6,0x0,0x0,0x0,0x1,0x24,0x63,0x6c,0x61,0x73,0x73,0x0,0x15,0x0,0x0,0x0,0x6,0x0,0x0,0x0,0x1,0x4e,0x53,0x55,0x55,0x49,0x44,0x0,0x0,0x0," |
| // mostly zero objects + "v@?c" (objective-C method signature) |
| "0x0,0x0,0x1,0x0,0x0,0x0,0x2c,0x0,0x0,0x0,0x59,0x1,0x0,0x0,0x0,0x9b,0x0,0x0,0x4,0x0,0x0,0x0,0x1,0x76,0x40,0x3f,0x63,0x0,]);" |
| "for(var x=0; x<100; x++) IPC.sendMessage('UI', x, IPC.messages.RemoteObjectRegistry_InvokeMethod.name, [buf]);</script>"]; |
| TestWebKitAPI::Util::runFor(&done, 1_s); |
| |
| // Make sure sayHello was not called, as the reply block was nil. |
| EXPECT_FALSE([delegate.get() sayHelloWasCalled]); |
| } |
| |
| TEST(IPCTestingAPI, CanSendAlert) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>IPC.sendSyncMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_RunJavaScriptAlert.name, 100," |
| "[{type: 'FrameID', value: IPC.frameID}, {type: 'FrameInfoData', value: IPC}, {'type': 'String', 'value': 'hi'}]);</script>"]; |
| 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:@"<!DOCTYPE html><script>alert(IPC.messages.WebPageProxy_RunJavaScriptAlert.isSync);</script>"]; |
| 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:@"<!DOCTYPE html><script>" |
| "IPC.sendMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_ShowShareSheet.name, []);" |
| "alert('hi')</script>"]; |
| 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:@"<!DOCTYPE html><script>" |
| "try{IPC.sendSyncMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_RunJavaScriptAlert.name, 100, [{type: 'FrameID', value: IPC.frameID}]);}catch(e){alert(e.message)}" |
| "</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([alertMessage UTF8String], "Receiver cancelled the reply due to invalid destination or deserialization error"); |
| } |
| |
| #if ENABLE(GPU_PROCESS) |
| |
| TEST(IPCTestingAPI, CanSendSyncMessageToGPUProcess) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "result = !!IPC.sendSyncMessage('GPU', 0, IPC.messages.GPUConnectionToWebProcess_EnsureAudioSession.name, 100, []);" |
| "alert(result)</script>"]; |
| 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:@"<!DOCTYPE html><script>(function test() {" |
| "let c = IPC.connectionForProcessTarget('GPU');" |
| "let cb = (result) => { window.result = result; alert(!!result); };" |
| "c.sendWithAsyncReply(0, IPC.messages.RemoteAudioDestinationManager_StartAudioDestination.name, [{type: 'uint64_t', value: 12345}], cb);" |
| "})();</script>"]; |
| 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:@"<!DOCTYPE html><script>(function test() {" |
| "let c = IPC.connectionForProcessTarget('GPU');" |
| "c.sendMessage(0, IPC.messages.GPUConnectionToWebProcess_CreateRenderingBackend.name, []);" |
| "let cb = (result) => { window.result = result; alert(!!result); };" |
| "c.sendWithAsyncReply(0, IPC.messages.RemoteAudioDestinationManager_StartAudioDestination.name, [{type: 'uint64_t', value: 12345}], cb);" |
| "})();</script>"]; |
| 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:@"<!DOCTYPE html><script>const sharedMemory = IPC.createSharedMemory(8); alert(sharedMemory.toString());</script>"]; |
| 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(<!DOCTYPE html> |
| <body> |
| <script> |
| const sharedMemory = IPC.createSharedMemory(8); |
| sharedMemory.writeBytes(new Uint8Array(Array.from('hello').map((char) => char.charCodeAt(0)))); |
| const result = IPC.sendSyncMessage('UI', 0, IPC.messages.WebPasteboardProxy_TestIPCSharedMemory.name, 100, [ |
| {type: 'String', value: 'Apple CFPasteboard general'}, |
| {type: 'String', value: 'text/plain'}, |
| {type: 'SharedMemory', value: sharedMemory, protection: 'ReadOnly'}, |
| {type: 'bool', value: 1}, {type: 'uint64_t', value: IPC.pageID}]); |
| alert(result.arguments.length + ':' + JSON.stringify(result.arguments[0]) + ',' + JSON.stringify(result.arguments[1])); |
| </script> |
| </body>)HTML"; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:html]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([alertMessage UTF8String], "2:{\"type\":\"int64_t\",\"value\":8},{\"type\":\"String\",\"value\":\"hello\\u0000\\u0000\\u0000\"}"); |
| } |
| #endif |
| |
| TEST(IPCTestingAPI, DecodesReplyArgumentsForPrompt) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| promptResult = @"foo"; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>result = IPC.sendSyncMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_RunJavaScriptPrompt.name, 100," |
| "[{type: 'FrameID', value: IPC.frameID}, {type: 'FrameInfoData', value: IPC}, {'type': 'String', 'value': 'hi'}, {'type': 'String', 'value': 'bar'}]);</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([promptDefault UTF8String], "bar"); |
| EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"JSON.stringify(result.arguments)"] UTF8String], "[{\"type\":\"String\",\"value\":\"foo\"}]"); |
| } |
| |
| TEST(IPCTestingAPI, DecodesReplyArgumentsForAsyncMessage) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "let c = IPC.connectionForProcessTarget('Networking');" |
| "let cb = (result) => alert(JSON.stringify(result.arguments));" |
| "c.sendWithAsyncReply(0, IPC.messages.NetworkConnectionToWebProcess_HasStorageAccess.name," |
| "[{type: 'RegistrableDomain', value: 'https://ipctestingapi.com'}, {type: 'RegistrableDomain', value: 'https://webkit.org'}, {type: 'FrameID', value: IPC.frameID}," |
| "{type: 'uint64_t', value: IPC.pageID}], cb);</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([alertMessage UTF8String], "[{\"type\":\"bool\",\"value\":false}]"); |
| } |
| |
| TEST(IPCTestingAPI, EmptyParametersDeleteCookie) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "let c = IPC.connectionForProcessTarget('Networking');" |
| "let cb = (result) => alert(JSON.stringify(result.arguments));" |
| "c.sendWithAsyncReply(0, IPC.messages.NetworkConnectionToWebProcess_DeleteCookie.name," |
| "[{type: 'URL', value: ''}," |
| "{type: 'URL', value: 'https://www.url.com'}," |
| "{type: 'String', value: 'a=b'}], cb);</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([alertMessage UTF8String], "[]"); |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "let c = IPC.connectionForProcessTarget('Networking');" |
| "let cb = (result) => alert(JSON.stringify(result.arguments));" |
| "c.sendWithAsyncReply(0, IPC.messages.NetworkConnectionToWebProcess_DeleteCookie.name," |
| "[{type: 'URL', value: 'https://www.firstparty.com'}," |
| "{type: 'URL', value: ''}," |
| "{type: 'String', value: 'a=b'}], cb);</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([alertMessage UTF8String], "[]"); |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "let c = IPC.connectionForProcessTarget('Networking');" |
| "let cb = (result) => alert(JSON.stringify(result.arguments));" |
| "c.sendWithAsyncReply(0, IPC.messages.NetworkConnectionToWebProcess_DeleteCookie.name," |
| "[{type: 'URL', value: 'https://www.firstparty.com'}," |
| "{type: 'URL', value: 'https://www.url.com'}," |
| "{type: 'String', value: ''}], cb);</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([alertMessage UTF8String], "[]"); |
| } |
| |
| TEST(IPCTestingAPI, InvalidURLsDeleteCookie) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "let c = IPC.connectionForProcessTarget('Networking');" |
| "let cb = (result) => alert(JSON.stringify(result.arguments));" |
| "c.sendWithAsyncReply(0, IPC.messages.NetworkConnectionToWebProcess_DeleteCookie.name," |
| "[{type: 'URL', value: 'firstparty.com'}," |
| "{type: 'URL', value: 'url.com'}," |
| "{type: 'String', value: 'a=b'}], cb);</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_STREQ([alertMessage UTF8String], "[]"); |
| } |
| |
| TEST(IPCTestingAPI, EmptyFirstPartyForCookiesCookieRequestHeaderFieldValue) |
| { |
| RetainPtr webView = createWebViewWithIPCTestingAPI(); |
| RetainPtr delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>document.cookie='a=b';</script>" baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| auto sendMessage = @"const connection = IPC.connectionForProcessTarget('Networking');" |
| "const result = connection.sendSyncMessage(" |
| " 0," |
| " IPC.messages.NetworkConnectionToWebProcess_CookieRequestHeaderFieldValue.name," |
| " 1000," |
| " [" |
| " {type: 'String', value: null}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'String', value: location.href}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'FrameID', value: IPC.frameID}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint64_t', value: IPC.pageID}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint64_t', value: IPC.webPageProxyID}," |
| " ]" |
| ");"; |
| [webView evaluateJavaScript:sendMessage completionHandler:nil]; |
| while (![webView objectByEvaluatingJavaScript:@"result"]) |
| TestWebKitAPI::Util::spinRunLoop(); |
| EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"result.arguments[0].value"] UTF8String], "<null>"); |
| } |
| |
| TEST(IPCTestingAPI, InvalidSameSiteInfoCookieRequestHeaderFieldValue) |
| { |
| RetainPtr webView = createWebViewWithIPCTestingAPI(); |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>document.cookie='a=b';</script>" baseURL:[NSURL URLWithString:@"https://webkit.org/"]]; |
| [webView synchronouslyLoadHTMLString:@"" baseURL:[NSURL URLWithString:@"https://apple.com/"]]; |
| auto sendMessage = @"const connection = IPC.connectionForProcessTarget('Networking');" |
| "const result = connection.sendSyncMessage(" |
| " 0," |
| " IPC.messages.NetworkConnectionToWebProcess_CookieRequestHeaderFieldValue.name," |
| " 1000," |
| " [" |
| " {type: 'String', value: location.href}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'String', value: 'https://webkit.org'}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'FrameID', value: IPC.frameID}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint64_t', value: IPC.pageID}," |
| " {type: 'uint8_t', value: 1}," |
| " {type: 'uint64_t', value: IPC.webPageProxyID}," |
| " ]" |
| ");"; |
| [webView evaluateJavaScript:sendMessage completionHandler:nil]; |
| while (![webView objectByEvaluatingJavaScript:@"result"]) |
| TestWebKitAPI::Util::spinRunLoop(); |
| EXPECT_STREQ([[webView stringByEvaluatingJavaScript:@"result.arguments[0].value"] UTF8String], "<null>"); |
| } |
| |
| TEST(IPCTestingAPI, DescribesArguments) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>window.args = IPC.messages.WebPageProxy_RunJavaScriptAlert.arguments; alert('ok')</script>"]; |
| 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:@"<!DOCTYPE html><script>messages = []; IPC.addOutgoingMessageListener('UI', (message) => messages.push(message)); alert('ok');</script>"]; |
| 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_NE([webView stringByEvaluatingJavaScript:@"args[0].value"].intValue, 0); |
| 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); |
| } |
| |
| TEST(IPCTestingAPI, CanInterceptHasStorageAccess) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| promptResult = @"foo"; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>let targetMessage = {}; const messageName = IPC.messages.NetworkConnectionToWebProcess_HasStorageAccess.name;" |
| "IPC.addOutgoingMessageListener('Networking', (currentMessage) => { if (currentMessage.name == messageName) targetMessage = currentMessage; });" |
| "let c = IPC.connectionForProcessTarget('Networking');" |
| "let cb = (result) => alert(JSON.stringify(result.arguments));" |
| "c.sendWithAsyncReply(0, messageName, [{type: 'RegistrableDomain', value: 'https://ipctestingapi.com'}, {type: 'RegistrableDomain', value: 'https://webkit.org'}," |
| "{type: 'FrameID', value: IPC.frameID}, {type: 'uint64_t', value: IPC.pageID}], cb);</script>"]; |
| 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_NE([webView stringByEvaluatingJavaScript:@"targetMessage.arguments[2].value"].intValue, 0); |
| 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); |
| } |
| |
| TEST(IPCTestingAPI, CanInterceptFindString) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><body><p>hello</p><script>messages = []; IPC.addIncomingMessageListener('UI', (message) => messages.push(message));</script>"]; |
| |
| 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); |
| } |
| |
| TEST(IPCTestingAPI, SerializedTypeInfo) |
| { |
| auto webView = createWebViewWithIPCTestingAPI(); |
| NSDictionary *typeInfo = [webView objectByEvaluatingJavaScript:@"IPC.serializedTypeInfo"]; |
| NSArray *expectedArray = @[@{ |
| @"name": @"ignoreSearch", |
| @"type": @"bool" |
| }, @{ |
| @"name": @"ignoreMethod", |
| @"type": @"bool" |
| }, @{ |
| @"name": @"ignoreVary", |
| @"type": @"bool" |
| }]; |
| EXPECT_TRUE([typeInfo[@"WebCore::CacheQueryOptions"] isEqualToArray:expectedArray]); |
| |
| NSDictionary *expectedDictionary = @{ |
| @"isOptionSet" : @1, |
| @"size" : @1, |
| @"validValues" : @[@1, @2], |
| @"valueMap" : @[@{@"value": @1, @"name": @"ComputeSizes"}, @{@"value": @2, @"name": @"DoNotCreateProcesses"}] |
| }; |
| NSDictionary *enumInfo = [webView objectByEvaluatingJavaScript:@"IPC.serializedEnumInfo"]; |
| EXPECT_TRUE([enumInfo[@"WebKit::WebsiteDataFetchOption"] isEqualToDictionary:expectedDictionary]); |
| NSDictionary *expectedMouseEventButtonDictionary = @{ |
| @"isOptionSet" : @NO, |
| @"size" : @1, |
| @"validValues" : @[@0, @1, @2, @3, @4, @254], |
| @"valueMap" : @[@{@"value": @0, @"name": @"Left"}, @{@"value": @1, @"name": @"Middle"}, @{@"value": @2, @"name": @"Right"}, @{@"value": @3, @"name": @"Back"}, @{@"value": @4, @"name": @"Forward"}, @{@"value": @254, @"name": @"None"}] |
| }; |
| EXPECT_TRUE([enumInfo[@"WebKit::WebMouseEventButton"] isEqualToDictionary:expectedMouseEventButtonDictionary]); |
| |
| NSArray *objectIdentifiers = [webView objectByEvaluatingJavaScript:@"IPC.objectIdentifiers"]; |
| EXPECT_TRUE([objectIdentifiers containsObject:@"WebCore::PageIdentifier"]); |
| } |
| |
| TEST(IPCTestingAPI, LockdownModeDisablesWebGL) |
| { |
| auto webView = createWebViewWithIPCTestingAPIAndLockdownMode(true); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "const canvas = document.createElement('canvas');" |
| "const gl = canvas.getContext('webgl');" |
| "alert(gl === null ? 'webgl_disabled' : 'webgl_enabled');" |
| "</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_TRUE([alertMessage isEqualToString:@"webgl_disabled"]); |
| |
| [WKProcessPool _clearCaptivePortalModeEnabledGloballyForTesting]; |
| } |
| |
| TEST(IPCTestingAPI, LockdownModeDisabledAllowsWebGL) |
| { |
| auto webView = createWebViewWithIPCTestingAPIAndLockdownMode(false); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| done = false; |
| [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><script>" |
| "const canvas = document.createElement('canvas');" |
| "const gl = canvas.getContext('webgl');" |
| "alert(gl !== null ? 'webgl_enabled' : 'webgl_disabled');" |
| "</script>"]; |
| TestWebKitAPI::Util::run(&done); |
| |
| EXPECT_TRUE([alertMessage isEqualToString:@"webgl_enabled"]); |
| } |
| |
| TEST(IPCTestingAPI, LockdownModeDetection) |
| { |
| // Test with lockdown mode enabled |
| { |
| auto webViewLockdown = createWebViewWithIPCTestingAPIAndLockdownMode(true); |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webViewLockdown setUIDelegate:delegate.get()]; |
| |
| [webViewLockdown synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><body>Test</body></html>"]; |
| |
| NSString *webglResult = [webViewLockdown stringByEvaluatingJavaScript:@"(function() { try { const canvas = document.createElement('canvas'); return canvas.getContext('webgl') === null; } catch(e) { return true; } })()"]; |
| NSLog(@"WebGL disabled in lockdown mode: %@", webglResult); |
| |
| NSString *webgpuResult = [webViewLockdown stringByEvaluatingJavaScript:@"(function() { try { return typeof navigator.gpu === 'undefined'; } catch(e) { return true; } })()"]; |
| NSLog(@"WebGPU disabled in lockdown mode: %@", webgpuResult); |
| |
| NSString *speechResult = [webViewLockdown stringByEvaluatingJavaScript:@"(function() { try { return typeof webkitSpeechRecognition === 'undefined' && typeof SpeechRecognition === 'undefined'; } catch(e) { return true; } })()"]; |
| NSLog(@"Speech Recognition disabled in lockdown mode: %@", speechResult); |
| |
| NSString *disabledCountResult = [webViewLockdown stringByEvaluatingJavaScript:@"(function() { " |
| "let count = 0; " |
| "try { const canvas = document.createElement('canvas'); if (canvas.getContext('webgl') === null) count++; } catch(e) { count++; } " |
| "try { if (typeof navigator.gpu === 'undefined') count++; } catch(e) { count++; } " |
| "try { if (typeof webkitSpeechRecognition === 'undefined' && typeof SpeechRecognition === 'undefined') count++; } catch(e) { count++; } " |
| "return count; " |
| "})()"]; |
| |
| int disabledCount = [disabledCountResult intValue]; |
| NSLog(@"Total disabled APIs in lockdown mode: %d", disabledCount); |
| |
| EXPECT_GT(disabledCount, 0); |
| } |
| |
| // Test with lockdown mode disabled |
| { |
| auto webViewNormal = createWebViewWithIPCTestingAPIAndLockdownMode(false); |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webViewNormal setUIDelegate:delegate.get()]; |
| |
| [webViewNormal synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><body>Test</body></html>"]; |
| |
| NSLog(@"Testing normal mode - checking API availability directly..."); |
| |
| NSString *webglResult = [webViewNormal stringByEvaluatingJavaScript:@"(function() { try { const canvas = document.createElement('canvas'); return canvas.getContext('webgl') !== null; } catch(e) { return false; } })()"]; |
| NSLog(@"WebGL available in normal mode: %@", webglResult); |
| |
| NSString *webgpuResult = [webViewNormal stringByEvaluatingJavaScript:@"(function() { try { return typeof navigator.gpu !== 'undefined'; } catch(e) { return false; } })()"]; |
| NSLog(@"WebGPU available in normal mode: %@", webgpuResult); |
| |
| NSString *speechResult = [webViewNormal stringByEvaluatingJavaScript:@"(function() { try { return typeof webkitSpeechRecognition !== 'undefined' || typeof SpeechRecognition !== 'undefined'; } catch(e) { return false; } })()"]; |
| NSLog(@"Speech Recognition available in normal mode: %@", speechResult); |
| |
| NSString *availableCountResult = [webViewNormal stringByEvaluatingJavaScript:@"(function() { " |
| "let count = 0; " |
| "try { const canvas = document.createElement('canvas'); if (canvas.getContext('webgl') !== null) count++; } catch(e) { } " |
| "try { if (typeof navigator.gpu !== 'undefined') count++; } catch(e) { } " |
| "try { if (typeof webkitSpeechRecognition !== 'undefined' || typeof SpeechRecognition !== 'undefined') count++; } catch(e) { } " |
| "return count; " |
| "})()"]; |
| |
| int availableCount = [availableCountResult intValue]; |
| NSLog(@"Total available APIs in normal mode: %d", availableCount); |
| |
| NSLog(@"Normal mode API availability check completed (count: %d)", availableCount); |
| } |
| |
| [WKProcessPool _setCaptivePortalModeEnabledGloballyForTesting:NO]; |
| } |
| |
| TEST(IPCTestingAPI, SpeechSynthesisWithFeatureFlag) |
| { |
| // Test 1: Feature flag enabled - message should succeed |
| { |
| auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| for (_WKFeature *feature in [WKPreferences _features]) { |
| if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| if ([feature.key isEqualToString:@"SpeechSynthesisAPIEnabled"]) |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| } |
| auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| NSURL *htmlURL = [NSBundle.test_resourcesBundle URLForResource:@"speechsynthesis_feature_test" withExtension:@"html"]; |
| [webView loadRequest:[NSURLRequest requestWithURL:htmlURL]]; |
| |
| done = false; |
| TestWebKitAPI::Util::runFor(&done, 10_s); |
| |
| NSLog(@"SpeechSynthesis feature test (enabled) result: %@", alertMessage.get()); |
| |
| EXPECT_TRUE(alertMessage.get() != nil); |
| EXPECT_TRUE([alertMessage containsString:@"speechsynthesis_message_sent_successfully"]); |
| } |
| |
| // Test 2: Feature flag disabled - message should fail with cancel error |
| { |
| auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| for (_WKFeature *feature in [WKPreferences _features]) { |
| if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| if ([feature.key isEqualToString:@"SpeechSynthesisAPIEnabled"]) |
| [[configuration preferences] _setEnabled:NO forFeature:feature]; |
| } |
| auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| NSURL *htmlURL = [NSBundle.test_resourcesBundle URLForResource:@"speechsynthesis_feature_test" withExtension:@"html"]; |
| [webView loadRequest:[NSURLRequest requestWithURL:htmlURL]]; |
| |
| done = false; |
| TestWebKitAPI::Util::runFor(&done, 10_s); |
| |
| NSLog(@"SpeechSynthesis feature test (disabled) result: %@", alertMessage.get()); |
| |
| EXPECT_TRUE(alertMessage.get() != nil); |
| EXPECT_TRUE([alertMessage containsString:@"speechsynthesis_enabledby_blocked"] |
| && [alertMessage containsString:@"Receiver cancelled the reply due to invalid destination or deserialization error"]); |
| } |
| } |
| |
| TEST(IPCTestingAPI, SpeechSynthesisWithLockdownMode) |
| { |
| [WKProcessPool _setCaptivePortalModeEnabledGloballyForTesting:YES]; |
| |
| auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| for (_WKFeature *feature in [WKPreferences _features]) { |
| if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| if ([feature.key isEqualToString:@"SpeechSynthesisAPIEnabled"]) { |
| // Even with feature enabled, lockdown mode should disable it |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| } |
| } |
| |
| RetainPtr<WKWebpagePreferences> webpagePreferences = adoptNS([[WKWebpagePreferences alloc] init]); |
| [webpagePreferences setLockdownModeEnabled:YES]; |
| [configuration setDefaultWebpagePreferences:webpagePreferences.get()]; |
| |
| auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); |
| |
| auto delegate = adoptNS([[IPCTestingAPIDelegate alloc] init]); |
| [webView setUIDelegate:delegate.get()]; |
| |
| NSURL *htmlURL = [NSBundle.test_resourcesBundle URLForResource:@"speechsynthesis_lockdown_test" withExtension:@"html"]; |
| [webView loadRequest:[NSURLRequest requestWithURL:htmlURL]]; |
| |
| done = false; |
| TestWebKitAPI::Util::runFor(&done, 10_s); |
| |
| NSLog(@"SpeechSynthesis lockdown test result: %@", alertMessage.get()); |
| |
| EXPECT_TRUE(alertMessage.get() != nil); |
| EXPECT_TRUE([alertMessage containsString:@"speechsynthesis_lockdown_correctly_blocked"] |
| && [alertMessage containsString:@"Receiver cancelled the reply due to invalid destination or deserialization error"]); |
| |
| [WKProcessPool _setCaptivePortalModeEnabledGloballyForTesting:NO]; |
| } |
| |
| #endif |
| |
| #if !HAVE(WK_SECURE_CODING_NSURLREQUEST) |
| TEST(IPCTestingAPI, CGColorInNSSecureCoding) |
| { |
| auto archiver = adoptNS([[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]); |
| |
| RetainPtr<id<NSKeyedArchiverDelegate, NSKeyedUnarchiverDelegate>> 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<id>(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<NSUInteger>(1)); |
| NSString *resultKey = result.allKeys[0]; |
| EXPECT_TRUE([key isEqual:resultKey]); |
| CGColorRef resultValue = static_cast<CGColorRef>(result.allValues[0]); |
| ASSERT_EQ(CFGetTypeID(resultValue), CGColorGetTypeID()); |
| RetainPtr resultValueColorSpace = 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; |
| } |
| |
| TEST(IPCTestingAPI, NSURLWithBaseURLInNSSecureCoding) |
| { |
| auto archiver = adoptNS([[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]); |
| |
| RetainPtr<id<NSKeyedArchiverDelegate, NSKeyedUnarchiverDelegate>> delegate = adoptNS([[NSClassFromString(@"WKSecureCodingArchivingDelegate") alloc] init]); |
| archiver.get().delegate = delegate.get(); |
| |
| NSString *key = @"SomeString"; |
| NSURL *value = [NSURL URLWithString:@"/garden_home.html" relativeToURL:[NSURL URLWithString:@"amcomponent://com.xunmeng.pinduoduo/"]]; |
| EXPECT_WK_STREQ(value.baseURL.absoluteString, @"amcomponent://com.xunmeng.pinduoduo/"); |
| EXPECT_WK_STREQ(value.relativeString, @"/garden_home.html"); |
| EXPECT_WK_STREQ(value.absoluteString, @"amcomponent://com.xunmeng.pinduoduo/garden_home.html"); |
| |
| auto payload = @{ key : static_cast<id>(value) }; |
| [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(@"WKSecureCodingURLWrapper")]; |
| |
| NSDictionary *result = [unarchiver decodeObjectOfClasses:allowedClassSet.get() forKey:NSKeyedArchiveRootObjectKey]; |
| |
| EXPECT_EQ(result.count, static_cast<NSUInteger>(1)); |
| NSString *resultKey = result.allKeys[0]; |
| EXPECT_TRUE([key isEqual:resultKey]); |
| RetainPtr resultValue = checked_objc_cast<NSURL>(result.allValues[0]); |
| |
| // Our coder resolves the URL so we end up with an absolute URL instead of base URL + relative string. |
| EXPECT_WK_STREQ(resultValue.get().baseURL.absoluteString, @""); |
| EXPECT_WK_STREQ(resultValue.get().baseURL.relativeString, @""); |
| EXPECT_WK_STREQ(resultValue.get().absoluteString, @"amcomponent://com.xunmeng.pinduoduo/garden_home.html"); |
| [unarchiver finishDecoding]; |
| unarchiver.get().delegate = nil; |
| } |
| #endif // !HAVE(WK_SECURE_CODING_NSURLREQUEST) |