mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-13 05:15:45 +00:00
1478e741fe
MozReview-Commit-ID: 4lKFqoJHVFb --HG-- rename : toolkit/components/extensions/ExtensionChild.jsm => toolkit/components/extensions/ExtensionPageChild.jsm extra : rebase_source : 904829043360c85e5d903a64aae32ab20ccdeb25 extra : absorb_source : 6b99da372da1b827a487594bc873a6ee6fcdc425
248 lines
8.5 KiB
JavaScript
248 lines
8.5 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
this.EXPORTED_SYMBOLS = ["LegacyExtensionsUtils"];
|
|
|
|
/* exported LegacyExtensionsUtils, LegacyExtensionContext */
|
|
|
|
/**
|
|
* This file exports helpers for Legacy Extensions that want to embed a webextensions
|
|
* and exchange messages with the embedded WebExtension.
|
|
*/
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Extension",
|
|
"resource://gre/modules/Extension.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild",
|
|
"resource://gre/modules/ExtensionChild.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
|
"resource://gre/modules/Services.jsm");
|
|
|
|
Cu.import("resource://gre/modules/ExtensionCommon.jsm");
|
|
|
|
var {
|
|
BaseContext,
|
|
} = ExtensionCommon;
|
|
|
|
/**
|
|
* Instances created from this class provide to a legacy extension
|
|
* a simple API to exchange messages with a webextension.
|
|
*/
|
|
var LegacyExtensionContext = class extends BaseContext {
|
|
/**
|
|
* Create a new LegacyExtensionContext given a target Extension instance.
|
|
*
|
|
* @param {Extension} targetExtension
|
|
* The webextension instance associated with this context. This will be the
|
|
* instance of the newly created embedded webextension when this class is
|
|
* used through the EmbeddedWebExtensionsUtils.
|
|
*/
|
|
constructor(targetExtension) {
|
|
super("legacy_extension", targetExtension);
|
|
|
|
// Legacy Extensions (xul overlays, bootstrap restartless and Addon SDK)
|
|
// runs with a systemPrincipal.
|
|
let addonPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
|
Object.defineProperty(
|
|
this, "principal",
|
|
{value: addonPrincipal, enumerable: true, configurable: true}
|
|
);
|
|
|
|
let cloneScope = Cu.Sandbox(this.principal, {});
|
|
Cu.setSandboxMetadata(cloneScope, {addonId: targetExtension.id});
|
|
Object.defineProperty(
|
|
this, "cloneScope",
|
|
{value: cloneScope, enumerable: true, configurable: true, writable: true}
|
|
);
|
|
|
|
let sender = {id: targetExtension.id};
|
|
let filter = {extensionId: targetExtension.id};
|
|
// Legacy addons live in the main process. Messages from other addons are
|
|
// Messages from WebExtensions are sent to the main process and forwarded via
|
|
// the parent process manager to the legacy extension.
|
|
this.messenger = new ExtensionChild.Messenger(this, [Services.cpmm], sender, filter);
|
|
|
|
this.api = {
|
|
browser: {
|
|
runtime: {
|
|
onConnect: this.messenger.onConnect("runtime.onConnect"),
|
|
onMessage: this.messenger.onMessage("runtime.onMessage"),
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This method is called when the extension shuts down or is unloaded,
|
|
* and it nukes the cloneScope sandbox, if any.
|
|
*/
|
|
unload() {
|
|
if (this.unloaded) {
|
|
throw new Error("Error trying to unload LegacyExtensionContext twice.");
|
|
}
|
|
super.unload();
|
|
Cu.nukeSandbox(this.cloneScope);
|
|
this.cloneScope = null;
|
|
}
|
|
};
|
|
|
|
var EmbeddedExtensionManager;
|
|
|
|
/**
|
|
* Instances of this class are used internally by the exported EmbeddedWebExtensionsUtils
|
|
* to manage the embedded webextension instance and the related LegacyExtensionContext
|
|
* instance used to exchange messages with it.
|
|
*/
|
|
class EmbeddedExtension {
|
|
/**
|
|
* Create a new EmbeddedExtension given the add-on id and the base resource URI of the
|
|
* container add-on (the webextension resources will be loaded from the "webextension/"
|
|
* subdir of the base resource URI for the legacy extension add-on).
|
|
*
|
|
* @param {Object} containerAddonParams
|
|
* An object with the following properties:
|
|
* @param {string} containerAddonParams.id
|
|
* The Add-on id of the Legacy Extension which will contain the embedded webextension.
|
|
* @param {nsIURI} containerAddonParams.resourceURI
|
|
* The nsIURI of the Legacy Extension container add-on.
|
|
*/
|
|
constructor({id, resourceURI}) {
|
|
this.addonId = id;
|
|
this.resourceURI = resourceURI;
|
|
|
|
// Setup status flag.
|
|
this.started = false;
|
|
}
|
|
|
|
/**
|
|
* Start the embedded webextension.
|
|
*
|
|
* @returns {Promise<LegacyContextAPI>} A promise which resolve to the API exposed to the
|
|
* legacy context.
|
|
*/
|
|
startup() {
|
|
if (this.started) {
|
|
return Promise.reject(new Error("This embedded extension has already been started"));
|
|
}
|
|
|
|
// Setup the startup promise.
|
|
this.startupPromise = new Promise((resolve, reject) => {
|
|
let embeddedExtensionURI = Services.io.newURI("webextension/", null, this.resourceURI);
|
|
|
|
// This is the instance of the WebExtension embedded in the hybrid add-on.
|
|
this.extension = new Extension({
|
|
id: this.addonId,
|
|
resourceURI: embeddedExtensionURI,
|
|
});
|
|
|
|
// This callback is register to the "startup" event, emitted by the Extension instance
|
|
// after the extension manifest.json has been loaded without any errors, but before
|
|
// starting any of the defined contexts (which give the legacy part a chance to subscribe
|
|
// runtime.onMessage/onConnect listener before the background page has been loaded).
|
|
const onBeforeStarted = () => {
|
|
this.extension.off("startup", onBeforeStarted);
|
|
|
|
// Resolve the startup promise and reset the startupError.
|
|
this.started = true;
|
|
this.startupPromise = null;
|
|
|
|
// Create the legacy extension context, the legacy container addon
|
|
// needs to use it before the embedded webextension startup,
|
|
// because it is supposed to be used during the legacy container startup
|
|
// to subscribe its message listeners (which are supposed to be able to
|
|
// receive any message that the embedded part can try to send to it
|
|
// during its startup).
|
|
this.context = new LegacyExtensionContext(this.extension);
|
|
|
|
// Destroy the LegacyExtensionContext cloneScope when
|
|
// the embedded webextensions is unloaded.
|
|
this.extension.callOnClose({
|
|
close: () => {
|
|
this.context.unload();
|
|
},
|
|
});
|
|
|
|
// resolve startupPromise to execute any pending shutdown that has been
|
|
// chained to it.
|
|
resolve(this.context.api);
|
|
};
|
|
|
|
this.extension.on("startup", onBeforeStarted);
|
|
|
|
// Run ambedded extension startup and catch any error during embedded extension
|
|
// startup.
|
|
this.extension.startup().catch((err) => {
|
|
this.started = false;
|
|
this.startupPromise = null;
|
|
this.extension.off("startup", onBeforeStarted);
|
|
|
|
reject(err);
|
|
});
|
|
});
|
|
|
|
return this.startupPromise;
|
|
}
|
|
|
|
/**
|
|
* Shuts down the embedded webextension.
|
|
*
|
|
* @returns {Promise<void>} a promise that is resolved when the shutdown has been done
|
|
*/
|
|
shutdown() {
|
|
EmbeddedExtensionManager.untrackEmbeddedExtension(this);
|
|
|
|
// If there is a pending startup, wait to be completed and then shutdown.
|
|
if (this.startupPromise) {
|
|
return this.startupPromise.then(() => {
|
|
this.extension.shutdown();
|
|
});
|
|
}
|
|
|
|
// Run shutdown now if the embedded webextension has been correctly started
|
|
if (this.extension && this.started && !this.extension.hasShutdown) {
|
|
this.extension.shutdown();
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
|
|
// Keep track on the created EmbeddedExtension instances and destroy
|
|
// them when their container addon is going to be disabled or uninstalled.
|
|
EmbeddedExtensionManager = {
|
|
// Map of the existent EmbeddedExtensions instances by addon id.
|
|
embeddedExtensionsByAddonId: new Map(),
|
|
|
|
untrackEmbeddedExtension(embeddedExtensionInstance) {
|
|
// Remove this instance from the tracked embedded extensions
|
|
let id = embeddedExtensionInstance.addonId;
|
|
if (this.embeddedExtensionsByAddonId.get(id) == embeddedExtensionInstance) {
|
|
this.embeddedExtensionsByAddonId.delete(id);
|
|
}
|
|
},
|
|
|
|
getEmbeddedExtensionFor({id, resourceURI}) {
|
|
let embeddedExtension = this.embeddedExtensionsByAddonId.get(id);
|
|
|
|
if (!embeddedExtension) {
|
|
embeddedExtension = new EmbeddedExtension({id, resourceURI});
|
|
// Keep track of the embedded extension instance.
|
|
this.embeddedExtensionsByAddonId.set(id, embeddedExtension);
|
|
}
|
|
|
|
return embeddedExtension;
|
|
},
|
|
};
|
|
|
|
this.LegacyExtensionsUtils = {
|
|
getEmbeddedExtensionFor: (addon) => {
|
|
return EmbeddedExtensionManager.getEmbeddedExtensionFor(addon);
|
|
},
|
|
};
|