forked from mirrors/gecko-dev
Bug 1811443 - Support optional background.type "module" for WebExtensions Event Page scripts. r=willdurand,robwu
Differential Revision: https://phabricator.services.mozilla.com/D169922
This commit is contained in:
parent
58c4c9c0ff
commit
94cb12435e
8 changed files with 169 additions and 3 deletions
|
|
@ -316,5 +316,8 @@ dictionary WebExtensionInit {
|
|||
sequence<DOMString>? backgroundScripts = null;
|
||||
DOMString? backgroundWorkerScript = null;
|
||||
|
||||
// Whether the background scripts should be loaded as ES modules.
|
||||
boolean backgroundTypeModule = false;
|
||||
|
||||
Promise<WebExtensionPolicy?> readyPromise;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2760,6 +2760,10 @@ class Extension extends ExtensionData {
|
|||
return this.manifest.background?.scripts;
|
||||
}
|
||||
|
||||
get backgroundTypeModule() {
|
||||
return this.manifest.background?.type === "module";
|
||||
}
|
||||
|
||||
get backgroundWorkerScript() {
|
||||
return this.manifest.background?.service_worker;
|
||||
}
|
||||
|
|
@ -2828,6 +2832,7 @@ class Extension extends ExtensionData {
|
|||
return {
|
||||
backgroundScripts: this.backgroundScripts,
|
||||
backgroundWorkerScript: this.backgroundWorkerScript,
|
||||
backgroundTypeModule: this.backgroundTypeModule,
|
||||
childModules: this.modules && this.modules.child,
|
||||
dependencies: this.dependencies,
|
||||
persistentBackground: this.persistentBackground,
|
||||
|
|
|
|||
|
|
@ -142,6 +142,14 @@ ExtensionManager = {
|
|||
({ backgroundWorkerScript } = getData(extension, "extendedData") || {});
|
||||
}
|
||||
|
||||
let { backgroundTypeModule } = extension;
|
||||
if (
|
||||
backgroundTypeModule == null &&
|
||||
WebExtensionPolicy.isExtensionProcess
|
||||
) {
|
||||
({ backgroundTypeModule } = getData(extension, "extendedData") || {});
|
||||
}
|
||||
|
||||
policy = new WebExtensionPolicy({
|
||||
id: extension.id,
|
||||
mozExtensionHostname: extension.uuid,
|
||||
|
|
@ -162,6 +170,7 @@ ExtensionManager = {
|
|||
|
||||
backgroundScripts,
|
||||
backgroundWorkerScript,
|
||||
backgroundTypeModule,
|
||||
|
||||
contentScripts: extension.contentScripts,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ using namespace dom;
|
|||
|
||||
static const char kProto[] = "moz-extension";
|
||||
|
||||
static const char kBackgroundScriptTypeDefault[] = "text/javascript";
|
||||
|
||||
static const char kBackgroundScriptTypeModule[] = "module";
|
||||
|
||||
static const char kBackgroundPageHTMLStart[] =
|
||||
"<!DOCTYPE html>\n\
|
||||
<html>\n\
|
||||
|
|
@ -38,7 +42,7 @@ static const char kBackgroundPageHTMLStart[] =
|
|||
|
||||
static const char kBackgroundPageHTMLScript[] =
|
||||
"\n\
|
||||
<script type=\"text/javascript\" src=\"%s\"></script>";
|
||||
<script type=\"%s\" src=\"%s\"></script>";
|
||||
|
||||
static const char kBackgroundPageHTMLEnd[] =
|
||||
"\n\
|
||||
|
|
@ -297,6 +301,8 @@ WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
|
|||
aInit.mBackgroundScripts.Value());
|
||||
}
|
||||
|
||||
mBackgroundTypeModule = aInit.mBackgroundTypeModule;
|
||||
|
||||
mContentScripts.SetCapacity(aInit.mContentScripts.Length());
|
||||
for (const auto& scriptInit : aInit.mContentScripts) {
|
||||
// The activeTab permission is only for dynamically injected scripts,
|
||||
|
|
@ -525,11 +531,13 @@ nsCString WebExtensionPolicy::BackgroundPageHTML() const {
|
|||
|
||||
result.AppendLiteral(kBackgroundPageHTMLStart);
|
||||
|
||||
const char* scriptType = mBackgroundTypeModule ? kBackgroundScriptTypeModule
|
||||
: kBackgroundScriptTypeDefault;
|
||||
|
||||
for (auto& script : mBackgroundScripts.Value()) {
|
||||
nsCString escaped;
|
||||
nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped);
|
||||
|
||||
result.AppendPrintf(kBackgroundPageHTMLScript, escaped.get());
|
||||
result.AppendPrintf(kBackgroundPageHTMLScript, scriptType, escaped.get());
|
||||
}
|
||||
|
||||
result.AppendLiteral(kBackgroundPageHTMLEnd);
|
||||
|
|
|
|||
|
|
@ -368,6 +368,8 @@ class WebExtensionPolicy final : public nsISupports, public nsWrapperCache {
|
|||
|
||||
dom::Nullable<nsTArray<nsString>> mBackgroundScripts;
|
||||
|
||||
bool mBackgroundTypeModule = false;
|
||||
|
||||
nsTArray<RefPtr<WebExtensionContentScript>> mContentScripts;
|
||||
|
||||
RefPtr<dom::Promise> mReadyPromise;
|
||||
|
|
|
|||
|
|
@ -148,6 +148,11 @@
|
|||
"type": "array",
|
||||
"items": { "$ref": "ExtensionURL" }
|
||||
},
|
||||
"type": {
|
||||
"optional": true,
|
||||
"type": "string",
|
||||
"enum": ["module", "classic"]
|
||||
},
|
||||
"persistent": {
|
||||
"optional": true,
|
||||
"type": "boolean",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
async function assertBackgroundScriptTypes(
|
||||
extensionTestWrapper,
|
||||
expectedScriptTypesMap
|
||||
) {
|
||||
const { baseURI } = extensionTestWrapper.extension;
|
||||
let expectedMapWithResolvedURLs = Object.keys(expectedScriptTypesMap).reduce(
|
||||
(result, scriptPath) => {
|
||||
result[baseURI.resolve(scriptPath)] = expectedScriptTypesMap[scriptPath];
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
const page = await ExtensionTestUtils.loadContentPage(
|
||||
baseURI.resolve("_generated_background_page.html")
|
||||
);
|
||||
const scriptTypesMap = await page.spawn([], () => {
|
||||
const scripts = Array.from(
|
||||
this.content.document.querySelectorAll("script")
|
||||
);
|
||||
return scripts.reduce((result, script) => {
|
||||
result[script.getAttribute("src")] = script.getAttribute("type");
|
||||
return result;
|
||||
}, {});
|
||||
});
|
||||
await page.close();
|
||||
Assert.deepEqual(
|
||||
scriptTypesMap,
|
||||
expectedMapWithResolvedURLs,
|
||||
"Got the expected script type from the generated background page"
|
||||
);
|
||||
}
|
||||
|
||||
async function testBackgroundScriptClassic({ manifestTypeClassicSet }) {
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
background: {
|
||||
scripts: ["anotherScript.js", "main.js"],
|
||||
type: manifestTypeClassicSet ? "classic" : undefined,
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"main.js": ``,
|
||||
"anotherScript.js": ``,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await assertBackgroundScriptTypes(extension, {
|
||||
"main.js": "text/javascript",
|
||||
"anotherScript.js": "text/javascript",
|
||||
});
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function test_background_scripts_type_default() {
|
||||
await testBackgroundScriptClassic({ manifestTypeClassicSet: false });
|
||||
});
|
||||
|
||||
add_task(async function test_background_scripts_type_classic() {
|
||||
await testBackgroundScriptClassic({ manifestTypeClassicSet: true });
|
||||
});
|
||||
|
||||
add_task(async function test_background_scripts_type_module() {
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
background: {
|
||||
scripts: ["anotherModule.js", "mainModule.js"],
|
||||
type: "module",
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"mainModule.js": `
|
||||
import { initBackground } from "/importedModule.js";
|
||||
browser.test.log("mainModule.js - ESM module executing");
|
||||
initBackground();
|
||||
`,
|
||||
"importedModule.js": `
|
||||
export function initBackground() {
|
||||
browser.test.onMessage.addListener((msg) => {
|
||||
browser.test.log("importedModule.js - test message received");
|
||||
browser.test.sendMessage("esm-module-reply", msg);
|
||||
});
|
||||
browser.test.log("importedModule.js - initBackground executed");
|
||||
}
|
||||
browser.test.log("importedModule.js - ESM module loaded");
|
||||
`,
|
||||
"anotherModule.js": `
|
||||
browser.test.log("anotherModule.js - ESM module loaded");
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.sendMessage("test-event-value");
|
||||
equal(
|
||||
await extension.awaitMessage("esm-module-reply"),
|
||||
"test-event-value",
|
||||
"Got the expected event from the ESM module loaded from the background script"
|
||||
);
|
||||
await assertBackgroundScriptTypes(extension, {
|
||||
"mainModule.js": "module",
|
||||
"anotherModule.js": "module",
|
||||
});
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_background_scripts_type_invalid() {
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
background: {
|
||||
scripts: ["anotherScript.js", "main.js"],
|
||||
type: "invalid",
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"main.js": ``,
|
||||
"anotherScript.js": ``,
|
||||
},
|
||||
});
|
||||
|
||||
ExtensionTestUtils.failOnSchemaWarnings(false);
|
||||
await Assert.rejects(
|
||||
extension.startup(),
|
||||
/Error processing background: .* \.type must be one of/,
|
||||
"Expected install to fail"
|
||||
);
|
||||
ExtensionTestUtils.failOnSchemaWarnings(true);
|
||||
});
|
||||
|
|
@ -28,6 +28,7 @@ skip-if = os == "android" # Android does not use Places for history.
|
|||
[test_ext_background_sub_windows.js]
|
||||
[test_ext_background_teardown.js]
|
||||
[test_ext_background_telemetry.js]
|
||||
[test_ext_background_type_module.js]
|
||||
[test_ext_background_window_properties.js]
|
||||
skip-if = os == "android"
|
||||
[test_ext_browserSettings.js]
|
||||
|
|
|
|||
Loading…
Reference in a new issue