blob: 0f662ba6e92f6c6317b00f53b0ec894474f73788 [file] [log] [blame] [edit]
/*
* Copyright (C) 2023-2025 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 "WebExtensionUtilities.h"
#import <WebKit/WKUserContentControllerPrivate.h>
namespace TestWebKitAPI {
static auto *scriptingManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1",
@"permissions": @[ @"scripting", @"webNavigation" ],
@"host_permissions": @[ @"*://localhost/*" ],
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
};
static auto *changeBackgroundColorScript = @"document.body.style.background = 'pink'";
static auto *changeBackgroundFontScript = @"document.body.style.fontSize = '555px'";
TEST(WKWebExtensionAPIScripting, ErrorsExecuteScript)
{
auto *backgroundScript = Util::constructScript(@[
@"browser.test.assertThrows(() => browser.scripting.executeScript(), /a required argument is missing/i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({}), /missing required keys: 'target'./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target' : { }}), /missing required keys: 'tabId'./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 'bad'}}), /'tabId' is expected to be a number, but a string was provided./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, 'func': () => { console.log('function') }, 'function': () => {console.log('function')}}), /it cannot specify both 'func' and 'function'. Please use 'func'./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, args: ['args'], func: () => 'function', arguments: ['arguments']}), /it cannot specify both 'args' and 'arguments'. Please use 'args'./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, args: ['args'], files: ['path/to/file']}), /it must specify both 'func' and 'args'./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, args: 0, func: () => 'function' }), /'args' is expected to be an array, but a number was provided./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, func: () => 'function', args: [ () => 'arguments' ] }), /it is not JSON-serializable./i)",
@"const notAFunction = null",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, func: 'not a function' }), /is expected to be a value, but a string was provided./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }}), /it must specify either 'func' or 'files'./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, args: ['args'], files: [0]}), /'files' is expected to be an array of strings, but a number was provided./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1, allFrames: true, frameIds: [0] }, files: ['path/to/file']}), /it cannot specify both 'allFrames' and 'frameIds'./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1, frameIds: ['0'] }, files: ['path/to/file']}), /'frameIds' is expected to be an array of numbers, but a string was provided./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1, frameIds: [-1] }, files: ['path/to/file']}), /'-1' is not a frame identifier./i)",
@"browser.test.assertThrows(() => browser.scripting.executeScript({'target': { 'tabId': 1 }, world: 'world', files: ['path/to/file']}), /it must specify either 'isolated' or 'main'./i)",
@"browser.test.notifyPass()"
]);
Util::loadAndRunExtension(scriptingManifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIScripting, ErrorsCSS)
{
auto *backgroundScript = Util::constructScript(@[
@"browser.test.assertThrows(() => browser.scripting.insertCSS(), /a required argument is missing./i)",
@"browser.test.assertThrows(() => browser.scripting.insertCSS({}), /missing required keys: 'target'./i)",
@"browser.test.assertThrows(() => browser.scripting.insertCSS({ target: {} }), /missing required keys: 'tabId'./i)",
@"browser.test.assertThrows(() => browser.scripting.insertCSS({target: { tabId: 1 }, files: ['path/to/file'], css: 'css'}), /it cannot specify both 'css' and 'files'./i)",
@"browser.test.assertThrows(() => browser.scripting.insertCSS({target: { tabId: 1 }}), /it must specify either 'css' or 'files'./i)",
@"browser.test.assertThrows(() => browser.scripting.insertCSS({target: { tabId: '1' }, files: ['path/to/file'], css: 'css'}), /'tabId' is expected to be a number, but a string was provided./i)",
@"browser.test.assertThrows(() => browser.scripting.insertCSS({target: { tabId: 1 }, css: 'body { color: red }', origin: 'bad' }), /'origin' value is invalid, because it must specify either 'AUTHOR' or 'USER'/i);",
@"browser.test.assertThrows(() => browser.scripting.removeCSS(), /a required argument is missing./i)",
@"browser.test.assertThrows(() => browser.scripting.removeCSS({}), /missing required keys: 'target'./i)",
@"browser.test.assertThrows(() => browser.scripting.removeCSS({ target: {} }), /missing required keys: 'tabId'./i)",
@"browser.test.assertThrows(() => browser.scripting.removeCSS({target: { tabId: 1 }, files: ['path/to/file'], css: 'css'}), /it cannot specify both 'css' and 'files'./i)",
@"browser.test.assertThrows(() => browser.scripting.removeCSS({target: { tabId: 1 } }), /it must specify either 'css' or 'files'./i)",
@"browser.test.assertThrows(() => browser.scripting.removeCSS({target: { tabId: 'bad' }, files: ['path/to/file'], css: 'css'}), /'tabId' is expected to be a number, but a string was provided./i)",
@"browser.test.notifyPass()"
]);
Util::loadAndRunExtension(scriptingManifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIScripting, ErrorsRegisteredContentScript)
{
auto *backgroundScript = Util::constructScript(@[
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts(), /a required argument is missing/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts({}), /an array is expected/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{}]), /it is missing required keys: 'id'/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: 0 }]), /'id' is expected to be a string, but a number was provided/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '' }]), /it must not be empty/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '_0' }]), /it must not start with '_'/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '0' }]), /it must specify at least one match pattern for script with ID '0'/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '0', matches: [] }]), /it must specify at least one match pattern for script with ID '0'/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '0', matches: [ '*://*/*' ] }]), /it must specify at least one 'css' or 'js' file/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '0', matches: [ '*://*/*' ], js: ['path/to/file'], runAt: 'invalid_runAt' }]), /it must specify either 'document_start', 'document_end', or 'document_idle'/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '0', matches: [ '*://*/*' ], js: ['path/to/file'], world: 'invalid_world' }]), /it must specify either 'isolated' or 'main'/i)",
@"browser.test.assertThrows(() => browser.scripting.registerContentScripts([{ id: '0', matches: [ '*://*/*' ], css: ['style.css'], cssOrigin: 'bad' }]), /'cssOrigin' value is invalid, because it must specify either 'author' or 'user'/i)",
@"browser.test.assertThrows(() => browser.scripting.updateContentScripts([{ id: '_0' }]), /it must not start with '_'/i)",
@"browser.test.assertThrows(() => browser.scripting.updateContentScripts([{ id: '0', matches: [ ], js: ['path/to/file'] }]), /it must not be empty/i)",
@"browser.test.assertThrows(() => browser.scripting.updateContentScripts([{ id: '0', matches: [ '*://*/*' ], js: ['path/to/file'], world: 'invalid_world' }]), /it must specify either 'isolated' or 'main'/i)",
@"browser.test.assertThrows(() => browser.scripting.getRegisteredContentScripts([]), /an object is expected/i)",
@"browser.test.assertThrows(() => browser.scripting.getRegisteredContentScripts({ ids: 0 }), /'ids' is expected to be an array of strings, but a number was provided/i)",
@"browser.test.assertThrows(() => browser.scripting.getRegisteredContentScripts({ ids: [ 0 ] }), /'ids' is expected to be an array of strings, but a number was provided/i)",
@"browser.test.assertThrows(() => browser.scripting.unregisterContentScripts([]), /an object is expected/i)",
@"browser.test.assertThrows(() => browser.scripting.unregisterContentScripts({ ids: 0 }), /'ids' is expected to be an array of strings, but a number was provided/i)",
@"browser.test.assertThrows(() => browser.scripting.unregisterContentScripts({ ids: [ 0 ] }), /'ids' is expected to be an array of strings, but a number was provided/i)",
@"await browser.test.assertRejects(browser.scripting.registerContentScripts([{ id: '1', matches: ['*://*/*'], js: ['invalidFile.js'] }]), /Invalid resource 'invalidFile.js'/i)",
@"browser.test.notifyPass()"
]);
Util::loadAndRunExtension(scriptingManifest, @{ @"background.js": backgroundScript });
}
TEST(WKWebExtensionAPIScripting, ExecuteScript)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const tabId = details.tabId",
@" const blueValue = 'rgb(0, 0, 255)'",
@" function changeBackgroundColor(color) { document.body.style.background = color }",
@" function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@" let results = await browser.scripting.executeScript({ target: { tabId, allFrames: false }, files: [ 'backgroundColor.js' ] })",
@" browser.test.assertEq(results?.[0]?.result, 'pink', 'Result should be')",
@" browser.test.assertEq(results?.[0]?.frameId, 0, 'Frame should be')",
@" results = await browser.scripting.executeScript({ target: { tabId, frameIds: [ 0 ] }, files: [ 'backgroundColor.js' ] })",
@" browser.test.assertEq(results?.[0]?.result, 'pink', 'Result should be')",
@" browser.test.assertEq(results?.[0]?.frameId, 0, 'Frame should be')",
@" results = await browser.scripting.executeScript({ target: { tabId }, files: [ 'backgroundColor.js'] })",
@" browser.test.assertEq(results?.[0]?.result, 'pink', 'Result should be')",
@" browser.test.assertEq(results?.[0]?.frameId, 0, 'Frame should be')",
@" results = await browser.scripting.executeScript({ target: { tabId, frameIds: [ 0 ] }, func: changeBackgroundColor, args: ['pink'] })",
@" browser.test.assertEq(results?.[0]?.result, null, 'Result should be')",
@" browser.test.assertEq(results?.[0]?.frameId, 0, 'Frame should be')",
@" results = await browser.scripting.executeScript({",
@" target: { tabId },",
@" func: (bool, number, string, dict, array) => {",
@" browser.test.assertEq(typeof bool, 'boolean', 'Boolean argument should be')",
@" browser.test.assertEq(typeof number, 'number', 'Number argument should be')",
@" browser.test.assertEq(typeof string, 'string', 'String argument should be')",
@" browser.test.assertEq(typeof dict, 'object', 'Object argument should be')",
@" browser.test.assertTrue(Array.isArray(array), 'Array argument should be an array')",
@" },",
@" args: [true, 10, 'string', {}, []]",
@" })",
@" browser.test.assertEq(results?.[0]?.result, null, 'Result should be')",
@" browser.test.assertEq(results?.[0]?.frameId, 0, 'Frame should be')",
@" await browser.scripting.executeScript({ target: { tabId, allFrames: true }, func: changeBackgroundColor, args: ['blue'] })",
@" results = await browser.scripting.executeScript({ target: { tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results?.[0]?.result, blueValue, 'Result should be')",
@" browser.test.assertEq(results?.[0]?.frameId, 0, 'Frame should be')",
@" browser.test.assertSafeResolve(() => browser.scripting.executeScript({ target: { tabId, frameIds: [0], allFrames: false }, func: () => {} }))",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *backgroundColor = @"document.body.style.background = 'pink'";
static auto *resources = @{ @"background.js": backgroundScript, @"backgroundColor.js": backgroundColor };
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionWebNavigation];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, ExecuteScriptJSONTypes)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const tabId = details.tabId",
@" const expectedResult = { 'boolean': true, 'number': 42, 'string': 'Test String', 'object': { 'key': 'value' }, 'array': [1, 2, 3] }",
@" function returnJSONTypes() {",
@" return { 'boolean': true, 'number': 42, 'string': 'Test String', 'object': { 'key': 'value' }, 'array': [1, 2, 3] };",
@" }",
@" const results = await browser.scripting.executeScript({",
@" target: { tabId: tabId, allFrames: false },",
@" func: returnJSONTypes",
@" });",
@" browser.test.assertDeepEq(results?.[0]?.result, expectedResult);",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
auto manager = Util::loadExtension(scriptingManifest, @{ @"background.js": backgroundScript });
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionWebNavigation];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, ExecuteScriptWithFrameIds)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='/frame.html'></iframe>"_s } },
{ "/frame.html"_s, { { { "Content-Type"_s, "text/html"_s } }, " "_s } },
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.runtime.onMessage.addListener(async (message, sender) => {",
@" browser.test.assertEq(message, 'Hello from frame')",
@" const frameId = sender.frameId",
@" browser.test.assertTrue(frameId !== 0, 'frameId should not be 0 for an iframe')",
@" const result = await browser.scripting.executeScript({",
@" target: { tabId: sender.tab.id, frameIds: [frameId] },",
@" func: () => location.pathname",
@" })",
@" browser.test.assertEq(result[0].result, '/frame.html', 'Should execute in the iframe and return the correct path')",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')"
]);
auto *contentScript = Util::constructScript(@[
@"browser.runtime.sendMessage('Hello from frame')",
]);
static auto *manifest = @{
@"manifest_version": @3,
@"name": @"Scripting API Test",
@"description": @"Scripting API Test",
@"version": @"1.0",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module"
},
@"content_scripts": @[ @{
@"js": @[ @"content.js" ],
@"matches": @[ @"*://localhost/frame.html" ],
@"all_frames": @YES
} ],
@"permissions": @[ @"scripting" ]
};
auto *resources = @{
@"background.js": backgroundScript,
@"content.js": contentScript,
};
auto manager = Util::loadExtension(manifest, resources);
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:server.requestWithLocalhost("/frame.html"_s).URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:server.requestWithLocalhost()];
[manager run];
}
TEST(WKWebExtensionAPIScripting, ExecuteScriptWithDocumentIds)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<span>Main Document</span><iframe src='/frame.html'></iframe>"_s } },
{ "/frame.html"_s, { { { "Content-Type"_s, "text/html"_s } }, "<span>Frame Document</span>"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.runtime.onMessage.addListener(async (message, sender) => {",
@" browser.test.assertEq(message, 'Hello from frame')",
@" const documentId = sender?.documentId",
@" browser.test.assertEq(typeof documentId, 'string', 'sender.documentId should be')",
@" browser.test.assertEq(documentId.length, 36, 'sender.documentId should be')",
@" const result = await browser.scripting.executeScript({",
@" target: { tabId: sender?.tab?.id, documentIds: [ documentId ] },",
@" func: () => document.body.innerText.trim()",
@" })",
@" browser.test.assertEq(result?.[0]?.result, 'Frame Document', 'Result should be')",
@" browser.test.assertEq(typeof result?.[0]?.documentId, 'string', 'Result documentId should be')",
@" browser.test.assertEq(result?.[0]?.documentId.length, 36, 'Result documentId should be')",
@" browser.test.assertEq(result?.[0]?.documentId, documentId, 'Result documentId should be')",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')"
]);
auto *contentScript = Util::constructScript(@[
@"browser.runtime.sendMessage('Hello from frame')",
]);
static auto *manifest = @{
@"manifest_version": @3,
@"name": @"Scripting API Test",
@"description": @"Scripting API Test",
@"version": @"1.0",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module"
},
@"content_scripts": @[ @{
@"js": @[ @"content.js" ],
@"matches": @[ @"*://localhost/frame.html" ],
@"all_frames": @YES
} ],
@"permissions": @[ @"scripting" ]
};
auto *resources = @{
@"background.js": backgroundScript,
@"content.js": contentScript,
};
auto manager = Util::loadExtension(manifest, resources);
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:server.requestWithLocalhost("/frame.html"_s).URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:server.requestWithLocalhost()];
[manager run];
}
TEST(WKWebExtensionAPIScripting, InsertAndRemoveCSS)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='/frame.html'></iframe>"_s } },
{ "/frame.html"_s, { { { "Content-Type"_s, "text/html"_s } }, "<body style='background-color: blue'></body>"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"const pinkValue = 'rgb(255, 192, 203)'",
@"const blueValue = 'rgb(0, 0, 255)'",
@"const transparentValue = 'rgba(0, 0, 0, 0)'",
@"browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {",
@" if (tab.status !== 'complete')",
@" return",
@" function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@" function getFontSize() { return window.getComputedStyle(document.body).getPropertyValue('font-size') }",
@" await browser.scripting.insertCSS( { target: { tabId: tabId, allFrames: false }, files: [ 'backgroundColor.css' ] })",
@" let results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" await browser.scripting.removeCSS( { target: { tabId: tabId, allFrames: false }, files: [ 'backgroundColor.css' ] })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, transparentValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" await browser.scripting.insertCSS( { target: { tabId: tabId, allFrames: false }, css: 'body { background-color: pink !important }' })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" await browser.scripting.removeCSS( { target: { tabId: tabId, allFrames: false }, css: 'body { background-color: pink !important }' })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, transparentValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" await browser.scripting.insertCSS( { target: { tabId: tabId, allFrames: true }, files: [ 'backgroundColor.css' ] })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" browser.test.assertEq(results[1].result, pinkValue)",
@" await browser.scripting.removeCSS( { target: { tabId: tabId, allFrames: true }, files: [ 'backgroundColor.css' ] })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, transparentValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
// Storing original font size.
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: false }, func: getFontSize })",
@" let originalFontSize = results[0].result",
@" await browser.scripting.insertCSS( { target: { tabId: tabId, allFrames: true }, files: [ 'backgroundColor.css', 'fontSize.css' ] })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" browser.test.assertEq(results[1].result, pinkValue)",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getFontSize })",
@" browser.test.assertEq(results[0].result, '104px')",
@" browser.test.assertEq(results[1].result, '104px')",
@" await browser.scripting.removeCSS( { target: { tabId: tabId, allFrames: true }, files: [ 'backgroundColor.css', 'fontSize.css' ] })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, transparentValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getFontSize })",
@" browser.test.assertEq(results[0].result, originalFontSize)",
@" browser.test.assertEq(results[1].result, originalFontSize)",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *backgroundColor = @"body { background-color: pink !important }";
static auto *fontSize = @"body { font-size: 104px }";
static auto *resources = @{
@"background.js": backgroundScript,
@"backgroundColor.css": backgroundColor,
@"fontSize.css": fontSize,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, InsertAndRemoveCSSWithFrameIds)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='/frame.html'></iframe>"_s } },
{ "/frame.html"_s, { { { "Content-Type"_s, "text/html"_s } }, "<body style='background-color: blue'></body>"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"const pinkValue = 'rgb(255, 192, 203)'",
@"const blueValue = 'rgb(0, 0, 255)'",
@"const transparentValue = 'rgba(0, 0, 0, 0)'",
@"browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {",
@" if (tab.status !== 'complete')",
@" return",
@" function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@" function getFontSize() { return window.getComputedStyle(document.body).getPropertyValue('font-size') }",
@" function logMessage() { console.log('Logging message') }",
@" await browser.scripting.insertCSS( { target: { tabId: tabId, frameIds: [ 0 ] }, files: [ 'backgroundColor.css' ] })",
@" let results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" await browser.scripting.removeCSS( { target: { tabId: tabId, frameIds: [ 0 ] }, files: [ 'backgroundColor.css' ] })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, transparentValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" await browser.scripting.insertCSS( { target: { tabId: tabId, frameIds: [ 0 ] }, css: 'body { background-color: pink !important }' })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
@" await browser.scripting.removeCSS( { target: { tabId: tabId, frameIds: [ 0 ] }, css: 'body { background-color: pink !important }' })",
@" results = await browser.scripting.executeScript( { target: { tabId: tabId, allFrames: true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, transparentValue)",
@" browser.test.assertEq(results[1].result, blueValue)",
// FIXME: <https://webkit.org/b/262491> Test with subframe once there's support for style injections for specific frame Ids.
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *backgroundColor = @"body { background-color: pink !important }";
static auto *fontSize = @"body { font-size: 104px }";
static auto *resources = @{
@"background.js": backgroundScript,
@"backgroundColor.css": backgroundColor,
@"fontSize.css": fontSize,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, CSSUserOrigin)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<body style='color: red'></body>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {",
@" if (tab.status === 'complete') {",
@" await browser.test.assertSafeResolve(() => browser.scripting.insertCSS({ target: { tabId }, css: 'body { color: green !important }', origin: 'user' }))",
@" let results = await browser.test.assertSafeResolve(() => browser.scripting.executeScript({ target: { tabId }, func: () => getComputedStyle(document.body).color }))",
@" browser.test.assertEq(results[0]?.result, 'rgb(0, 128, 0)', 'Color should be green')",
@" browser.test.notifyPass()",
@" }",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
auto *resources = @{
@"background.js": backgroundScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, CSSAuthorOrigin)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<style> body { color: red } </style>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {",
@" if (tab.status === 'complete') {",
@" await browser.test.assertSafeResolve(() => browser.scripting.insertCSS({ target: { tabId }, css: 'body { color: green !important }', origin: 'author' }))",
@" let results = await browser.test.assertSafeResolve(() => browser.scripting.executeScript({ target: { tabId }, func: () => getComputedStyle(document.body).color }))",
@" browser.test.assertEq(results[0]?.result, 'rgb(0, 128, 0)', 'Color should be green')",
@" browser.test.notifyPass()",
@" }",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
auto *resources = @{
@"background.js": backgroundScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, World)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<script> const world = 'MAIN'; </script>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"function testWorld() {",
@" if (world)",
@" browser.test.notifyPass()",
@" else",
@" throw 'Variable not defined'",
@"}",
@"browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {",
@" if (tab.status === 'complete') {",
@" var results = await browser.scripting.executeScript( { target: { tabId, world: 'ISOLATED'}, func: testWorld })",
@" browser.test.assertEq(results[0]?.error, 'A JavaScript exception occurred')",
@" await browser.scripting.executeScript( { target: { tabId, world: 'MAIN'}, func: testWorld })",
@" browser.test.notifyPass()",
@" }",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
auto *resources = @{
@"background.js": backgroundScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, RegisterContentScripts)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@"function getFontSize() { return window.getComputedStyle(document.body).getPropertyValue('font-size') }",
@" const pinkValue = 'rgb(255, 192, 203)'",
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const tabId = details.tabId",
@" var expectedResults = [{",
@" id: '1',",
@" js: ['changeBackgroundColorScript.js', 'changeBackgroundFontScript.js'],",
@" matches: ['*://localhost/*'],",
@" persistAcrossSessions: true,",
@" }]",
@" var results",
@" var resultsPassingIds",
@" await browser.scripting.registerContentScripts([{ id: '1', matches: ['*://localhost/*'], js: ['changeBackgroundColorScript.js', 'changeBackgroundFontScript.js'] }])",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" resultsPassingIds = await browser.scripting.getRegisteredContentScripts({ ids: ['1'] })",
@" browser.test.assertDeepEq(results, resultsPassingIds)",
@" browser.test.assertDeepEq(results, expectedResults)",
// Verify scripts injected.
@" results = await browser.scripting.executeScript({ target: { tabId: tabId }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" results = await browser.scripting.executeScript({ target: { tabId: tabId }, func: getFontSize })",
@" browser.test.assertEq(results[0].result, '555px')",
@" await browser.scripting.unregisterContentScripts()",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"changeBackgroundColorScript.js": changeBackgroundColorScript,
@"changeBackgroundFontScript.js": changeBackgroundFontScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionWebNavigation];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, RegisterContentScriptsWithCSSUserOrigin)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<body style='color: red'></body>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const tabId = details.tabId",
@" await browser.scripting.registerContentScripts([{",
@" id: 'changeColor',",
@" matches: ['*://localhost/*'],",
@" css: ['changeColor.css'],",
@" cssOrigin: 'user'",
@" }])",
@" let results = await browser.test.assertSafeResolve(() =>",
@" browser.scripting.executeScript({",
@" target: { tabId: tabId },",
@" func: () => getComputedStyle(document.body).color",
@" })",
@" )",
@" browser.test.assertEq(results[0].result, 'rgb(0, 128, 0)', 'Color should be green')",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
auto *css = @"body { color: green !important }";
auto resources = @{
@"background.js": backgroundScript,
@"changeColor.css": css
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, RegisterContentScriptsWithCSSAuthorOrigin)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<style> body { color: red } </style>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const tabId = details.tabId",
@" await browser.scripting.registerContentScripts([{",
@" id: 'changeColor',",
@" matches: ['*://localhost/*'],",
@" css: ['changeColor.css'],",
@" cssOrigin: 'user'",
@" }])",
@" let results = await browser.test.assertSafeResolve(() =>",
@" browser.scripting.executeScript({",
@" target: { tabId: tabId },",
@" func: () => getComputedStyle(document.body).color",
@" })",
@" )",
@" browser.test.assertEq(results[0].result, 'rgb(0, 128, 0)', 'Color should be green')",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
auto *css = @"body { color: green !important }";
auto resources = @{
@"background.js": backgroundScript,
@"changeColor.css": css
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, RegisterContentScriptsMatchOriginAsFallback)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='about:blank'></iframe>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"await browser.scripting.registerContentScripts([{",
@" id: 'applyStylesAndScript',",
@" matches: ['*://localhost/*'],",
@" matchOriginAsFallback: true,",
@" allFrames: true,",
@" js: ['content.js'],",
@" css: ['applyStyles.css']",
@"}])",
@"browser.test.sendMessage('Scripts Registered');"
]);
auto *contentStyle = @"body { background-color: green !important }";
auto *contentScript = Util::constructScript(@[
@"browser.test.assertEq(document.body.dataset.injected, undefined, 'Script should not have run before')",
@"document.body.dataset.injected = 'true'",
@"const bgColor = getComputedStyle(document.body).backgroundColor",
@"browser.test.assertEq(bgColor, 'rgb(0, 128, 0)', 'CSS should be applied correctly')",
@"browser.test.sendMessage(window.top === window ? 'Main Frame Injected' : 'Sub-Frame Injected')"
]);
auto *resources = @{
@"background.js": backgroundScript,
@"content.js": contentScript,
@"applyStyles.css": contentStyle
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Scripts Registered"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager runUntilTestMessage:@"Main Frame Injected"];
[manager runUntilTestMessage:@"Sub-Frame Injected"];
}
TEST(WKWebExtensionAPIScripting, UpdateContentScripts)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='/frame.html'></iframe>"_s } },
{ "/frame.html"_s, { { { "Content-Type"_s, "text/html"_s } }, "<body style='background-color: blue'></body>"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@"function getFontSize() { return window.getComputedStyle(document.body).getPropertyValue('font-size') }",
@" const pinkValue = 'rgb(255, 192, 203)'",
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const tabId = details.tabId",
@" var expectedResults = [{",
@" id: '1',",
@" js: ['changeBackgroundColorScript.js'],",
@" matches: ['*://localhost/*'],",
@" persistAcrossSessions: true,",
@" }]",
@" var results",
@" await browser.scripting.registerContentScripts([{ id: '1', matches: ['*://localhost/*'], js: ['changeBackgroundColorScript.js'] }])",
@" await browser.scripting.updateContentScripts([{ id: '1', allFrames: true, persistAcrossSessions: false, runAt: 'document_start', world: 'MAIN' }])",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" expectedResults[0].allFrames = true",
@" expectedResults[0].persistAcrossSessions = false",
@" expectedResults[0].runAt = 'document_start'",
@" expectedResults[0].world = 'main'",
@" browser.test.assertDeepEq(results, expectedResults)",
@" await browser.scripting.updateContentScripts([{ id: '1', js: ['changeBackgroundColorScript.js', 'changeBackgroundFontScript.js'] }])",
@" expectedResults[0].js = ['changeBackgroundColorScript.js', 'changeBackgroundFontScript.js']",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertDeepEq(results, expectedResults)",
// Verify scripts injected.
@" results = await browser.scripting.executeScript({ target: { tabId: tabId, allFrames:true }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
@" browser.test.assertEq(results[1].result, pinkValue)",
@" results = await browser.scripting.executeScript({ target: { tabId: tabId, allFrames:true }, func: getFontSize })",
@" browser.test.assertEq(results[0].result, '555px')",
@" browser.test.assertEq(results[1].result, '555px')",
@" await browser.scripting.unregisterContentScripts()",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"changeBackgroundColorScript.js": changeBackgroundColorScript,
@"changeBackgroundFontScript.js": changeBackgroundFontScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionWebNavigation];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, UpdateContentScriptsWithMinimalParametersShouldNotCrash)
{
auto *backgroundScript = Util::constructScript(@[
@"await browser.scripting.registerContentScripts([{ id: '1', matches: ['*://localhost/*'], js: ['script.js'] }])",
@"await browser.scripting.updateContentScripts([{ id: '1', allFrames: true }])",
@"const results = await browser.scripting.getRegisteredContentScripts()",
@"browser.test.assertDeepEq(results, [{ id: '1', matches: ['*://localhost/*'], js: ['script.js'], allFrames: true, persistAcrossSessions: true }])",
@"browser.test.notifyPass()",
]);
auto *resources = @{
@"background.js": backgroundScript,
@"script.js": @"document.body.style.backgroundColor = 'red'",
};
Util::loadAndRunExtension(scriptingManifest, resources);
}
TEST(WKWebExtensionAPIScripting, GetContentScripts)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async () => {",
@" var expectedResults = [{",
@" id: '1',",
@" js: ['changeBackgroundColorScript.js'],",
@" matches: ['*://localhost/*'],",
@" persistAcrossSessions: true,",
@" }]",
@" var results",
@" await browser.scripting.registerContentScripts([{ id: '1', matches: ['*://localhost/*'], js: ['changeBackgroundColorScript.js'] }])",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertDeepEq(results, expectedResults)",
@" results = await browser.scripting.getRegisteredContentScripts({ 'ids': ['1'] })",
@" browser.test.assertDeepEq(results, expectedResults)",
// Unrecognized ids should be ignored.
@" results = await browser.scripting.getRegisteredContentScripts({ 'ids': ['1', '2'] })",
@" browser.test.assertDeepEq(results, expectedResults)",
@" await browser.scripting.registerContentScripts([{ id: '2', matches: ['*://localhost/*'], js: ['changeBackgroundFontScript.js'], runAt: 'document_start', world: 'MAIN' }])",
@" results = await browser.scripting.getRegisteredContentScripts({ 'ids': ['1', '2'] })",
@" expectedResults.push({ id: '2', js: ['changeBackgroundFontScript.js'], matches: ['*://localhost/*'], persistAcrossSessions: true, runAt: 'document_start', world: 'main' })",
@" browser.test.assertDeepEq(results, expectedResults)",
@" await browser.scripting.unregisterContentScripts()",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"changeBackgroundColorScript.js": changeBackgroundColorScript,
@"changeBackgroundFontScript.js": changeBackgroundFontScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionWebNavigation];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, UnregisterContentScripts)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@" const pinkValue = 'rgb(255, 192, 203)'",
@" const transparentValue = 'rgba(0, 0, 0, 0)'",
@" browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const tabId = details.tabId",
@" await browser.scripting.registerContentScripts([{ id: '1', matches: ['*://localhost/*'], js: ['changeBackgroundColorScript.js'] }])",
@" var results",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertEq(results.length, 1)",
@" await browser.scripting.unregisterContentScripts()",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertEq(results.length, 0)",
// Unrecognized ids should return an error and result in a no-op.
@" await browser.scripting.registerContentScripts([{ id: '1', matches: ['*://localhost/*'], js: ['changeBackgroundColorScript.js'] }])",
@" await browser.test.assertRejects(browser.scripting.unregisterContentScripts({ 'ids': ['1', '2'] }))",
@" results = await browser.scripting.executeScript({ target: { tabId: tabId }, func: getBackgroundColor })",
@" browser.test.assertEq(results[0].result, pinkValue)",
// Tests removal with multiple ids.
@" await browser.scripting.registerContentScripts([{ id: '2', matches: ['*://localhost/*'], js: ['changeBackgroundFontScript.js'] }])",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertEq(results.length, 2)",
@" await browser.scripting.unregisterContentScripts({ 'ids': ['1', '2'] })",
@" results = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertEq(results.length, 0)",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"changeBackgroundColorScript.js": changeBackgroundColorScript,
@"changeBackgroundFontScript.js": changeBackgroundFontScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionWebNavigation];
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, RegisteredScriptIsInjectedAfterContextReloads)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const pinkValue = 'rgb(255, 192, 203)'",
@" function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@" let results = await browser.scripting.executeScript({ target: { tabId: details.tabId, allFrames: false }, func: getBackgroundColor })",
@" browser.test.assertEq(results?.[0]?.result, pinkValue)",
@" browser.test.notifyPass()",
@"})",
@"let registeredScripts = await browser.scripting.getRegisteredContentScripts()",
@"if (!registeredScripts.length) {",
@" await browser.scripting.registerContentScripts([{ id: '1', matches: [ '*://localhost/*' ], js: [ 'changeBackgroundColorScript.js' ] }])",
@" registeredScripts = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertEq(registeredScripts.length, 1)",
@" browser.test.sendMessage('Unload extension')",
@"} else {",
@" browser.test.assertEq(registeredScripts.length, 1)",
@" browser.test.sendMessage('Load Tab')",
@"}"
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"changeBackgroundColorScript.js": changeBackgroundColorScript,
};
auto manager = Util::parseExtension(scriptingManifest, resources, WKWebExtensionControllerConfiguration._temporaryConfiguration);
// Give the extension a unique identifier so it opts into saving data in the temporary configuration.
manager.get().context.uniqueIdentifier = @"org.webkit.test.extension (76C788B8)";
EXPECT_FALSE(manager.get().context.hasInjectedContent);
[manager load];
[manager runUntilTestMessage:@"Unload extension"];
EXPECT_TRUE(manager.get().context.hasInjectedContent);
[manager unload];
EXPECT_FALSE(manager.get().context.hasInjectedContent);
[manager load];
[manager runUntilTestMessage:@"Load Tab"];
EXPECT_TRUE(manager.get().context.hasInjectedContent);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, PersistentRegisteredScriptIsInjectedAfterContextReloads)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async (details) => {",
@" const pinkValue = 'rgb(255, 192, 203)'",
@" function getBackgroundColor() { return window.getComputedStyle(document.body).getPropertyValue('background-color') }",
@" let results = await browser.scripting.executeScript({ target: { tabId: details.tabId, allFrames: false }, func: getBackgroundColor })",
@" browser.test.assertEq(results?.[0]?.result, pinkValue)",
@" browser.test.notifyPass()",
@"})",
@"let registeredScripts = await browser.scripting.getRegisteredContentScripts()",
@"if (!registeredScripts.length) {",
@" await browser.scripting.registerContentScripts([{ id: '1', matches: [ '*://localhost/*' ], js: [ 'changeBackgroundColorScript.js' ], 'allFrames': true, 'persistAcrossSessions': true }])",
@" registeredScripts = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertEq(registeredScripts.length, 1)",
@" browser.test.sendMessage('Unload extension')",
@"} else {",
@" browser.test.assertEq(registeredScripts.length, 1)",
@" browser.test.sendMessage('Load Tab')",
@"}"
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"changeBackgroundColorScript.js": changeBackgroundColorScript,
};
auto manager = Util::parseExtension(scriptingManifest, resources, WKWebExtensionControllerConfiguration._temporaryConfiguration);
// Give the extension a unique identifier so it opts into saving data in the temporary configuration.
manager.get().context.uniqueIdentifier = @"org.webkit.test.extension (76C788B8)";
EXPECT_FALSE(manager.get().context.hasInjectedContent);
[manager load];
[manager runUntilTestMessage:@"Unload extension"];
EXPECT_TRUE(manager.get().context.hasInjectedContent);
[manager unload];
EXPECT_FALSE(manager.get().context.hasInjectedContent);
[manager load];
[manager runUntilTestMessage:@"Load Tab"];
EXPECT_TRUE(manager.get().context.hasInjectedContent);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, RegisteredScriptExcludeMatches)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *backgroundScript = Util::constructScript(@[
@"browser.webNavigation.onCompleted.addListener(async () => {",
@" var expectedResults = [{",
@" id: '1',",
@" js: ['changeBackgroundColorScript.js'],",
@" matches: ['*://*/*'],",
@" persistAcrossSessions: true,",
@" excludeMatches: ['*://*.example.com/*']",
@" }]",
@" await browser.scripting.registerContentScripts([{ id: '1', matches: ['*://*/*'], js: ['changeBackgroundColorScript.js'], excludeMatches: ['*://*.example.com/*']}])",
@" var results = await browser.scripting.getRegisteredContentScripts()",
@" browser.test.assertDeepEq(results, expectedResults)",
@" results = await browser.scripting.getRegisteredContentScripts({ 'ids': ['1'] })",
@" browser.test.assertDeepEq(results, expectedResults)",
@" await browser.scripting.unregisterContentScripts()",
@" browser.test.notifyPass()",
@"})",
@"browser.test.sendMessage('Load Tab')",
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"changeBackgroundColorScript.js": changeBackgroundColorScript,
};
auto manager = Util::loadExtension(scriptingManifest, resources);
auto *testContext = manager.get().context;
// Confirm that the script can't be injected on example.com.
auto *exampleURL = [NSURL URLWithString:@"https://example.com/"];
EXPECT_FALSE([testContext hasInjectedContentForURL:exampleURL]);
auto *urlRequest = server.requestWithLocalhost();
auto *url = urlRequest.URL;
auto *matchPattern = [WKWebExtensionMatchPattern matchPatternWithScheme:url.scheme host:url.host path:@"/*"];
[testContext setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forPermission:WKWebExtensionPermissionWebNavigation];
[testContext setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forMatchPattern:matchPattern];
[manager runUntilTestMessage:@"Load Tab"];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, InjectedOnlyOnce)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='about:blank'></iframe>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"js": @[ @"content_script.js" ]
} ]
};
auto *contentScript = Util::constructScript(@[
@"if (document.body.dataset.injected)",
@" browser.test.notifyFail('Script injected more than once')",
@"document.body.dataset.injected = 'true'",
@"browser.test.sendMessage('script-injected')"
]);
auto *resources = @{
@"content_script.js": contentScript
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager runUntilTestMessage:@"script-injected"];
[manager runForTimeInterval:3];
}
TEST(WKWebExtensionAPIScripting, MainWorld)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<script>window.secretValue = 'This is a secret.'</script>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"js": @[ @"content_script.js" ],
@"world": @"MAIN"
} ]
};
auto *contentScript = Util::constructScript(@[
@"browser.test.assertEq(window.secretValue, 'This is a secret.')",
// Externally connectable exposes a limited browser but not chrome.
@"browser.test.assertEq(window.chrome, undefined)",
@"browser.test.assertEq(typeof window.browser?.runtime, 'object')",
@"browser.test.assertEq(window.browser?.runtime?.id, undefined)",
@"browser.test.notifyPass()"
]);
auto *resources = @{
@"content_script.js": contentScript
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, IsolatedWorld)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<script>window.secretValue = 'This is a secret.'</script>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"js": @[ @"content_script.js" ],
@"world": @"ISOLATED"
} ]
};
auto *contentScript = Util::constructScript(@[
@"browser.test.assertEq(window.secretValue, undefined)",
// Both chrome and browser should be exposed.
@"browser.test.assertEq(typeof window.chrome?.runtime?.id, 'string')",
@"browser.test.assertEq(typeof window.browser?.runtime?.id, 'string')",
@"browser.test.notifyPass()"
]);
auto *resources = @{
@"content_script.js": contentScript
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, MatchAboutBlank)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='about:blank'></iframe>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"match_about_blank": @YES,
@"all_frames": @YES,
@"js": @[ @"content.js" ],
@"css": @[ @"content.css" ]
} ]
};
auto *contentStyle = @"body { background-color: red !important; }";
auto *contentScript = Util::constructScript(@[
@"browser.test.assertEq(document.body.dataset.injected, undefined, 'Script should not have run before')",
@"document.body.dataset.injected = 'true'",
@"const bgColor = getComputedStyle(document.body).backgroundColor",
@"browser.test.assertEq(bgColor, 'rgb(255, 0, 0)', 'CSS should be applied correctly')",
@"browser.test.sendMessage(window.top === window ? 'Main Frame Injected' : 'Sub-Frame Injected')"
]);
auto *resources = @{
@"content.js": contentScript,
@"content.css": contentStyle
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager runUntilTestMessage:@"Main Frame Injected"];
[manager runUntilTestMessage:@"Sub-Frame Injected"];
}
TEST(WKWebExtensionAPIScripting, MatchOriginAsFallbackWithAboutBlank)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='about:blank'></iframe>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"match_origin_as_fallback": @YES,
@"all_frames": @YES,
@"js": @[ @"content.js" ],
@"css": @[ @"content.css" ]
} ]
};
auto *contentStyle = @"body { background-color: green !important; }";
auto *contentScript = Util::constructScript(@[
@"browser.test.assertEq(document.body.dataset.injected, undefined, 'Script should not have run before')",
@"document.body.dataset.injected = 'true'",
@"const bgColor = getComputedStyle(document.body).backgroundColor",
@"browser.test.assertEq(bgColor, 'rgb(0, 128, 0)', 'CSS should be applied correctly')",
@"browser.test.sendMessage(window.top === window ? 'Main Frame Injected' : 'Sub-Frame Injected')"
]);
auto *resources = @{
@"content.js": contentScript,
@"content.css": contentStyle
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager runUntilTestMessage:@"Main Frame Injected"];
[manager runUntilTestMessage:@"Sub-Frame Injected"];
}
TEST(WKWebExtensionAPIScripting, MatchOriginAsFallbackWithData)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<iframe src='data:text/html,<body></body>'></iframe>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"match_origin_as_fallback": @YES,
@"all_frames": @YES,
@"js": @[ @"content.js" ],
@"css": @[ @"content.css" ]
} ]
};
auto *contentStyle = @"body { background-color: blue !important; }";
auto *contentScript = Util::constructScript(@[
@"browser.test.assertEq(document.body.dataset.injected, undefined, 'Script should not have run before')",
@"document.body.dataset.injected = 'true'",
@"const bgColor = getComputedStyle(document.body).backgroundColor",
@"browser.test.assertEq(bgColor, 'rgb(0, 0, 255)', 'CSS should be applied correctly')",
@"browser.test.sendMessage(window.top === window ? 'Main Frame Injected' : 'Sub-Frame Injected')"
]);
auto *resources = @{
@"content.js": contentScript,
@"content.css": contentStyle
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager runUntilTestMessage:@"Main Frame Injected"];
[manager runUntilTestMessage:@"Sub-Frame Injected"];
}
TEST(WKWebExtensionAPIScripting, MatchOriginAsFallbackWithBlob)
{
auto *pageScript = Util::constructScript(@[
@"<script>",
@" const blob = new Blob(['<body></body>'], { type: 'text/html' })",
@" const blobURL = URL.createObjectURL(blob)",
@" document.write(`<iframe src='${blobURL}'></iframe>`)",
@"</script>"
]);
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, pageScript } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"match_origin_as_fallback": @YES,
@"all_frames": @YES,
@"js": @[ @"content.js" ],
@"css": @[ @"content.css" ]
} ]
};
auto *contentStyle = @"body { background-color: green !important; }";
auto *contentScript = Util::constructScript(@[
@"browser.test.assertEq(document.body.dataset.injected, undefined, 'Script should not have run before')",
@"document.body.dataset.injected = 'true'",
@"const bgColor = getComputedStyle(document.body).backgroundColor",
@"browser.test.assertEq(bgColor, 'rgb(0, 128, 0)', 'CSS should be applied correctly')",
@"browser.test.sendMessage(window.top === window ? 'Main Frame Injected' : 'Sub-Frame Injected')"
]);
auto *resources = @{
@"content.js": contentScript,
@"content.css": contentStyle
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager runUntilTestMessage:@"Main Frame Injected"];
[manager runUntilTestMessage:@"Sub-Frame Injected"];
}
TEST(WKWebExtensionAPIScripting, RemoveAllUserScriptsDoesNotRemoveWebExtensionScripts)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, ""_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"js": @[ @"content.js" ]
} ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.sendMessage('Remove Scripts and Load Tab')"
]);
auto *contentScript = Util::constructScript(@[
@"browser.test.notifyPass()"
]);
auto *resources = @{
@"background.js": backgroundScript,
@"content.js": contentScript
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Remove Scripts and Load Tab"];
[manager.get().defaultTab.webView.configuration.userContentController removeAllUserScripts];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, RemoveAllUserStyleSheetsDoesNotRemoveWebExtensionStyleSheets)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s } }, "<div id='test'>Test</div>"_s } }
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *contentScriptsManifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"background": @{
@"scripts": @[ @"background.js" ],
@"type": @"module",
@"persistent": @NO,
},
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"css": @[ @"content.css" ],
@"js": @[ @"content.js" ]
} ]
};
auto *backgroundScript = Util::constructScript(@[
@"browser.test.sendMessage('Remove StyleSheets and Load Tab')"
]);
auto *contentScript = Util::constructScript(@[
@"const testElement = document.getElementById('test')",
@"const style = window.getComputedStyle(testElement)",
@"browser.test.assertEq(style.color, 'rgb(255, 0, 0)', 'CSS should still apply after removing user stylesheets')",
@"browser.test.notifyPass()"
]);
auto *resources = @{
@"background.js": backgroundScript,
@"content.css": @"#test { color: red; }",
@"content.js": contentScript
};
auto manager = Util::loadExtension(contentScriptsManifest, resources);
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager runUntilTestMessage:@"Remove StyleSheets and Load Tab"];
[manager.get().defaultTab.webView.configuration.userContentController _removeAllUserStyleSheets];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, InjectScriptWithTrustedTypesCSP)
{
TestWebKitAPI::HTTPServer server({
{ "/"_s, { { { "Content-Type"_s, "text/html"_s }, { "Content-Security-Policy"_s, "require-trusted-types-for 'script'"_s } }, ""_s } },
}, TestWebKitAPI::HTTPServer::Protocol::Http);
auto *manifest = @{
@"manifest_version": @3,
@"name": @"Scripting Test",
@"description": @"Scripting Test",
@"version": @"1.0",
@"content_scripts": @[ @{
@"matches": @[ @"*://localhost/*" ],
@"js": @[ @"content.js" ]
} ]
};
auto *contentScript = Util::constructScript(@[
@"try {",
@" document.body.innerHTML = '<p>Injected</p>'",
@" browser.test.notifyPass()",
@"} catch (error) {",
@" browser.test.notifyFail('CSP blocked innerHTML: ' + error.message)",
@"}",
]);
auto manager = Util::loadExtension(manifest, @{ @"content.js": contentScript });
auto *urlRequest = server.requestWithLocalhost();
[manager.get().context setPermissionStatus:WKWebExtensionContextPermissionStatusGrantedExplicitly forURL:urlRequest.URL];
[manager.get().defaultTab.webView loadRequest:urlRequest];
[manager run];
}
TEST(WKWebExtensionAPIScripting, MigrateScriptDataToNewFormat)
{
auto *backgroundScript = Util::constructScript(@[
@"var expectedResults = [{",
@" id: '1',",
@" js: ['content.js'],",
@" matches: ['*://localhost/*'],",
@" persistAcrossSessions: true",
@"}]",
@"var results",
@"var resultsPassingIds",
@"results = await browser.scripting.getRegisteredContentScripts()",
@"resultsPassingIds = await browser.scripting.getRegisteredContentScripts({ ids: ['1'] })",
@"browser.test.assertDeepEq(results, resultsPassingIds)",
@"browser.test.assertDeepEq(results, expectedResults)",
@"browser.test.notifyPass()",
]);
static auto *resources = @{
@"background.js": backgroundScript,
@"content.js": Util::constructScript(@[]),
};
auto manager = Util::parseExtension(scriptingManifest, resources, WKWebExtensionControllerConfiguration._temporaryConfiguration);
// Give the extension a unique identifier so it opts into saving data in the temporary configuration.
manager.get().context.uniqueIdentifier = @"org.webkit.test.extension (76C788B8)";
[manager load];
auto *storageDirectory = manager.get().controller.configuration._storageDirectoryPath;
storageDirectory = [storageDirectory stringByAppendingPathComponent:manager.get().context.uniqueIdentifier];
static auto *files = @[
[NSBundle.test_resourcesBundle URLForResource:@"RegisteredContentScripts" withExtension:@"db"],
[NSBundle.test_resourcesBundle URLForResource:@"RegisteredContentScripts" withExtension:@"db-shm"],
[NSBundle.test_resourcesBundle URLForResource:@"RegisteredContentScripts" withExtension:@"db-wal"]
];
for (NSURL *file in files) {
NSString *combinedPath = [storageDirectory stringByAppendingPathComponent:[file lastPathComponent]];
[NSFileManager.defaultManager copyItemAtURL:file toURL:[NSURL fileURLWithPath:combinedPath] error:nil];
}
[manager run];
}
} // namespace TestWebKitAPI
#endif // ENABLE(WK_WEB_EXTENSIONS)