blob: fcc1bc31b718650962704ef4a7ea62ccf96abc53 [file] [log] [blame] [edit]
/*
* Copyright (C) 2022-2024 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"
#if ENABLE(WK_WEB_EXTENSIONS)
#import "HTTPServer.h"
#import "TestCocoa.h"
#import "TestWKWebView.h"
#import "TestWebExtensionsDelegate.h"
#import "WebExtensionUtilities.h"
#import <WebCore/UserGestureIndicator.h>
#import <WebKit/WKFoundation.h>
#import <WebKit/WKWebExtensionContextPrivate.h>
#import <WebKit/WKWebExtensionControllerDelegate.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <wtf/darwin/DispatchExtras.h>
namespace TestWebKitAPI {
TEST(WKWebExtensionAPIPermissions, Errors)
{
auto *manifest = @{
@"manifest_version": @3,
@"permissions": @[ @"alarms", @"activeTab" ],
@"optional_permissions": @[ @"webNavigation", @"cookies" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"await browser.test.assertRejects(browser.permissions.remove({ permissions: ['webRequest'] }), /only permissions specified in the manifest may be removed/i)",
@"await browser.test.assertRejects(browser.permissions.remove({ origins: ['https://example.com/'] }), /only permissions specified in the manifest may be removed/i)",
@"await browser.test.assertRejects(browser.permissions.remove({ permissions: ['alarms'] }), /required permissions cannot be removed/i)",
@"await browser.test.assertRejects(browser.permissions.remove({ origins: ['*://*.apple.com/*'] }), /required permissions cannot be removed/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ permissions: ['bad'] }), /'bad' is not a valid permission/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ origins: ['bad'] }), /'bad' is not a valid pattern/i)",
@"await browser.test.assertRejects(browser.permissions.request({ permissions: ['cookies'] }), /must be called during a user gesture/i)",
@"browser.test.assertThrows(() => browser.permissions.contains(null), /'permissions' value is invalid, because an object is expected/i)",
@"browser.test.assertThrows(() => browser.permissions.contains('string'), /'permissions' value is invalid, because an object is expected/i)",
@"browser.test.assertThrows(() => browser.permissions.contains(123), /'permissions' value is invalid, because an object is expected/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ permissions: 'notAnArray' }), /'permissions' is expected to be an array of strings, but a string was provided/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ permissions: { name: 'storage' } }), /'permissions' is expected to be an array of strings, but an object was provided/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ permissions: 123 }), /'permissions' is expected to be an array of strings, but a number was provided/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ origins: 'notAnArray' }), /'origins' is expected to be an array of strings, but a string was provided/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ origins: { domain: 'https://example.com/' } }), /'origins' is expected to be an array of strings, but an object was provided/i)",
@"browser.test.assertThrows(() => browser.permissions.contains({ origins: 123 }), /'origins' is expected to be an array of strings, but a number was provided/i)",
@"browser.test.notifyPass()"
]);
Util::loadAndRunExtension(manifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIPermissions, Basics)
{
auto *manifest = @{
@"manifest_version": @3,
@"permissions": @[ @"alarms", @"activeTab" ],
@"optional_permissions": @[ @"webNavigation", @"cookies" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
// contains() should return true for all named permissions and granted match patterns.
@"browser.test.assertTrue(await browser.permissions.contains({'permissions': ['alarms', 'activeTab']}))",
@"browser.test.assertTrue(await browser.permissions.contains({'origins': ['*://webkit.org/*']}))",
// contains() should return false for non granted match patterns.
@"browser.test.assertFalse(await browser.permissions.contains({'origins': ['*://apple.com/*']}))",
// Removing optional permissions should pass.
@"browser.test.assertTrue(await browser.permissions.remove({'permissions': ['cookies']}))",
// Removing functional permissions should fail.
@"await browser.test.assertRejects(browser.permissions.remove({'permissions': ['alarms']}))",
@"await browser.test.assertRejects(browser.permissions.remove({'permissions': ['alarms', 'activeTab']}))",
@"await browser.test.assertRejects(browser.permissions.remove({'permissions': ['cookies', 'activeTab']}))",
@"await browser.test.assertRejects(browser.permissions.remove({'permissions': ['scripting']}))",
// getAll() should return all named permissions and granted match patterns.
@"let permissions = {'origins': ['*://webkit.org/*'], 'permissions': ['alarms', 'activeTab']}",
@"const result = await browser.permissions.getAll()",
@"browser.test.assertDeepEq(result, permissions)",
// Finish
@"browser.test.notifyPass()"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
// Grant "permissions" in the manifest.
for (NSString *permission in manager.get().extension.requestedPermissions)
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:permission];
// Grant extension access to webkit.org.
WKWebExtensionMatchPattern *matchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://webkit.org/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager run];
}
TEST(WKWebExtensionAPIPermissions, AcceptPermissionsRequest)
{
auto *manifest = @{
@"manifest_version": @3,
@"permissions": @[ @"alarms", @"activeTab" ],
@"optional_permissions": @[ @"webNavigation", @"cookies", @"declarativeNetRequest", @"*://*.apple.com/*" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertTrue(await browser.permissions.request({'permissions': ['declarativeNetRequest'], 'origins': ['*://*.apple.com/*']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
NSSet<NSString *> *permissions = [NSSet setWithArray:@[ @"declarativeNetRequest" ]];
NSSet<WKWebExtensionMatchPattern *> *matchPatterns = [NSSet setWithArray:@[ [WKWebExtensionMatchPattern matchPatternWithString:@"*://*.apple.com/*" ]]];
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Implement the delegate methods that're called when a call to permissions.request() is made.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
EXPECT_EQ(requestedPermissions.count, permissions.count);
EXPECT_TRUE([requestedPermissions isEqualToSet:permissions]);
callback(requestedPermissions, nil);
};
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
EXPECT_EQ(requestedMatchPatterns.count, matchPatterns.count);
EXPECT_TRUE([requestedMatchPatterns isEqualToSet:matchPatterns]);
dispatch_async(mainDispatchQueueSingleton(), ^{
callback(requestedMatchPatterns, nil);
requestComplete = true;
});
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, DenyPermissionsRequest)
{
auto *manifest = @{
@"manifest_version": @3,
@"optional_permissions": @[ @"declarativeNetRequest" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertFalse(await browser.permissions.request({'permissions': ['declarativeNetRequest'], 'origins': ['*://*.apple.com/*']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Implement the delegate methods, but don't grant the permissions.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
callback(NSSet.set, NSDate.distantPast);
};
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
requestComplete = true;
callback(NSSet.set, NSDate.distantFuture);
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, AcceptPermissionsDenyMatchPatternsRequest)
{
auto *manifest = @{
@"manifest_version": @3,
@"optional_permissions": @[ @"declarativeNetRequest" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertFalse(await browser.permissions.request({'permissions': ['declarativeNetRequest'], 'origins': ['*://*.apple.com/*']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Grant the requested permissions.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
callback(requestedPermissions, nil);
};
// Deny the requested match patterns.
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
requestComplete = true;
callback(NSSet.set, nil);
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, RequestPermissionsOnly)
{
auto *manifest = @{
@"manifest_version": @3,
@"optional_permissions": @[ @"declarativeNetRequest" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertTrue(await browser.permissions.request({'permissions': ['declarativeNetRequest']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Grant the requested permissions.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
dispatch_async(mainDispatchQueueSingleton(), ^{
requestComplete = true;
callback(requestedPermissions, nil);
});
};
// Match patterns method should not be called.
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
ASSERT_NOT_REACHED();
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, RequestMatchPatternsOnly)
{
auto *manifest = @{
@"manifest_version": @3,
@"optional_permissions": @[ @"declarativeNetRequest" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertTrue(await browser.permissions.request({'origins': ['*://*.apple.com/*']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Permissions method should not be called.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
ASSERT_NOT_REACHED();
};
// Grant the requested match patterns.
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
dispatch_async(mainDispatchQueueSingleton(), ^{
requestComplete = true;
callback(requestedMatchPatterns, [NSDate dateWithTimeIntervalSinceNow:10]);
});
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, RequestAllURLsMatchPattern)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"<all_urls>" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertTrue(await browser.permissions.request({'origins': ['<all_urls>']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Permissions method should not be called.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
ASSERT_NOT_REACHED();
return;
};
// Grant the requested match patterns.
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
dispatch_async(mainDispatchQueueSingleton(), ^{
requestComplete = true;
callback(requestedMatchPatterns, [NSDate dateWithTimeIntervalSinceNow:10]);
});
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, ImplicitAllHostsAndURLsPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"permissions": @[ @"tabs" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertTrue(permissions.origins.includes('*://*/*'))",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:WKWebExtensionMatchPattern.allHostsAndSchemesMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager run];
}
TEST(WKWebExtensionAPIPermissions, AllHostsHostPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"*://*/*" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertFalse(permissions.origins.includes('<all_urls>'))",
@" browser.test.assertTrue(permissions.origins.includes('*://*/*'))",
@" browser.test.sendMessage('Test done')",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *allHostsMatchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*/*"];
WKWebExtensionMatchPattern *allURLsMatchPattern = [WKWebExtensionMatchPattern allURLsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allHostsMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusUnknown forMatchPattern:allHostsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allURLsMatchPattern];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager done];
}
TEST(WKWebExtensionAPIPermissions, AllURLsHostPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"<all_urls>" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertTrue(permissions.origins.includes('<all_urls>'))",
@" browser.test.assertFalse(permissions.origins.includes('*://*/*'))",
@" browser.test.sendMessage('Test done')",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *allHostsMatchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*/*"];
WKWebExtensionMatchPattern *allURLsMatchPattern = [WKWebExtensionMatchPattern allURLsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allHostsMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusUnknown forMatchPattern:allHostsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allURLsMatchPattern];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager done];
}
TEST(WKWebExtensionAPIPermissions, AllHostsOptionalHostPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*/*" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertFalse(permissions.origins.includes('<all_urls>'))",
@" browser.test.assertTrue(permissions.origins.includes('*://*/*'))",
@" browser.test.sendMessage('Test done')",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *allHostsMatchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*/*"];
WKWebExtensionMatchPattern *allURLsMatchPattern = [WKWebExtensionMatchPattern allURLsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allHostsMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusUnknown forMatchPattern:allHostsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allURLsMatchPattern];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager done];
}
TEST(WKWebExtensionAPIPermissions, AllURLsOptionalHostPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"<all_urls>" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertTrue(permissions.origins.includes('<all_urls>'))",
@" browser.test.assertFalse(permissions.origins.includes('*://*/*'))",
@" browser.test.sendMessage('Test done')",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *allHostsMatchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*/*"];
WKWebExtensionMatchPattern *allURLsMatchPattern = [WKWebExtensionMatchPattern allURLsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allHostsMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusUnknown forMatchPattern:allHostsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allURLsMatchPattern];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager done];
}
TEST(WKWebExtensionAPIPermissions, AllHostsAndURLsHostPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"*://*/*", @"<all_urls>" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertTrue(permissions.origins.includes('<all_urls>'))",
@" browser.test.assertTrue(permissions.origins.includes('*://*/*'))",
@" browser.test.sendMessage('Test done')",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *allHostsMatchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*/*"];
WKWebExtensionMatchPattern *allURLsMatchPattern = [WKWebExtensionMatchPattern allURLsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allHostsMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusUnknown forMatchPattern:allHostsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allURLsMatchPattern];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager done];
}
TEST(WKWebExtensionAPIPermissions, AllHostsAndURLsOptionalHostPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*/*", @"<all_urls>" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertTrue(permissions.origins.includes('<all_urls>'))",
@" browser.test.assertTrue(permissions.origins.includes('*://*/*'))",
@" browser.test.sendMessage('Test done')",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *allHostsMatchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*/*"];
WKWebExtensionMatchPattern *allURLsMatchPattern = [WKWebExtensionMatchPattern allURLsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allHostsMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusUnknown forMatchPattern:allHostsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allURLsMatchPattern];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager done];
}
TEST(WKWebExtensionAPIPermissions, AllHostsAndURLsOptionalAndHostPermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"*://*/*" ],
@"optional_host_permissions": @[ @"<all_urls>" ],
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.onMessage.addListener(async (message, data) => {",
@" if (message != 'Run test') return",
@" const permissions = await browser.permissions.getAll()",
@" browser.test.assertTrue(permissions.origins.includes('<all_urls>'))",
@" browser.test.assertTrue(permissions.origins.includes('*://*/*'))",
@" browser.test.sendMessage('Test done')",
@"})",
@"browser.test.sendMessage('Loaded')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *allHostsMatchPattern = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*/*"];
WKWebExtensionMatchPattern *allURLsMatchPattern = [WKWebExtensionMatchPattern allURLsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allHostsMatchPattern];
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
[manager runUntilTestMessage:@"Loaded"];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusUnknown forMatchPattern:allHostsMatchPattern];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:allURLsMatchPattern];
[manager sendTestMessage:@"Run test"];
[manager runUntilTestMessage:@"Test done"];
[manager done];
}
TEST(WKWebExtensionAPIPermissions, GrantOnlySomePermissions)
{
auto *manifest = @{
@"manifest_version": @3,
@"optional_permissions": @[ @"declarativeNetRequest", @"alarms" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*.apple.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertFalse(await browser.permissions.request({'permissions': ['alarms', 'declarativeNetRequest']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Grant the requested permissions.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
dispatch_async(mainDispatchQueueSingleton(), ^{
requestComplete = true;
callback(requestedPermissions, nil);
});
};
// Match patterns method should not be called.
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
ASSERT_NOT_REACHED();
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, GrantOnlySomeMatchPatterns)
{
auto *manifest = @{
@"manifest_version": @3,
@"optional_permissions": @[ @"declarativeNetRequest" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"optional_host_permissions": @[ @"*://*.apple.com/*", @"*://*.example.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.runWithUserGesture(async () => {",
@" browser.test.assertFalse(await browser.permissions.request({'origins': ['*://*.apple.com/*', '*://*.example.com/*']}))",
@"})"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
configuration.get().webExtensionController = manager.get().controller;
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
__block bool requestComplete = false;
// Permissions method should not be called.
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
ASSERT_NOT_REACHED();
};
// Grant only one of the requested match patterns.
requestDelegate.get().promptForPermissionMatchPatterns = ^(id<WKWebExtensionTab> tab, NSSet<WKWebExtensionMatchPattern *> *requestedMatchPatterns, void (^callback)(NSSet<WKWebExtensionMatchPattern *> *, NSDate *)) {
requestComplete = true;
callback([NSSet setWithObject:requestedMatchPatterns.anyObject], NSDate.distantFuture);
};
manager.get().controllerDelegate = requestDelegate.get();
TestWebKitAPI::Util::run(&requestComplete);
}
TEST(WKWebExtensionAPIPermissions, ValidMatchPatterns)
{
auto *manifest = @{
@"manifest_version": @3,
@"permissions": @[ @"alarms", @"activeTab" ],
@"optional_permissions": @[ @"webNavigation", @"cookies" ],
@"background": @{ @"scripts": @[ @"background.js" ], @"type": @"module", @"persistent": @NO },
@"host_permissions": @[ @"*://*.example.com/*" ]
};
auto *backgroundScript = Util::constructScript(@[
// Supported Schemes
@"await browser.test.assertSafeResolve(() => browser.permissions.contains({ origins: [ '*://*.example.com/*' ] }))",
@"await browser.test.assertSafeResolve(() => browser.permissions.contains({ origins: [ 'http://*.example.com/*' ] }))",
@"await browser.test.assertSafeResolve(() => browser.permissions.contains({ origins: [ 'https://*.example.com/*' ] }))",
@"await browser.test.assertSafeResolve(() => browser.permissions.contains({ origins: [ 'webkit-extension://*/*' ] }))",
// Custom Web Extension Schemes
@"await browser.test.assertSafeResolve(() => browser.permissions.contains({ origins: [ 'test-extension://*/*' ] }))",
@"await browser.test.assertSafeResolve(() => browser.permissions.contains({ origins: [ 'other-extension://*/*' ] }))",
// Invalid Schemes
@"await browser.test.assertThrows(() => browser.permissions.contains({ origins: [ 'ftp://*.example.com/*' ] }), /not a valid pattern/)",
@"await browser.test.assertThrows(() => browser.permissions.contains({ origins: [ 'data:*' ] }), /not a valid pattern/)",
@"await browser.test.assertThrows(() => browser.permissions.contains({ origins: [ 'file:///*' ] }), /not a valid pattern/)",
@"await browser.test.assertThrows(() => browser.permissions.contains({ origins: [ 'chrome-extension://*/*' ] }), /not a valid pattern/)",
// Finish
@"browser.test.notifyPass()"
]);
[WKWebExtensionMatchPattern registerCustomURLScheme:@"test-extension"];
[WKWebExtensionMatchPattern registerCustomURLScheme:@"other-extension"];
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
WKWebExtensionMatchPattern *matchPatternApple = [WKWebExtensionMatchPattern matchPatternWithString:@"*://*.example.com/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPatternApple];
[manager run];
}
TEST(WKWebExtensionAPIPermissions, ClipboardWrite)
{
auto *manifest = @{
@"manifest_version": @3,
@"name": @"Permissions Test",
@"description": @"Permissions Test",
@"version": @"1",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
@"permissions": @[ @"clipboardWrite" ],
};
auto *backgroundScript = Util::constructScript(@[
@"await navigator.clipboard.writeText('Test Clipboard Write')",
@"browser.test.sendMessage('Clipboard Written')",
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionClipboardWrite];
[manager runUntilTestMessage:@"Clipboard Written"];
#if TARGET_OS_IPHONE
auto *clipboardContent = UIPasteboard.generalPasteboard.string;
#else
auto *clipboardContent = [NSPasteboard.generalPasteboard stringForType:NSPasteboardTypeString];
#endif
EXPECT_NS_EQUAL(clipboardContent, @"Test Clipboard Write");
}
TEST(WKWebExtensionAPIPermissions, ClipboardWriteWithoutPermission)
{
auto *manifest = @{
@"manifest_version": @3,
@"name": @"Permissions Test",
@"description": @"Permissions Test",
@"version": @"1",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
};
auto *backgroundScript = Util::constructScript(@[
@"try {",
@" await navigator.clipboard.writeText('Attempt Without Permission')",
@" browser.test.notifyFail('Should not write to clipboard without permission')",
@"} catch (error) {",
@" browser.test.assertTrue(error instanceof DOMException, 'Expect a DOMException for insufficient permissions')",
@" browser.test.notifyPass()",
@"}"
]);
#if TARGET_OS_IPHONE
auto *clipboardContentBefore = UIPasteboard.generalPasteboard.string;
#else
auto *clipboardContentBefore = [NSPasteboard.generalPasteboard stringForType:NSPasteboardTypeString];
#endif
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
[manager run];
#if TARGET_OS_IPHONE
auto *clipboardContentAfter = UIPasteboard.generalPasteboard.string;
#else
auto *clipboardContentAfter = [NSPasteboard.generalPasteboard stringForType:NSPasteboardTypeString];
#endif
EXPECT_NS_EQUAL(clipboardContentBefore, clipboardContentAfter);
}
TEST(WKWebExtensionAPIPermissions, ClipboardWriteWithRequest)
{
auto *manifest = @{
@"manifest_version": @3,
@"name": @"Permissions Test",
@"description": @"Permissions Test",
@"version": @"1",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
@"optional_permissions": @[ @"clipboardWrite" ],
};
auto *backgroundScript = Util::constructScript(@[
@"try {",
@" await navigator.clipboard.writeText('Initial Attempt Without Permission')",
@"} catch (error) {",
@" browser.test.assertTrue(error instanceof DOMException, 'Expect a DOMException for insufficient permissions')",
@"}",
@"const permissionGranted = await browser.test.runWithUserGesture(() => {",
@" return browser.permissions.request({ permissions: [ 'clipboardWrite' ] })",
@"})",
@"if (permissionGranted) {",
@" await navigator.clipboard.writeText('Test Clipboard Write After Permission')",
@" browser.test.sendMessage('Clipboard Written')",
@"} else {",
@" browser.test.notifyFail('Permission was not granted')",
@"}"
]);
auto manager = Util::loadExtension(manifest, @{ @"background.js": backgroundScript });
auto requestDelegate = adoptNS([[TestWebExtensionsDelegate alloc] init]);
requestDelegate.get().promptForPermissions = ^(id<WKWebExtensionTab> tab, NSSet<NSString *> *requestedPermissions, void (^callback)(NSSet<NSString *> *, NSDate *)) {
EXPECT_EQ(requestedPermissions.count, 1ul);
EXPECT_TRUE([requestedPermissions containsObject:WKWebExtensionPermissionClipboardWrite]);
callback(requestedPermissions, nil);
};
manager.get().controllerDelegate = requestDelegate.get();
[manager runUntilTestMessage:@"Clipboard Written"];
#if TARGET_OS_IPHONE
auto *clipboardContent = UIPasteboard.generalPasteboard.string;
#else
auto *clipboardContent = [NSPasteboard.generalPasteboard stringForType:NSPasteboardTypeString];
#endif
EXPECT_NS_EQUAL(clipboardContent, @"Test Clipboard Write After Permission");
}
static auto *corsManifest = @{
@"manifest_version": @3,
@"name": @"Permissions Test",
@"description": @"Permissions Test",
@"version": @"1",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
@"optional_host_permissions": @[ @"*://*/*" ]
};
TEST(WKWebExtensionAPIPermissions, CORSUsingFetchWithPermissions)
{
TestWebKitAPI::HTTPServer server({
{ "/subresource"_s, { {{ "Content-Type"_s, "application/json"_s }, { "headerName"_s, "headerValue"_s }}, "{ \"testKey\": \"testValue\" }"_s } }
});
auto *backgroundScript = Util::constructScript(@[
[NSString stringWithFormat:@"const subresourceURL = 'http://127.0.0.1:%d/subresource'", server.port()],
@"try {",
@" const response = await fetch(subresourceURL)",
@" if (response.headers.get('headerName') !== 'headerValue')",
@" throw new Error('CORS failed: Incorrect header value')",
@" const json = await response.json()",
@" if (json.testKey !== 'testValue')",
@" throw new Error('CORS failed: Incorrect JSON value')",
@" browser.test.notifyPass()",
@"} catch (error) {",
@" browser.test.notifyFail('CORS failed unexpectedly: ' + error.message)",
@"}"
]);
auto manager = Util::loadExtension(corsManifest, @{ @"background.js": backgroundScript });
auto *urlRequest = server.request();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager run];
}
TEST(WKWebExtensionAPIPermissions, CORSUsingFetchWithoutPermissions)
{
TestWebKitAPI::HTTPServer server({
{ "/subresource"_s, { {{ "Content-Type"_s, "application/json"_s }, { "headerName"_s, "headerValue"_s }}, "{ \"testKey\": \"testValue\" }"_s } }
});
auto *subresourceURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/subresource", server.port()]];
auto *backgroundScript = Util::constructScript(@[
[NSString stringWithFormat:@"const subresourceURL = '%@'", subresourceURL],
@"browser.permissions.onAdded.addListener(async () => {",
@" try {",
@" const response = await fetch(subresourceURL)",
@" if (response.headers.get('headerName') !== 'headerValue')",
@" throw new Error('CORS failed: Incorrect header value')",
@" const json = await response.json()",
@" if (json.testKey !== 'testValue')",
@" throw new Error('CORS failed: Incorrect JSON value')",
@" browser.test.notifyPass()",
@" } catch (error) {",
@" browser.test.notifyFail('CORS failed unexpectedly after permission was granted: ' + error.message)",
@" }",
@"})",
@"try {",
@" const response = await fetch(subresourceURL)",
@" browser.test.notifyFail('CORS enabled: Fetch succeeded when it should have failed')",
@"} catch (error) {",
@" // CORS failed as expected",
@"}",
]);
auto manager = Util::loadExtension(corsManifest, @{ @"background.js": backgroundScript });
__block size_t promptCount = 0;
manager.get().internalDelegate.promptForPermissionToAccessURLs = ^(id<WKWebExtensionTab>, NSSet<NSURL *> *requestedURLs, void (^completionHandler)(NSSet<NSURL *> *allowedURLs, NSDate *)) {
EXPECT_EQ(requestedURLs.count, 1ul);
EXPECT_TRUE([requestedURLs containsObject:subresourceURL]);
++promptCount;
completionHandler(requestedURLs, nil);
};
[manager run];
EXPECT_EQ(promptCount, 1ul);
}
TEST(WKWebExtensionAPIPermissions, CORSUsingFetchWithoutGrantingPermission)
{
TestWebKitAPI::HTTPServer server({
{ "/subresource"_s, { {{ "Content-Type"_s, "application/json"_s }, { "headerName"_s, "headerValue"_s }}, "{ \"testKey\": \"testValue\" }"_s } }
});
auto *subresourceURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/subresource", server.port()]];
auto *backgroundScript = Util::constructScript(@[
[NSString stringWithFormat:@"const subresourceURL = '%@'", subresourceURL],
@"let fetchAttempt = 0",
@"async function performFetch() {",
@" try {",
@" const response = await fetch(subresourceURL)",
@" browser.test.notifyFail(`CORS enabled on attempt ${fetchAttempt + 1}, when it should have failed`)",
@" } catch (error) {",
@" if (++fetchAttempt < 2)",
@" performFetch()",
@" else",
@" browser.test.notifyPass()",
@" }",
@"}",
@"performFetch()"
]);
auto manager = Util::loadExtension(corsManifest, @{ @"background.js": backgroundScript });
__block size_t promptCount = 0;
manager.get().internalDelegate.promptForPermissionToAccessURLs = ^(id<WKWebExtensionTab>, NSSet<NSURL *> *requestedURLs, void (^completionHandler)(NSSet<NSURL *> *allowedURLs, NSDate *)) {
EXPECT_EQ(requestedURLs.count, 1ul);
EXPECT_TRUE([requestedURLs containsObject:subresourceURL]);
++promptCount;
completionHandler(NSSet.set, nil);
};
[manager run];
EXPECT_EQ(promptCount, 2ul);
}
TEST(WKWebExtensionAPIPermissions, CORSUsingXHRWithPermissions)
{
TestWebKitAPI::HTTPServer server({
{ "/subresource"_s, { {{ "Content-Type"_s, "application/json"_s }, { "headerName"_s, "headerValue"_s }}, "{ \"testKey\": \"testValue\" }"_s } }
});
auto *backgroundScript = Util::constructScript(@[
[NSString stringWithFormat:@"const subresourceURL = 'http://127.0.0.1:%d/subresource'", server.port()],
@"const xhr = new XMLHttpRequest()",
@"xhr.onload = () => {",
@" if (xhr.getResponseHeader('headerName') !== 'headerValue')",
@" return browser.test.notifyFail('CORS failed: Incorrect header value')",
@" try {",
@" const json = JSON.parse(xhr.responseText)",
@" if (json.testKey !== 'testValue')",
@" throw new Error('Incorrect JSON value')",
@" browser.test.notifyPass()",
@" } catch (error) {",
@" browser.test.notifyFail('CORS failed: JSON parsing error - ' + error.message)",
@" }",
@"}",
@"xhr.onerror = () => browser.test.notifyFail('CORS failed unexpectedly')",
@"xhr.open('GET', subresourceURL, true)",
@"xhr.send()"
]);
auto manager = Util::loadExtension(corsManifest, @{ @"background.js": backgroundScript });
auto *urlRequest = server.request();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager run];
}
TEST(WKWebExtensionAPIPermissions, CORSUsingXHRWithoutPermissions)
{
TestWebKitAPI::HTTPServer server({
{ "/subresource"_s, { {{ "Content-Type"_s, "application/json"_s }, { "headerName"_s, "headerValue"_s }}, "{ \"testKey\": \"testValue\" }"_s } }
});
auto *subresourceURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/subresource", server.port()]];
auto *backgroundScript = Util::constructScript(@[
[NSString stringWithFormat:@"const subresourceURL = '%@'", subresourceURL],
@"browser.permissions.onAdded.addListener(() => {",
@" const xhr = new XMLHttpRequest()",
@" xhr.onload = () => {",
@" if (xhr.getResponseHeader('headerName') !== 'headerValue')",
@" return browser.test.notifyFail('CORS failed: Incorrect header value')",
@" try {",
@" const json = JSON.parse(xhr.responseText)",
@" if (json.testKey !== 'testValue')",
@" throw new Error('Incorrect JSON value')",
@" browser.test.notifyPass()",
@" } catch (error) {",
@" browser.test.notifyFail('CORS failed: JSON parsing error - ' + error.message)",
@" }",
@" }",
@" xhr.onerror = () => browser.test.notifyFail('CORS failed unexpectedly after permission was granted')",
@" xhr.open('GET', subresourceURL, true)",
@" xhr.send()",
@"})",
@"const xhr = new XMLHttpRequest()",
@"xhr.onload = () => browser.test.notifyFail('CORS enabled: XHR succeeded when it should have failed')",
@"xhr.onerror = () => {",
@" // Expected CORS failure",
@"}",
@"xhr.open('GET', subresourceURL, true)",
@"xhr.send()"
]);
auto manager = Util::loadExtension(corsManifest, @{ @"background.js": backgroundScript });
__block size_t promptCount = 0;
manager.get().internalDelegate.promptForPermissionToAccessURLs = ^(id<WKWebExtensionTab>, NSSet<NSURL *> *requestedURLs, void (^completionHandler)(NSSet<NSURL *> *allowedURLs, NSDate *)) {
EXPECT_EQ(requestedURLs.count, 1ul);
EXPECT_TRUE([requestedURLs containsObject:subresourceURL]);
++promptCount;
completionHandler(requestedURLs, nil);
};
[manager run];
EXPECT_EQ(promptCount, 1ul);
}
TEST(WKWebExtensionAPIPermissions, CORSUsingXHRWithoutGrantingPermission)
{
TestWebKitAPI::HTTPServer server({
{ "/subresource"_s, { {{ "Content-Type"_s, "application/json"_s }, { "headerName"_s, "headerValue"_s }}, "{ \"testKey\": \"testValue\" }"_s } }
});
auto *subresourceURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/subresource", server.port()]];
auto *backgroundScript = Util::constructScript(@[
[NSString stringWithFormat:@"const subresourceURL = '%@'", subresourceURL],
@"let fetchAttempt = 0",
@"function performXHR() {",
@" const xhr = new XMLHttpRequest()",
@" xhr.onload = () => {",
@" browser.test.notifyFail(`CORS enabled on attempt ${fetchAttempt + 1}, when it should have failed`)",
@" }",
@" xhr.onerror = () => {",
@" if (++fetchAttempt < 2)",
@" performXHR()",
@" else",
@" browser.test.notifyPass()",
@" }",
@" xhr.open('GET', subresourceURL, true)",
@" xhr.send()",
@"}",
@"performXHR()"
]);
auto manager = Util::loadExtension(corsManifest, @{ @"background.js": backgroundScript });
__block size_t promptCount = 0;
manager.get().internalDelegate.promptForPermissionToAccessURLs = ^(id<WKWebExtensionTab>, NSSet<NSURL *> *requestedURLs, void (^completionHandler)(NSSet<NSURL *> *allowedURLs, NSDate *)) {
EXPECT_EQ(requestedURLs.count, 1ul);
EXPECT_TRUE([requestedURLs containsObject:subresourceURL]);
++promptCount;
// Do not grant the permission in the prompt.
completionHandler(NSSet.set, nil);
};
[manager run];
EXPECT_EQ(promptCount, 2ul);
}
} // namespace TestWebKitAPI
#endif // ENABLE(WK_WEB_EXTENSIONS)