| /* |
| * Copyright (C) 2021 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 "DaemonTestUtilities.h" |
| #import "HTTPServer.h" |
| #import "PlatformUtilities.h" |
| #import "Test.h" |
| #import "TestNotificationProvider.h" |
| #import "TestURLSchemeHandler.h" |
| #import "TestWKWebView.h" |
| #import "Utilities.h" |
| #import <WebKit/WKPreferencesPrivate.h> |
| #import <WebKit/WKProcessPoolPrivate.h> |
| #import <WebKit/WKUIDelegatePrivate.h> |
| #import <WebKit/WKWebsiteDataRecordPrivate.h> |
| #import <WebKit/WKWebsiteDataStorePrivate.h> |
| #import <WebKit/WebPushDaemonConstants.h> |
| #import <WebKit/_WKExperimentalFeature.h> |
| #import <WebKit/_WKWebsiteDataStoreConfiguration.h> |
| #import <mach/mach_init.h> |
| #import <mach/task.h> |
| #import <wtf/BlockPtr.h> |
| #import <wtf/text/Base64.h> |
| |
| #if ENABLE(NOTIFICATIONS) && ENABLE(NOTIFICATION_EVENT) && (PLATFORM(MAC) || PLATFORM(IOS)) |
| |
| using WebKit::WebPushD::MessageType; |
| |
| static bool alertReceived = false; |
| @interface NotificationPermissionDelegate : NSObject<WKUIDelegatePrivate> |
| @end |
| |
| @implementation NotificationPermissionDelegate |
| |
| - (void)_webView:(WKWebView *)webView requestNotificationPermissionForSecurityOrigin:(WKSecurityOrigin *)securityOrigin decisionHandler:(void (^)(BOOL))decisionHandler |
| { |
| decisionHandler(true); |
| } |
| |
| - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler |
| { |
| alertReceived = true; |
| completionHandler(); |
| } |
| |
| @end |
| |
| @interface NotificationScriptMessageHandler : NSObject<WKScriptMessageHandler> { |
| BlockPtr<void(id)> _messageHandler; |
| } |
| @end |
| |
| @implementation NotificationScriptMessageHandler |
| |
| - (void)setMessageHandler:(void (^)(id))handler |
| { |
| _messageHandler = handler; |
| } |
| |
| - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message |
| { |
| if (_messageHandler) |
| _messageHandler(message.body); |
| } |
| |
| @end |
| |
| namespace TestWebKitAPI { |
| |
| static RetainPtr<NSURL> testWebPushDaemonLocation() |
| { |
| return [currentExecutableDirectory() URLByAppendingPathComponent:@"webpushd" isDirectory:NO]; |
| } |
| |
| enum LaunchOnlyOnce : BOOL { No, Yes }; |
| |
| static NSDictionary<NSString *, id> *testWebPushDaemonPList(NSURL *storageLocation, LaunchOnlyOnce launchOnlyOnce) |
| { |
| return @{ |
| @"Label" : @"org.webkit.webpushtestdaemon", |
| @"LaunchOnlyOnce" : @(static_cast<BOOL>(launchOnlyOnce)), |
| @"ThrottleInterval" : @(1), |
| @"StandardErrorPath" : [storageLocation URLByAppendingPathComponent:@"daemon_stderr"].path, |
| @"EnvironmentVariables" : @{ @"DYLD_FRAMEWORK_PATH" : currentExecutableDirectory().get().path }, |
| @"MachServices" : @{ @"org.webkit.webpushtestdaemon.service" : @YES }, |
| @"ProgramArguments" : @[ |
| testWebPushDaemonLocation().get().path, |
| @"--machServiceName", |
| @"org.webkit.webpushtestdaemon.service", |
| @"--useMockPushService" |
| ] |
| }; |
| } |
| |
| static bool shouldSetupWebPushD() |
| { |
| static bool shouldSetup = true; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| NSArray<NSString *> *arguments = [[NSProcessInfo processInfo] arguments]; |
| if ([arguments containsObject:@"--no-webpushd"]) |
| shouldSetup = false; |
| }); |
| |
| return shouldSetup; |
| } |
| |
| static NSURL *setUpTestWebPushD(LaunchOnlyOnce launchOnlyOnce = LaunchOnlyOnce::Yes) |
| { |
| if (!shouldSetupWebPushD()) |
| return nil; |
| |
| NSFileManager *fileManager = [NSFileManager defaultManager]; |
| NSURL *tempDir = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"WebPushDaemonTest"] isDirectory:YES]; |
| NSError *error = nil; |
| if ([fileManager fileExistsAtPath:tempDir.path]) |
| [fileManager removeItemAtURL:tempDir error:&error]; |
| EXPECT_NULL(error); |
| |
| killFirstInstanceOfDaemon(@"webpushd"); |
| |
| registerPlistWithLaunchD(testWebPushDaemonPList(tempDir, launchOnlyOnce), tempDir); |
| |
| return tempDir; |
| } |
| |
| // Only works if the test daemon was registered with LaunchOnlyOnce::No. |
| static BOOL restartTestWebPushD() |
| { |
| return restartService(@"org.webkit.webpushtestdaemon", @"webpushd"); |
| } |
| |
| static void cleanUpTestWebPushD(NSURL *tempDir) |
| { |
| if (!shouldSetupWebPushD()) |
| return; |
| |
| killFirstInstanceOfDaemon(@"webpushd"); |
| |
| if (![[NSFileManager defaultManager] fileExistsAtPath:tempDir.path]) |
| return; |
| |
| NSError *error = nil; |
| [[NSFileManager defaultManager] removeItemAtURL:tempDir error:&error]; |
| |
| if (error) |
| NSLog(@"Error removing tempDir URL: %@", error); |
| |
| EXPECT_NULL(error); |
| } |
| |
| static RetainPtr<xpc_object_t> createMessageDictionary(MessageType messageType, const Vector<uint8_t>& message) |
| { |
| auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); |
| xpc_dictionary_set_uint64(dictionary.get(), "protocol version", 1); |
| xpc_dictionary_set_uint64(dictionary.get(), "message type", static_cast<uint64_t>(messageType)); |
| xpc_dictionary_set_data(dictionary.get(), "encoded message", message.data(), message.size()); |
| return WTFMove(dictionary); |
| } |
| |
| // Uses an existing connection to the daemon for a one-off message |
| void sendMessageToDaemon(xpc_connection_t connection, MessageType messageType, const Vector<uint8_t>& message) |
| { |
| auto dictionary = createMessageDictionary(messageType, message); |
| xpc_connection_send_message(connection, dictionary.get()); |
| } |
| |
| // Uses an existing connection to the daemon for a one-off message, waiting for the reply |
| void sendMessageToDaemonWaitingForReply(xpc_connection_t connection, MessageType messageType, const Vector<uint8_t>& message) |
| { |
| auto dictionary = createMessageDictionary(messageType, message); |
| |
| __block bool done = false; |
| xpc_connection_send_message_with_reply(connection, dictionary.get(), dispatch_get_main_queue(), ^(xpc_object_t request) { |
| done = true; |
| }); |
| |
| TestWebKitAPI::Util::run(&done); |
| } |
| |
| static void sendConfigurationWithAuditToken(xpc_connection_t connection) |
| { |
| audit_token_t token = { 0, 0, 0, 0, 0, 0, 0, 0 }; |
| mach_msg_type_number_t auditTokenCount = TASK_AUDIT_TOKEN_COUNT; |
| kern_return_t result = task_info(mach_task_self(), TASK_AUDIT_TOKEN, (task_info_t)(&token), &auditTokenCount); |
| if (result != KERN_SUCCESS) { |
| EXPECT_TRUE(false); |
| return; |
| } |
| |
| // Send configuration with audit token |
| { |
| Vector<uint8_t> encodedMessage(42); |
| encodedMessage.fill(0); |
| encodedMessage[1] = 1; |
| encodedMessage[2] = 32; |
| memcpy(&encodedMessage[10], &token, sizeof(token)); |
| sendMessageToDaemon(connection, MessageType::UpdateConnectionConfiguration, encodedMessage); |
| } |
| } |
| |
| RetainPtr<xpc_connection_t> createAndConfigureConnectionToService(const char* serviceName) |
| { |
| auto connection = adoptNS(xpc_connection_create_mach_service(serviceName, dispatch_get_main_queue(), 0)); |
| xpc_connection_set_event_handler(connection.get(), ^(xpc_object_t) { }); |
| xpc_connection_activate(connection.get()); |
| sendConfigurationWithAuditToken(connection.get()); |
| |
| return WTFMove(connection); |
| } |
| |
| static Vector<uint8_t> encodeString(const String& message) |
| { |
| ASSERT(message.is8Bit()); |
| auto utf8 = message.utf8(); |
| |
| Vector<uint8_t> result(utf8.length() + 5); |
| result[0] = static_cast<uint8_t>(utf8.length()); |
| result[1] = static_cast<uint8_t>(utf8.length() >> 8); |
| result[2] = static_cast<uint8_t>(utf8.length() >> 16); |
| result[3] = static_cast<uint8_t>(utf8.length() >> 24); |
| result[4] = 0x01; |
| |
| auto data = utf8.data(); |
| for (size_t i = 0; i < utf8.length(); ++i) |
| result[5 + i] = data[i]; |
| |
| return result; |
| } |
| |
| TEST(WebPushD, BasicCommunication) |
| { |
| NSURL *tempDir = setUpTestWebPushD(); |
| |
| auto connection = adoptNS(xpc_connection_create_mach_service("org.webkit.webpushtestdaemon.service", dispatch_get_main_queue(), 0)); |
| |
| __block bool done = false; |
| xpc_connection_set_event_handler(connection.get(), ^(xpc_object_t request) { |
| if (xpc_get_type(request) != XPC_TYPE_DICTIONARY) |
| return; |
| const char* debugMessage = xpc_dictionary_get_string(request, "debug message"); |
| if (!debugMessage) |
| return; |
| |
| NSString *nsMessage = [NSString stringWithUTF8String:debugMessage]; |
| |
| // Ignore possible connections/messages from webpushtool |
| if ([nsMessage hasPrefix:@"[webpushtool "]) |
| return; |
| |
| bool stringMatches = [nsMessage hasPrefix:@"[com.apple.WebKit.TestWebKitAPI"] || [nsMessage hasPrefix:@"[TestWebKitAPI"]; |
| stringMatches = stringMatches && [nsMessage hasSuffix:@" Turned Debug Mode on"]; |
| |
| EXPECT_TRUE(stringMatches); |
| if (!stringMatches) |
| WTFLogAlways("String does not match, actual string was %@", nsMessage); |
| |
| done = true; |
| }); |
| |
| xpc_connection_activate(connection.get()); |
| sendConfigurationWithAuditToken(connection.get()); |
| |
| // Enable debug messages, and wait for the resulting debug message |
| { |
| auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); |
| Vector<uint8_t> encodedMessage(1); |
| encodedMessage[0] = 1; |
| sendMessageToDaemon(connection.get(), MessageType::SetDebugModeIsEnabled, encodedMessage); |
| TestWebKitAPI::Util::run(&done); |
| } |
| |
| // Echo and wait for a reply |
| auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); |
| auto encodedString = encodeString("hello"_s); |
| xpc_dictionary_set_uint64(dictionary.get(), "protocol version", 1); |
| xpc_dictionary_set_uint64(dictionary.get(), "message type", 1); |
| xpc_dictionary_set_data(dictionary.get(), "encoded message", encodedString.data(), encodedString.size()); |
| |
| done = false; |
| xpc_connection_send_message_with_reply(connection.get(), dictionary.get(), dispatch_get_main_queue(), ^(xpc_object_t reply) { |
| if (xpc_get_type(reply) != XPC_TYPE_DICTIONARY) { |
| NSLog(@"Unexpected non-dictionary: %@", reply); |
| done = true; |
| EXPECT_TRUE(FALSE); |
| return; |
| } |
| |
| size_t dataSize = 0; |
| const void* data = xpc_dictionary_get_data(reply, "encoded message", &dataSize); |
| EXPECT_EQ(dataSize, 15u); |
| std::array<uint8_t, 15> expectedReply { 10, 0, 0, 0, 1, 'h', 'e', 'l', 'l', 'o' , 'h', 'e', 'l', 'l', 'o' }; |
| EXPECT_FALSE(memcmp(data, expectedReply.data(), expectedReply.size())); |
| done = true; |
| }); |
| TestWebKitAPI::Util::run(&done); |
| |
| cleanUpTestWebPushD(tempDir); |
| } |
| |
| static void clearWebsiteDataStore(WKWebsiteDataStore *store) |
| { |
| __block bool clearedStore = false; |
| [store removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() { |
| clearedStore = true; |
| }]; |
| TestWebKitAPI::Util::run(&clearedStore); |
| } |
| |
| class WebPushDTest : public ::testing::Test { |
| protected: |
| WebPushDTest(LaunchOnlyOnce launchOnlyOnce = LaunchOnlyOnce::Yes) |
| { |
| [WKWebsiteDataStore _allowWebsiteDataRecordsForAllOrigins]; |
| |
| m_tempDirectory = retainPtr(setUpTestWebPushD(launchOnlyOnce)); |
| |
| auto dataStoreConfiguration = adoptNS([_WKWebsiteDataStoreConfiguration new]); |
| dataStoreConfiguration.get().webPushMachServiceName = @"org.webkit.webpushtestdaemon.service"; |
| dataStoreConfiguration.get().webPushDaemonUsesMockBundlesForTesting = YES; |
| m_dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]); |
| |
| m_configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| m_configuration.get().websiteDataStore = m_dataStore.get(); |
| clearWebsiteDataStore([m_configuration websiteDataStore]); |
| |
| [m_configuration.get().preferences _setNotificationsEnabled:YES]; |
| [m_configuration.get().preferences _setPushAPIEnabled:YES]; |
| for (_WKExperimentalFeature *feature in [WKPreferences _experimentalFeatures]) { |
| if ([feature.key isEqualToString:@"BuiltInNotificationsEnabled"]) |
| [[m_configuration preferences] _setEnabled:YES forFeature:feature]; |
| } |
| |
| m_testMessageHandler = adoptNS([[TestMessageHandler alloc] init]); |
| [[m_configuration userContentController] addScriptMessageHandler:m_testMessageHandler.get() name:@"test"]; |
| |
| m_notificationMessageHandler = adoptNS([[NotificationScriptMessageHandler alloc] init]); |
| [[m_configuration userContentController] addScriptMessageHandler:m_notificationMessageHandler.get() name:@"note"]; |
| } |
| |
| void loadRequest(ASCIILiteral htmlSource, ASCIILiteral serviceWorkerScriptSource) |
| { |
| static constexpr auto constants = R"SRC( |
| const VALID_SERVER_KEY = "BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dRuU_RCPCfA5aq9ojSwk5Y2EmClBPs"; |
| const VALID_SERVER_KEY_THAT_CAUSES_INJECTED_FAILURE = "BEAxaUMo1s8tjORxJfnSSvWhYb4u51kg1hWT2s_9gpV7Zxar1pF_2BQ8AncuAdS2BoLhN4qaxzBy2CwHE8BBzWg"; |
| )SRC"_s; |
| m_server.reset(new TestWebKitAPI::HTTPServer({ |
| { "/"_s, { htmlSource } }, |
| { "/constants.js"_s, { { { "Content-Type"_s, "application/javascript"_s } }, constants } }, |
| { "/sw.js"_s, { { { "Content-Type"_s, "application/javascript"_s } }, serviceWorkerScriptSource } } |
| }, TestWebKitAPI::HTTPServer::Protocol::Http)); |
| |
| m_notificationProvider = makeUnique<TestWebKitAPI::TestNotificationProvider>(Vector<WKNotificationManagerRef> { [[m_configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() }); |
| m_notificationProvider->setPermission(m_server->origin(), true); |
| |
| m_webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:m_configuration.get()]); |
| m_uiDelegate = adoptNS([[NotificationPermissionDelegate alloc] init]); |
| [m_webView setUIDelegate:m_uiDelegate.get()]; |
| [m_webView loadRequest:m_server->request()]; |
| } |
| |
| bool hasPushSubscription() |
| { |
| __block bool done = false; |
| __block bool result = false; |
| |
| [m_dataStore _scopeURL:m_server->request().URL hasPushSubscriptionForTesting:^(BOOL fetchedResult) { |
| result = fetchedResult; |
| done = true; |
| }]; |
| |
| TestWebKitAPI::Util::run(&done); |
| return result; |
| } |
| |
| ~WebPushDTest() |
| { |
| cleanUpTestWebPushD(m_tempDirectory.get()); |
| } |
| |
| RetainPtr<NSDictionary> injectPushMessage(NSDictionary *pushUserInfo) |
| { |
| String scope = m_server->request().URL.absoluteString; |
| String topic = "com.apple.WebKit.TestWebKitAPI "_str + scope; |
| id obj = @{ |
| @"topic": (NSString *)topic, |
| @"userInfo": pushUserInfo |
| }; |
| NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:0 error:nullptr]; |
| |
| String message { static_cast<const char *>(data.bytes), static_cast<unsigned>(data.length) }; |
| auto encodedMessage = encodeString(WTFMove(message)); |
| |
| auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service"); |
| sendMessageToDaemonWaitingForReply(utilityConnection.get(), MessageType::InjectEncryptedPushMessageForTesting, encodedMessage); |
| |
| // Fetch push messages |
| __block bool gotMessages = false; |
| __block RetainPtr<NSArray<NSDictionary *>> messages; |
| [m_dataStore _getPendingPushMessages:^(NSArray<NSDictionary *> *rawMessages) { |
| messages = rawMessages; |
| gotMessages = true; |
| }]; |
| TestWebKitAPI::Util::run(&gotMessages); |
| |
| EXPECT_EQ([messages count], 1u); |
| |
| return [messages objectAtIndex:0]; |
| } |
| |
| RetainPtr<NSURL> m_tempDirectory; |
| RetainPtr<WKWebsiteDataStore> m_dataStore; |
| RetainPtr<WKWebViewConfiguration> m_configuration; |
| RetainPtr<TestMessageHandler> m_testMessageHandler; |
| RetainPtr<NotificationScriptMessageHandler> m_notificationMessageHandler; |
| std::unique_ptr<TestWebKitAPI::HTTPServer> m_server; |
| std::unique_ptr<TestWebKitAPI::TestNotificationProvider> m_notificationProvider; |
| RetainPtr<WKWebView> m_webView; |
| RetainPtr<id<WKUIDelegatePrivate>> m_uiDelegate; |
| }; |
| |
| class WebPushDInjectedPushTest : public WebPushDTest { |
| protected: |
| void runTest(NSString *expectedMessage, NSDictionary *pushUserInfo); |
| }; |
| |
| class WebPushDMultipleLaunchTest : public WebPushDTest { |
| public: |
| WebPushDMultipleLaunchTest() |
| : WebPushDTest(LaunchOnlyOnce::No) |
| { |
| } |
| }; |
| |
| void WebPushDInjectedPushTest::runTest(NSString *expectedMessage, NSDictionary *pushUserInfo) |
| { |
| static constexpr auto htmlSource = R"SWRESOURCE( |
| <script src="/constants.js"></script> |
| <script> |
| function log(msg) |
| { |
| window.webkit.messageHandlers.test.postMessage(msg); |
| } |
| |
| const channel = new MessageChannel(); |
| channel.port1.onmessage = (event) => log(event.data); |
| |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| registration.active.postMessage({port: channel.port2}, [channel.port2]); |
| }).catch(function(error) { |
| log("Registration failed with: " + error); |
| }); |
| </script> |
| )SWRESOURCE"_s; |
| |
| static constexpr auto serviceWorkerSource = R"SWRESOURCE( |
| let port; |
| self.addEventListener("message", (event) => { |
| port = event.data.port; |
| port.postMessage("Ready"); |
| }); |
| self.addEventListener("push", (event) => { |
| try { |
| self.registration.showNotification("notification"); |
| if (!event.data) { |
| port.postMessage("Received: null data"); |
| return; |
| } |
| const value = event.data.text(); |
| port.postMessage("Received: " + value); |
| } catch (e) { |
| port.postMessage("Error: " + e); |
| } |
| }); |
| )SWRESOURCE"_s; |
| |
| __block bool ready = false; |
| [m_testMessageHandler addMessage:@"Ready" withHandler:^{ |
| ready = true; |
| }]; |
| |
| __block bool gotExpectedMessage = false; |
| [m_testMessageHandler addMessage:expectedMessage withHandler:^{ |
| gotExpectedMessage = true; |
| }]; |
| |
| loadRequest(htmlSource, serviceWorkerSource); |
| TestWebKitAPI::Util::run(&ready); |
| |
| auto message = injectPushMessage(pushUserInfo); |
| |
| __block bool pushMessageProcessed = false; |
| __block bool pushMessageProcessedResult = false; |
| [m_dataStore _processPushMessage:message.get() completionHandler:^(bool result) { |
| pushMessageProcessedResult = result; |
| pushMessageProcessed = true; |
| }]; |
| TestWebKitAPI::Util::run(&gotExpectedMessage); |
| TestWebKitAPI::Util::run(&pushMessageProcessed); |
| |
| EXPECT_TRUE(pushMessageProcessedResult); |
| } |
| |
| TEST_F(WebPushDInjectedPushTest, HandleInjectedEmptyPush) |
| { |
| runTest(@"Received: null data", @{ }); |
| } |
| |
| TEST_F(WebPushDInjectedPushTest, HandleInjectedAESGCMPush) |
| { |
| runTest(@"Received: test aesgcm payload", @{ |
| @"content_encoding": @"aesgcm", |
| @"as_publickey": @"BC-AgYMhqmzamH7_Aum0YvId8FV1-umgHweJNe6XQ1IMAm3E29loWXqTRndibxH27kJKWcIbyymundODMfVx_UM", |
| @"as_salt": @"tkPT5xDeN0lAkSc6lZUkNg", |
| @"payload": @"o/u4yvcXI1nap+zyIOBbWXdLqj1qHG2cX+KVhAdBQj1GVAt7lQ==" |
| }); |
| } |
| |
| TEST_F(WebPushDInjectedPushTest, HandleInjectedAES128GCMPush) |
| { |
| // From example in RFC8291 Section 5. |
| String payloadBase64URL = "DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPTpK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN"_s; |
| String payloadBase64 = base64EncodeToString(base64URLDecode(payloadBase64URL).value()); |
| |
| runTest(@"Received: When I grow up, I want to be a watermelon", @{ |
| @"content_encoding": @"aes128gcm", |
| @"payload": (NSString *)payloadBase64 |
| }); |
| } |
| |
| TEST_F(WebPushDTest, SubscribeTest) |
| { |
| static constexpr auto source = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| result = subscription.toJSON(); |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> obj = nil; |
| __block bool done = false; |
| [m_notificationMessageHandler setMessageHandler:^(id message) { |
| obj = message; |
| done = true; |
| }]; |
| |
| loadRequest(source, ""_s); |
| TestWebKitAPI::Util::run(&done); |
| |
| ASSERT_TRUE([obj isKindOfClass:[NSDictionary class]]); |
| |
| NSDictionary *subscription = obj.get(); |
| ASSERT_TRUE([subscription[@"endpoint"] hasPrefix:@"https://"]); |
| ASSERT_TRUE([subscription[@"keys"] isKindOfClass:[NSDictionary class]]); |
| |
| // Shared auth secret should be 16 bytes (22 bytes in unpadded base64url). |
| ASSERT_EQ([subscription[@"keys"][@"auth"] length], 22u); |
| |
| // Client public key should be 65 bytes (87 bytes in unpadded base64url). |
| ASSERT_EQ([subscription[@"keys"][@"p256dh"] length], 87u); |
| |
| ASSERT_TRUE(hasPushSubscription()); |
| } |
| |
| TEST_F(WebPushDTest, SubscribeFailureTest) |
| { |
| static constexpr auto source = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY_THAT_CAUSES_INJECTED_FAILURE |
| }); |
| result = subscription.toJSON(); |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> obj = nil; |
| __block bool done = false; |
| [m_notificationMessageHandler setMessageHandler:^(id message) { |
| obj = message; |
| done = true; |
| }]; |
| |
| loadRequest(source, ""_s); |
| TestWebKitAPI::Util::run(&done); |
| |
| // Spec says that an error in the push service should be an AbortError. |
| ASSERT_TRUE([obj isKindOfClass:[NSString class]]); |
| ASSERT_TRUE([obj hasPrefix:@"Error: AbortError"]); |
| |
| ASSERT_FALSE(hasPushSubscription()); |
| } |
| |
| TEST_F(WebPushDTest, UnsubscribeTest) |
| { |
| static constexpr auto source = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| let result1 = await subscription.unsubscribe(); |
| let result2 = await subscription.unsubscribe(); |
| result = [result1, result2]; |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> obj = nil; |
| __block bool done = false; |
| [m_notificationMessageHandler setMessageHandler:^(id message) { |
| obj = message; |
| done = true; |
| }]; |
| |
| loadRequest(source, ""_s); |
| TestWebKitAPI::Util::run(&done); |
| |
| // First unsubscribe should succeed. Second one should fail since the first one removed the record from the database. |
| id expected = @[@(1), @(0)]; |
| ASSERT_TRUE([obj isEqual:expected]); |
| |
| ASSERT_FALSE(hasPushSubscription()); |
| } |
| |
| TEST_F(WebPushDTest, UnsubscribesOnServiceWorkerUnregisterTest) |
| { |
| static constexpr auto source = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| result = await registration.unregister(); |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> unregisterSucceeded = nil; |
| __block bool done = false; |
| [m_notificationMessageHandler setMessageHandler:^(id message) { |
| unregisterSucceeded = message; |
| done = true; |
| }]; |
| |
| loadRequest(source, ""_s); |
| TestWebKitAPI::Util::run(&done); |
| |
| ASSERT_TRUE([unregisterSucceeded isEqual:@YES]); |
| ASSERT_FALSE(hasPushSubscription()); |
| } |
| |
| TEST_F(WebPushDTest, UnsubscribesOnClearingAllWebsiteData) |
| { |
| static constexpr auto source = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| result = "Subscribed"; |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> result = nil; |
| __block bool done = false; |
| [m_notificationMessageHandler setMessageHandler:^(id message) { |
| result = message; |
| done = true; |
| }]; |
| |
| loadRequest(source, ""_s); |
| TestWebKitAPI::Util::run(&done); |
| |
| ASSERT_TRUE([result isEqualToString:@"Subscribed"]); |
| |
| __block bool removedData = false; |
| [m_dataStore removeDataOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] modifiedSince:[NSDate distantPast] completionHandler:^(void) { |
| removedData = true; |
| }]; |
| TestWebKitAPI::Util::run(&removedData); |
| |
| ASSERT_FALSE(hasPushSubscription()); |
| } |
| |
| TEST_F(WebPushDTest, UnsubscribesOnClearingWebsiteDataForOrigin) |
| { |
| static constexpr auto source = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| result = "Subscribed"; |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> result = nil; |
| __block bool done = false; |
| [m_notificationMessageHandler setMessageHandler:^(id message) { |
| result = message; |
| done = true; |
| }]; |
| |
| loadRequest(source, ""_s); |
| TestWebKitAPI::Util::run(&done); |
| |
| ASSERT_TRUE([result isEqualToString:@"Subscribed"]); |
| |
| __block bool fetchedRecords = false; |
| __block RetainPtr<NSArray<WKWebsiteDataRecord *>> records; |
| [m_dataStore fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) { |
| records = dataRecords; |
| fetchedRecords = true; |
| }]; |
| TestWebKitAPI::Util::run(&fetchedRecords); |
| |
| WKWebsiteDataRecord *filteredRecord = nil; |
| for (WKWebsiteDataRecord *record in records.get()) { |
| for (NSString *originString in record._originsStrings) { |
| if ([originString isEqualToString:m_server->origin()]) { |
| filteredRecord = record; |
| break; |
| } |
| } |
| } |
| ASSERT_TRUE(filteredRecord); |
| |
| __block bool removedData = false; |
| [m_dataStore removeDataOfTypes:[NSSet setWithObject:WKWebsiteDataTypeServiceWorkerRegistrations] forDataRecords:[NSArray arrayWithObject:filteredRecord] completionHandler:^(void) { |
| removedData = true; |
| }]; |
| TestWebKitAPI::Util::run(&removedData); |
| |
| ASSERT_FALSE(hasPushSubscription()); |
| } |
| |
| TEST_F(WebPushDTest, UnsubscribesOnPermissionReset) |
| { |
| static constexpr auto source = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| let subscription = await registration.pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| result = "Subscribed"; |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> result = nil; |
| __block bool done = false; |
| [m_notificationMessageHandler setMessageHandler:^(id message) { |
| result = message; |
| done = true; |
| }]; |
| |
| loadRequest(source, ""_s); |
| TestWebKitAPI::Util::run(&done); |
| |
| ASSERT_TRUE([result isEqualToString:@"Subscribed"]); |
| ASSERT_TRUE(hasPushSubscription()); |
| |
| m_notificationProvider->resetPermission(m_server->origin()); |
| |
| bool isSubscribed = true; |
| TestWebKitAPI::Util::waitForConditionWithLogging([this, &isSubscribed] { |
| isSubscribed = hasPushSubscription(); |
| if (!isSubscribed) |
| return true; |
| |
| sleep(1); |
| return false; |
| }, 5, @"Timed out waiting for push subscription to be removed."); |
| |
| ASSERT_FALSE(isSubscribed); |
| } |
| |
| #if ENABLE(INSTALL_COORDINATION_BUNDLES) |
| #if USE(APPLE_INTERNAL_SDK) |
| TEST(WebPushD, PermissionManagement) |
| { |
| NSURL *tempDirectory = setUpTestWebPushD(); |
| |
| auto dataStoreConfiguration = adoptNS([_WKWebsiteDataStoreConfiguration new]); |
| dataStoreConfiguration.get().webPushMachServiceName = @"org.webkit.webpushtestdaemon.service"; |
| dataStoreConfiguration.get().webPushDaemonUsesMockBundlesForTesting = YES; |
| auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]); |
| |
| auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| configuration.get().websiteDataStore = dataStore.get(); |
| [configuration.get().preferences _setNotificationsEnabled:YES]; |
| for (_WKExperimentalFeature *feature in [WKPreferences _experimentalFeatures]) { |
| if ([feature.key isEqualToString:@"BuiltInNotificationsEnabled"]) |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| } |
| |
| auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration.get()]); |
| auto uiDelegate = adoptNS([[NotificationPermissionDelegate alloc] init]); |
| [webView setUIDelegate:uiDelegate.get()]; |
| [webView synchronouslyLoadHTMLString:@"" baseURL:[NSURL URLWithString:@"https://example.org"]]; |
| [webView evaluateJavaScript:@"Notification.requestPermission().then(() => { alert('done') })" completionHandler:nil]; |
| TestWebKitAPI::Util::run(&alertReceived); |
| |
| static bool originOperationDone = false; |
| static RetainPtr<WKSecurityOrigin> origin; |
| [dataStore _getOriginsWithPushAndNotificationPermissions:^(NSSet<WKSecurityOrigin *> *origins) { |
| EXPECT_EQ([origins count], 1u); |
| origin = [origins anyObject]; |
| originOperationDone = true; |
| }]; |
| |
| TestWebKitAPI::Util::run(&originOperationDone); |
| |
| EXPECT_WK_STREQ(origin.get().protocol, "https"); |
| EXPECT_WK_STREQ(origin.get().host, "example.org"); |
| |
| // If we failed to retrieve an expected origin, we will have failed the above checks |
| if (!origin) { |
| cleanUpTestWebPushD(tempDirectory); |
| return; |
| } |
| |
| originOperationDone = false; |
| [dataStore _deletePushAndNotificationRegistration:origin.get() completionHandler:^(NSError *error) { |
| EXPECT_FALSE(!!error); |
| originOperationDone = true; |
| }]; |
| |
| TestWebKitAPI::Util::run(&originOperationDone); |
| |
| originOperationDone = false; |
| [dataStore _getOriginsWithPushAndNotificationPermissions:^(NSSet<WKSecurityOrigin *> *origins) { |
| EXPECT_EQ([origins count], 0u); |
| originOperationDone = true; |
| }]; |
| TestWebKitAPI::Util::run(&originOperationDone); |
| |
| cleanUpTestWebPushD(tempDirectory); |
| } |
| |
| static void deleteAllRegistrationsForDataStore(WKWebsiteDataStore *dataStore) |
| { |
| __block bool originOperationDone = false; |
| __block RetainPtr<NSSet<WKSecurityOrigin *>> originSet; |
| [dataStore _getOriginsWithPushAndNotificationPermissions:^(NSSet<WKSecurityOrigin *> *origins) { |
| originSet = origins; |
| originOperationDone = true; |
| }]; |
| TestWebKitAPI::Util::run(&originOperationDone); |
| |
| if (![originSet count]) |
| return; |
| |
| __block size_t deletedOrigins = 0; |
| originOperationDone = false; |
| for (WKSecurityOrigin *origin in originSet.get()) { |
| [dataStore _deletePushAndNotificationRegistration:origin completionHandler:^(NSError *error) { |
| EXPECT_FALSE(!!error); |
| if (++deletedOrigins == [originSet count]) |
| originOperationDone = true; |
| }]; |
| } |
| TestWebKitAPI::Util::run(&originOperationDone); |
| |
| originOperationDone = false; |
| [dataStore _getOriginsWithPushAndNotificationPermissions:^(NSSet<WKSecurityOrigin *> *origins) { |
| EXPECT_EQ([origins count], 0u); |
| originOperationDone = true; |
| }]; |
| TestWebKitAPI::Util::run(&originOperationDone); |
| |
| } |
| |
| static const char* mainBytes = R"WEBPUSHRESOURCE( |
| <script> |
| Notification.requestPermission().then(() => { alert("done") }) |
| </script> |
| )WEBPUSHRESOURCE"; |
| |
| TEST(WebPushD, InstallCoordinationBundles) |
| { |
| NSURL *tempDirectory = setUpTestWebPushD(); |
| |
| auto dataStoreConfiguration = adoptNS([_WKWebsiteDataStoreConfiguration new]); |
| dataStoreConfiguration.get().webPushMachServiceName = @"org.webkit.webpushtestdaemon.service"; |
| auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]); |
| |
| deleteAllRegistrationsForDataStore(dataStore.get()); |
| |
| auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| configuration.get().websiteDataStore = dataStore.get(); |
| [configuration.get().preferences _setNotificationsEnabled:YES]; |
| for (_WKExperimentalFeature *feature in [WKPreferences _experimentalFeatures]) { |
| if ([feature.key isEqualToString:@"BuiltInNotificationsEnabled"]) |
| [[configuration preferences] _setEnabled:YES forFeature:feature]; |
| } |
| |
| auto handler = adoptNS([[TestURLSchemeHandler alloc] init]); |
| [configuration setURLSchemeHandler:handler.get() forURLScheme:@"testing"]; |
| |
| [handler setStartURLSchemeTaskHandler:^(WKWebView *, id<WKURLSchemeTask> task) { |
| auto response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil]); |
| [task didReceiveResponse:response.get()]; |
| [task didReceiveData:[NSData dataWithBytes:mainBytes length:strlen(mainBytes)]]; |
| [task didFinish]; |
| }]; |
| |
| auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration.get()]); |
| auto uiDelegate = adoptNS([[NotificationPermissionDelegate alloc] init]); |
| [webView setUIDelegate:uiDelegate.get()]; |
| |
| [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"testing://main/index.html"]]]; |
| TestWebKitAPI::Util::run(&alertReceived); |
| |
| alertReceived = false; |
| [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"testing://secondary/index.html"]]]; |
| TestWebKitAPI::Util::run(&alertReceived); |
| |
| static bool originOperationDone = false; |
| static RetainPtr<NSSet<WKSecurityOrigin *>> origins; |
| [dataStore _getOriginsWithPushAndNotificationPermissions:^(NSSet<WKSecurityOrigin *> *rawOrigins) { |
| EXPECT_EQ([rawOrigins count], 2u); |
| origins = rawOrigins; |
| originOperationDone = true; |
| }]; |
| TestWebKitAPI::Util::run(&originOperationDone); |
| |
| for (WKSecurityOrigin *origin in origins.get()) { |
| EXPECT_TRUE([origin.protocol isEqualToString:@"testing"]); |
| EXPECT_TRUE([origin.host isEqualToString:@"main"] || [origin.host isEqualToString:@"secondary"]); |
| } |
| |
| deleteAllRegistrationsForDataStore(dataStore.get()); |
| cleanUpTestWebPushD(tempDirectory); |
| } |
| #endif // #if USE(APPLE_INTERNAL_SDK) |
| #endif // ENABLE(INSTALL_COORDINATION_BUNDLES) |
| |
| TEST_F(WebPushDTest, TooManySilentPushesCausesUnsubscribe) |
| { |
| static constexpr auto htmlSource = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| let pushManager = null; |
| |
| navigator.serviceWorker.register('/sw.js').then(async () => { |
| const registration = await navigator.serviceWorker.ready; |
| let result = null; |
| try { |
| pushManager = registration.pushManager; |
| let subscription = await pushManager.subscribe({ |
| userVisibleOnly: true, |
| applicationServerKey: VALID_SERVER_KEY |
| }); |
| result = "Subscribed"; |
| } catch (e) { |
| result = "Error: " + e; |
| } |
| window.webkit.messageHandlers.note.postMessage(result); |
| }); |
| |
| function getPushSubscription() |
| { |
| pushManager.getSubscription().then((subscription) => { |
| window.webkit.messageHandlers.note.postMessage(subscription ? "Subscribed" : "Unsubscribed"); |
| }); |
| } |
| </script> |
| )HTML"_s; |
| static constexpr auto serviceWorkerScriptSource = "self.addEventListener('push', (event) => { });"_s; |
| |
| __block RetainPtr<id> message = nil; |
| __block bool gotMessage = false; |
| [m_notificationMessageHandler setMessageHandler:^(id receivedMessage) { |
| message = receivedMessage; |
| gotMessage = true; |
| }]; |
| |
| loadRequest(htmlSource, serviceWorkerScriptSource); |
| |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqualToString:@"Subscribed"]); |
| |
| for (unsigned i = 0; i < WebKit::WebPushD::maxSilentPushCount; i++) { |
| gotMessage = false; |
| [m_webView evaluateJavaScript:@"getPushSubscription()" completionHandler:^(id, NSError*) { }]; |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqualToString:@"Subscribed"]); |
| |
| __block bool processedPush = false; |
| __block bool pushResult = false; |
| auto message = injectPushMessage(@{ }); |
| |
| [m_dataStore _processPushMessage:message.get() completionHandler:^(bool result) { |
| pushResult = result; |
| processedPush = true; |
| }]; |
| TestWebKitAPI::Util::run(&processedPush); |
| |
| // WebContent should fail processing the push since no notification was shown. |
| EXPECT_FALSE(pushResult); |
| } |
| |
| gotMessage = false; |
| [m_webView evaluateJavaScript:@"getPushSubscription()" completionHandler:^(id, NSError*) { }]; |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqualToString:@"Unsubscribed"]); |
| } |
| |
| TEST_F(WebPushDTest, GetPushSubscriptionWithMismatchedPublicToken) |
| { |
| static constexpr auto htmlSource = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| let postNoteMessage = window.webkit.messageHandlers.note.postMessage.bind(window.webkit.messageHandlers.note); |
| let getPushManager = |
| navigator.serviceWorker.register('/sw.js') |
| .then(() => navigator.serviceWorker.ready) |
| .then(registration => registration.pushManager); |
| |
| function subscribe() |
| { |
| getPushManager |
| .then(pushManager => pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VALID_SERVER_KEY })) |
| .then(subscription => subscription.toJSON()) |
| .then(postNoteMessage) |
| .catch(e => postNoteMessage(e.toString())); |
| } |
| |
| function getSubscription() |
| { |
| getPushManager |
| .then(pushManager => pushManager.getSubscription()) |
| .then(subscription => subscription ? subscription.toJSON() : null) |
| .then(postNoteMessage) |
| .catch(e => postNoteMessage(e.toString())); |
| } |
| |
| postNoteMessage('Ready'); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> message = nil; |
| __block bool gotMessage = false; |
| [m_notificationMessageHandler setMessageHandler:^(id receivedMessage) { |
| message = receivedMessage; |
| gotMessage = true; |
| }]; |
| |
| loadRequest(htmlSource, ""_s); |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqualToString:@"Ready"]); |
| |
| message = nil; |
| gotMessage = false; |
| [m_webView evaluateJavaScript:@"subscribe()" completionHandler:^(id, NSError*) { }]; |
| TestWebKitAPI::Util::run(&gotMessage); |
| RetainPtr<id> subscription = message; |
| ASSERT_FALSE([subscription isEqual:[NSNull null]]); |
| ASSERT_TRUE([subscription isKindOfClass:[NSDictionary class]] && [subscription objectForKey:@"endpoint"]); |
| |
| message = nil; |
| gotMessage = false; |
| [m_webView evaluateJavaScript:@"getSubscription()" completionHandler:^(id, NSError*) { }]; |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqual:subscription.get()]); |
| |
| // If the public token changes, all subscriptions should be invalidated. |
| auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service"); |
| sendMessageToDaemonWaitingForReply(utilityConnection.get(), MessageType::SetPublicTokenForTesting, encodeString("foobar"_s)); |
| |
| message = nil; |
| gotMessage = false; |
| [m_webView evaluateJavaScript:@"getSubscription()" completionHandler:^(id, NSError*) { }]; |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqual:[NSNull null]]); |
| } |
| |
| TEST_F(WebPushDMultipleLaunchTest, GetPushSubscriptionAfterDaemonRelaunch) |
| { |
| static constexpr auto htmlSource = R"HTML( |
| <script src="/constants.js"></script> |
| <script> |
| let postNoteMessage = window.webkit.messageHandlers.note.postMessage.bind(window.webkit.messageHandlers.note); |
| let getPushManager = |
| navigator.serviceWorker.register('/sw.js') |
| .then(() => navigator.serviceWorker.ready) |
| .then(registration => registration.pushManager); |
| |
| function getSubscription() |
| { |
| getPushManager |
| .then(pushManager => pushManager.getSubscription()) |
| .then(subscription => subscription ? subscription.toJSON() : null) |
| .then(postNoteMessage) |
| .catch(e => postNoteMessage(e.toString())); |
| } |
| |
| postNoteMessage('Ready'); |
| </script> |
| )HTML"_s; |
| |
| __block RetainPtr<id> message = nil; |
| __block bool gotMessage = false; |
| [m_notificationMessageHandler setMessageHandler:^(id receivedMessage) { |
| message = receivedMessage; |
| gotMessage = true; |
| }]; |
| |
| loadRequest(htmlSource, ""_s); |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqualToString:@"Ready"]); |
| |
| message = nil; |
| gotMessage = false; |
| [m_webView evaluateJavaScript:@"getSubscription()" completionHandler:^(id, NSError*) { }]; |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqual:[NSNull null]]); |
| |
| ASSERT_TRUE(restartTestWebPushD()); |
| |
| // Make sure that getSubscription works after killing webpushd. Previously, this didn't work and |
| // would fail with an AbortError because we didn't re-send the connection configuration after |
| // the daemon relaunched. |
| message = nil; |
| gotMessage = false; |
| [m_webView evaluateJavaScript:@"getSubscription()" completionHandler:^(id, NSError*) { }]; |
| TestWebKitAPI::Util::run(&gotMessage); |
| ASSERT_TRUE([message isEqual:[NSNull null]]); |
| } |
| |
| } // namespace TestWebKitAPI |
| |
| #endif // ENABLE(NOTIFICATIONS) && ENABLE(NOTIFICATION_EVENT) && (PLATFORM(MAC) || PLATFORM(IOS)) |