blob: 76bec68bf466adfed7fa48fc7c6dfef6a8bf875f [file] [log] [blame]
/*
* 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 "TestURLSchemeHandler.h"
#import "TestWKWebView.h"
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKUIDelegatePrivate.h>
#import <WebKit/_WKExperimentalFeature.h>
#import <mach/mach_init.h>
#import <mach/task.h>
#if PLATFORM(MAC) || PLATFORM(IOS)
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
namespace TestWebKitAPI {
static RetainPtr<NSURL> testWebPushDaemonLocation()
{
return [currentExecutableDirectory() URLByAppendingPathComponent:@"webpushd" isDirectory:NO];
}
#if HAVE(OS_LAUNCHD_JOB)
static RetainPtr<xpc_object_t> testWebPushDaemonPList(NSURL *storageLocation)
{
auto currentDirectory = currentExecutableDirectory();
auto plist = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0));
xpc_dictionary_set_string(plist.get(), "_ManagedBy", "TestWebKitAPI");
xpc_dictionary_set_string(plist.get(), "Label", "org.webkit.webpushtestdaemon");
xpc_dictionary_set_bool(plist.get(), "LaunchOnlyOnce", true);
xpc_dictionary_set_bool(plist.get(), "RootedSimulatorPath", true);
xpc_dictionary_set_string(plist.get(), "StandardErrorPath", [storageLocation URLByAppendingPathComponent:@"daemon_stderr"].path.fileSystemRepresentation);
{
auto environmentVariables = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0));
xpc_dictionary_set_string(environmentVariables.get(), "DYLD_FRAMEWORK_PATH", currentDirectory.get().fileSystemRepresentation);
xpc_dictionary_set_value(plist.get(), "EnvironmentVariables", environmentVariables.get());
}
{
auto machServices = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0));
xpc_dictionary_set_bool(machServices.get(), "org.webkit.webpushtestdaemon.service", true);
xpc_dictionary_set_value(plist.get(), "MachServices", machServices.get());
}
{
auto programArguments = adoptNS(xpc_array_create(nullptr, 0));
auto executableLocation = testWebPushDaemonLocation();
#if PLATFORM(MAC)
xpc_array_set_string(programArguments.get(), XPC_ARRAY_APPEND, executableLocation.get().fileSystemRepresentation);
#else
xpc_array_set_string(programArguments.get(), XPC_ARRAY_APPEND, [currentDirectory URLByAppendingPathComponent:@"webpushd"].path.fileSystemRepresentation);
#endif
xpc_array_set_string(programArguments.get(), XPC_ARRAY_APPEND, "--machServiceName");
xpc_array_set_string(programArguments.get(), XPC_ARRAY_APPEND, "org.webkit.webpushtestdaemon.service");
xpc_dictionary_set_value(plist.get(), "ProgramArguments", programArguments.get());
}
return plist;
}
#else // HAVE(OS_LAUNCHD_JOB)
static RetainPtr<NSDictionary> testWebPushDaemonPList(NSURL *storageLocation)
{
return @{
@"Label" : @"org.webkit.pcmtestdaemon",
@"LaunchOnlyOnce" : @YES,
@"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"
]
};
}
#endif // HAVE(OS_LAUNCHD_JOB)
static NSURL *setUpTestWebPushD()
{
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");
auto plist = testWebPushDaemonPList(tempDir);
#if HAVE(OS_LAUNCHD_JOB)
registerPlistWithLaunchD(WTFMove(plist));
#else
registerPlistWithLaunchD(WTFMove(plist), tempDir);
#endif
return tempDir;
}
static void cleanUpTestWebPushD(NSURL *tempDir)
{
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);
}
// FIXME: Re-enable this test for Monterey+ once webkit.org/232857 is resolved.
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 110000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
TEST(WebPushD, DISABLED_BasicCommunication)
#else
TEST(WebPushD, BasicCommunication)
#endif
{
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;
bool stringMatches = !strcmp(debugMessage, "[webpushd - TestWebKitAPI] Turned Debug Mode on");
if (!stringMatches)
stringMatches = !strcmp(debugMessage, "[webpushd - com.apple.WebKit.TestWebKitAPI] Turned Debug Mode on");
EXPECT_TRUE(stringMatches);
done = true;
});
xpc_connection_activate(connection.get());
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 audit token
{
std::array<uint8_t, 40> encodedMessage;
encodedMessage.fill(0);
encodedMessage[0] = 32;
memcpy(&encodedMessage[8], &token, sizeof(token));
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", 5);
xpc_dictionary_set_data(dictionary.get(), "encoded message", encodedMessage.data(), encodedMessage.size());
xpc_connection_send_message(connection.get(), dictionary.get());
}
// Enable debug messages, and wait for the resulting debug message
{
auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0));
std::array<uint8_t, 1> encodedMessage { 1 };
xpc_dictionary_set_uint64(dictionary.get(), "protocol version", 1);
xpc_dictionary_set_uint64(dictionary.get(), "message type", 6);
xpc_dictionary_set_data(dictionary.get(), "encoded message", encodedMessage.data(), encodedMessage.size());
xpc_connection_send_message(connection.get(), dictionary.get());
TestWebKitAPI::Util::run(&done);
}
// Echo and wait for a reply
auto dictionary = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0));
std::array<uint8_t, 10> encodedString { 5, 0, 0, 0, 1, 'h', 'e', 'l', 'l', 'o' };
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 const char* mainBytes = R"WEBPUSHRESOURCE(
<script>
Notification.requestPermission().then(() => { alert("done") })
</script>
)WEBPUSHRESOURCE";
// FIXME: Re-enable this test for Monterey+ once webkit.org/232857 is resolved.
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 110000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
TEST(WebPushD, DISABLED_PermissionManagement)
#else
TEST(WebPushD, PermissionManagement)
#endif
{
NSURL *tempDirectory = setUpTestWebPushD();
auto dataStoreConfiguration = adoptNS([_WKWebsiteDataStoreConfiguration new]);
dataStoreConfiguration.get().webPushMachServiceName = @"org.webkit.webpushtestdaemon.service";
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 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);
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_TRUE([origin.get().protocol isEqualToString:@"testing"]);
EXPECT_TRUE([origin.get().host isEqualToString:@"main"]);
// 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);
}
} // namespace TestWebKitAPI
#endif // PLATFORM(MAC) || PLATFORM(IOS)