blob: d539efafcf34aad294207c0eeb426f3ba4328a9e [file] [log] [blame] [edit]
/*
* Copyright (C) 2022 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 "PlatformUtilities.h"
#import "TestWKWebView.h"
#import <WebKit/WKContext.h>
#import <WebKit/WKGeolocationManager.h>
#import <WebKit/WKGeolocationPosition.h>
#import <WebKit/WKSecurityOriginRef.h>
#import <WebKit/WKString.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <wtf/text/StringBuilder.h>
static bool didReceiveMessage;
static bool didReceiveQueryPermission;
static WKPermissionDecision queryPermissionDelegateResult;
static RetainPtr<WKScriptMessage> scriptMessage;
static bool didReceiveGeolocationRequest;
static bool requestGeolocationPermissionDelegateResult;
enum class RequestGeolocationSincePageLoad : bool { No, Yes };
enum class GeolocationPermissionState {
GrantedPermanently,
DeniedPermanently,
GrantedFromPrompt,
DeniedFromPrompt
};
@interface PermissionsAPIMessageHandler : NSObject <WKScriptMessageHandler>
@end
@implementation PermissionsAPIMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
scriptMessage = message;
didReceiveMessage = true;
}
@end
@interface PermissionsAPIUIDelegate : NSObject<WKUIDelegate>
- (void)_webView:(WKWebView *)webView queryPermission:(NSString*) name forOrigin:(WKSecurityOrigin *)origin completionHandler:(void (^)(WKPermissionDecision state))completionHandler;
@end
@implementation PermissionsAPIUIDelegate
- (void)_webView:(WKWebView *)webView queryPermission:(NSString*) name forOrigin:(WKSecurityOrigin *)origin completionHandler:(void (^)(WKPermissionDecision state))completionHandler {
didReceiveQueryPermission = true;
completionHandler(queryPermissionDelegateResult);
}
@end
@interface PermissionsAPIAndGeolocationRequestUIDelegate : PermissionsAPIUIDelegate
- (void)_webView:(WKWebView *)webView requestGeolocationPermissionForFrame:(WKFrameInfo *)frame decisionHandler:(void (^)(BOOL allowed))decisionHandler;
@end
@implementation PermissionsAPIAndGeolocationRequestUIDelegate
- (void)_webView:(WKWebView *)webView requestGeolocationPermissionForFrame:(WKFrameInfo *)frame decisionHandler:(void (^)(BOOL allowed))decisionHandler {
didReceiveGeolocationRequest = true;
decisionHandler(requestGeolocationPermissionDelegateResult);
}
@end
namespace TestWebKitAPI {
static void urlEncodeIfNeeded(uint8_t byte, StringBuilder& buffer)
{
if (byte < '0' || (byte > '9' && byte < 'A') || (byte > 'Z' && byte < 'a') || byte > 'z') {
buffer.append('%');
buffer.append(upperNibbleToASCIIHexDigit(byte));
buffer.append(lowerNibbleToASCIIHexDigit(byte));
return;
}
buffer.append(byte);
}
TEST(PermissionsAPI, DataURL)
{
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get()._allowTopNavigationToDataURLs = YES;
RetainPtr messageHandler = adoptNS([[PermissionsAPIMessageHandler alloc] init]);
[[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"msg"];
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration.get()]);
RetainPtr delegate = adoptNS([[PermissionsAPIUIDelegate alloc] init]);
[webView setUIDelegate:delegate.get()];
queryPermissionDelegateResult = WKPermissionDecisionDeny;
didReceiveMessage = false;
didReceiveQueryPermission = false;
char script[] = "<script>\
navigator.permissions.query({ name : 'geolocation' }).then((result) => { \
window.webkit.messageHandlers.msg.postMessage(result.state);\
}, () => { \
window.webkit.messageHandlers.msg.postMessage('FAIL');\
});\
</script>";
StringBuilder buffer;
buffer.append("data:text/html,"_s);
for (size_t cptr = 0; cptr < sizeof(script) - 1; ++cptr)
urlEncodeIfNeeded(script[cptr], buffer);
RetainPtr request = adoptNS([[NSURLRequest alloc] initWithURL:adoptNS([[NSURL alloc] initWithString:buffer.createNSString().get()]).get()]);
[webView loadRequest:request.get()];
TestWebKitAPI::Util::run(&didReceiveMessage);
EXPECT_STREQ(((NSString *)[scriptMessage body]).UTF8String, "prompt");
EXPECT_FALSE(didReceiveQueryPermission);
}
TEST(PermissionsAPI, OnChange)
{
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr messageHandler = adoptNS([[PermissionsAPIMessageHandler alloc] init]);
[[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"msg"];
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration.get()]);
RetainPtr delegate = adoptNS([[PermissionsAPIUIDelegate alloc] init]);
[webView setUIDelegate:delegate.get()];
queryPermissionDelegateResult = WKPermissionDecisionGrant;
didReceiveMessage = false;
didReceiveQueryPermission = false;
NSString *script = @"<script>"
"navigator.permissions.query({ name : 'geolocation' }).then((permissionStatus) => {"
" window.webkit.messageHandlers.msg.postMessage(permissionStatus.state);"
" permissionStatus.onchange = () => {"
" window.webkit.messageHandlers.msg.postMessage(permissionStatus.state);"
" };"
"}, () => {"
" window.webkit.messageHandlers.msg.postMessage('FAIL');"
"});"
"</script>";
[webView synchronouslyLoadHTMLString:script baseURL:[NSURL URLWithString:@"https://example.com/"]];
TestWebKitAPI::Util::run(&didReceiveMessage);
EXPECT_STREQ(((NSString *)[scriptMessage body]).UTF8String, "granted");
didReceiveMessage = false;
queryPermissionDelegateResult = WKPermissionDecisionPrompt;
auto originString = adoptWK(WKStringCreateWithUTF8CString("https://example.com/"));
auto origin = adoptWK(WKSecurityOriginCreateFromString(originString.get()));
[WKWebView _permissionChanged:@"geolocation" forOrigin:(__bridge WKSecurityOrigin *)origin.get()];
TestWebKitAPI::Util::run(&didReceiveMessage);
EXPECT_STREQ(((NSString *)[scriptMessage body]).UTF8String, "prompt");
}
static void testPermissionsAPIForGeolocation(GeolocationPermissionState geolocationPermissionState, RequestGeolocationSincePageLoad requestGeolocationSincePageLoad, String expectedResult)
{
RetainPtr pool = adoptNS([[WKProcessPool alloc] init]);
WKGeolocationProviderV1 providerCallback;
zeroBytes(providerCallback);
providerCallback.base.version = 1;
providerCallback.startUpdating = [] (WKGeolocationManagerRef manager, const void*) {
WKGeolocationManagerProviderDidChangePosition(manager, adoptWK(WKGeolocationPositionCreate(0, 50.644358, 3.345453, 2.53)).get());
};
WKGeolocationManagerSetProvider(WKContextGetGeolocationManager((WKContextRef)pool.get()), &providerCallback.base);
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().processPool = pool.get();
RetainPtr messageHandler = adoptNS([[PermissionsAPIMessageHandler alloc] init]);
[[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"msg"];
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration.get()]);
RetainPtr delegate = adoptNS([[PermissionsAPIAndGeolocationRequestUIDelegate alloc] init]);
[webView setUIDelegate:delegate.get()];
switch (geolocationPermissionState) {
case GeolocationPermissionState::GrantedPermanently:
queryPermissionDelegateResult = WKPermissionDecisionGrant;
break;
case GeolocationPermissionState::DeniedPermanently:
queryPermissionDelegateResult = WKPermissionDecisionDeny;
break;
case GeolocationPermissionState::GrantedFromPrompt:
queryPermissionDelegateResult = WKPermissionDecisionPrompt;
requestGeolocationPermissionDelegateResult = true;
break;
case GeolocationPermissionState::DeniedFromPrompt:
queryPermissionDelegateResult = WKPermissionDecisionPrompt;
requestGeolocationPermissionDelegateResult = false;
break;
}
didReceiveMessage = false;
didReceiveQueryPermission = false;
NSString *scriptWithGeolocationRequestedSincePageLoad = @"<script>"
"let success = () => {"
" navigator.permissions.query({ name : 'geolocation' }).then((permissionStatus) => {"
" window.webkit.messageHandlers.msg.postMessage(permissionStatus.state);"
" }, () => {"
" window.webkit.messageHandlers.msg.postMessage('FAIL');"
" });"
"};"
"let failure = () => {"
" navigator.permissions.query({ name : 'geolocation' }).then((permissionStatus) => {"
" window.webkit.messageHandlers.msg.postMessage(permissionStatus.state);"
" }, () => {"
" window.webkit.messageHandlers.msg.postMessage('FAIL');"
" });"
"};"
"navigator.geolocation.getCurrentPosition(success, failure);"
"</script>";
NSString *scriptWithoutGeolocationRequestedSincePageLoad = @"<script>"
"navigator.permissions.query({ name : 'geolocation' }).then((permissionStatus) => {"
" window.webkit.messageHandlers.msg.postMessage(permissionStatus.state);"
"}, () => {"
" window.webkit.messageHandlers.msg.postMessage('FAIL');"
"});"
"</script>";
if (requestGeolocationSincePageLoad == RequestGeolocationSincePageLoad::Yes)
[webView synchronouslyLoadHTMLString:scriptWithGeolocationRequestedSincePageLoad baseURL:[NSURL URLWithString:@"https://example.com/"]];
else
[webView synchronouslyLoadHTMLString:scriptWithoutGeolocationRequestedSincePageLoad baseURL:[NSURL URLWithString:@"https://example.com/"]];
TestWebKitAPI::Util::run(&didReceiveMessage);
EXPECT_STREQ(((NSString *)[scriptMessage body]).UTF8String, expectedResult.utf8().data());
}
TEST(PermissionsAPI, GeolocationPermissionGrantedFromPromptAndGeolocationRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::GrantedFromPrompt, RequestGeolocationSincePageLoad::Yes, "granted"_s);
}
TEST(PermissionsAPI, GeolocationPermissionGrantedFromPromptAndGeolocationNotRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::GrantedFromPrompt, RequestGeolocationSincePageLoad::No, "prompt"_s);
}
TEST(PermissionsAPI, GeolocationPermissionDeniedFromPromptAndGeolocationRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::DeniedFromPrompt, RequestGeolocationSincePageLoad::Yes, "denied"_s);
}
TEST(PermissionsAPI, GeolocationPermissionDeniedFromPromptAndGeolocationNotRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::DeniedFromPrompt, RequestGeolocationSincePageLoad::No, "prompt"_s);
}
TEST(PermissionsAPI, GeolocationPermissionGrantedPermanentlyAndGeolocationRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::GrantedPermanently, RequestGeolocationSincePageLoad::Yes, "granted"_s);
}
TEST(PermissionsAPI, GeolocationPermissionGrantedPermanentlyAndGeolocationNotRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::GrantedPermanently, RequestGeolocationSincePageLoad::No, "granted"_s);
}
TEST(PermissionsAPI, GeolocationPermissionDeniedPermanentlyAndGeolocationRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::DeniedPermanently, RequestGeolocationSincePageLoad::Yes, "denied"_s);
}
TEST(PermissionsAPI, GeolocationPermissionDeniedPermanentlyAndGeolocationNotRequestedSinceLoad)
{
testPermissionsAPIForGeolocation(GeolocationPermissionState::DeniedPermanently, RequestGeolocationSincePageLoad::No, "prompt"_s);
}
} // namespace TestWebKitAPI