summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/parent/ext-backgroundPage.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/parent/ext-backgroundPage.js')
-rw-r--r--toolkit/components/extensions/parent/ext-backgroundPage.js231
1 files changed, 231 insertions, 0 deletions
diff --git a/toolkit/components/extensions/parent/ext-backgroundPage.js b/toolkit/components/extensions/parent/ext-backgroundPage.js
new file mode 100644
index 0000000000..9de2ad669c
--- /dev/null
+++ b/toolkit/components/extensions/parent/ext-backgroundPage.js
@@ -0,0 +1,231 @@
+/* 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";
+
+var { ExtensionParent } = ChromeUtils.import(
+ "resource://gre/modules/ExtensionParent.jsm"
+);
+var { HiddenExtensionPage, promiseExtensionViewLoaded } = ExtensionParent;
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "ExtensionTelemetry",
+ "resource://gre/modules/ExtensionTelemetry.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm"
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "DELAYED_STARTUP",
+ "extensions.webextensions.background-delayed-startup"
+);
+
+XPCOMUtils.defineLazyGetter(this, "serviceWorkerManager", () => {
+ return Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+});
+
+// Responsible for the background_page section of the manifest.
+class BackgroundPage extends HiddenExtensionPage {
+ constructor(extension, options) {
+ super(extension, "background");
+
+ this.page = options.page || null;
+ this.isGenerated = !!options.scripts;
+
+ if (this.page) {
+ this.url = this.extension.baseURI.resolve(this.page);
+ } else if (this.isGenerated) {
+ this.url = this.extension.baseURI.resolve(
+ "_generated_background_page.html"
+ );
+ }
+ }
+
+ async build() {
+ const { extension } = this;
+
+ ExtensionTelemetry.backgroundPageLoad.stopwatchStart(extension, this);
+
+ let context;
+ try {
+ await this.createBrowserElement();
+ if (!this.browser) {
+ throw new Error(
+ "Extension shut down before the background page was created"
+ );
+ }
+ extension._backgroundPageFrameLoader = this.browser.frameLoader;
+
+ extensions.emit("extension-browser-inserted", this.browser);
+
+ let contextPromise = promiseExtensionViewLoaded(this.browser);
+ this.browser.loadURI(this.url, {
+ triggeringPrincipal: extension.principal,
+ });
+
+ context = await contextPromise;
+ } catch (e) {
+ // Extension was down before the background page has loaded.
+ Cu.reportError(e);
+ ExtensionTelemetry.backgroundPageLoad.stopwatchCancel(extension, this);
+ if (extension.persistentListeners) {
+ EventManager.clearPrimedListeners(this.extension, false);
+ }
+ extension.emit("background-page-aborted");
+ return;
+ }
+
+ ExtensionTelemetry.backgroundPageLoad.stopwatchFinish(extension, this);
+
+ if (context) {
+ // Wait until all event listeners registered by the script so far
+ // to be handled.
+ await Promise.all(context.listenerPromises);
+ context.listenerPromises = null;
+ }
+
+ if (extension.persistentListeners) {
+ // |this.extension| may be null if the extension was shut down.
+ // In that case, we still want to clear the primed listeners,
+ // but not update the persistent listeners in the startupData.
+ EventManager.clearPrimedListeners(extension, !!this.extension);
+ }
+
+ extension.emit("background-page-started");
+ }
+
+ shutdown() {
+ this.extension._backgroundPageFrameLoader = null;
+ super.shutdown();
+ }
+}
+
+// Responsible for the background.service_worker section of the manifest.
+class BackgroundWorker {
+ constructor(extension, options) {
+ this.registrationInfo = null;
+ this.extension = extension;
+ this.workerScript = options.service_worker;
+
+ if (!this.workerScript) {
+ throw new Error("Missing mandatory background.service_worker property");
+ }
+ }
+
+ async build() {
+ const regInfo = await serviceWorkerManager.registerForAddonPrincipal(
+ this.extension.principal
+ );
+ this.registrationInfo = regInfo.QueryInterface(
+ Ci.nsIServiceWorkerRegistrationInfo
+ );
+ }
+
+ shutdown() {
+ if (this.registrationInfo) {
+ this.registrationInfo.forceShutdown();
+ this.registrationInfo = null;
+ }
+ }
+}
+
+this.backgroundPage = class extends ExtensionAPI {
+ async build() {
+ if (this.bgInstance) {
+ return;
+ }
+
+ let { extension } = this;
+ let { manifest } = extension;
+
+ let BackgroundClass = manifest.background.service_worker
+ ? BackgroundWorker
+ : BackgroundPage;
+
+ this.bgInstance = new BackgroundClass(extension, manifest.background);
+ return this.bgInstance.build();
+ }
+
+ onManifestEntry(entryName) {
+ let { extension } = this;
+
+ this.bgInstance = null;
+
+ // When in PPB background pages all run in a private context. This check
+ // simply avoids an extraneous error in the console since the BaseContext
+ // will throw.
+ if (
+ PrivateBrowsingUtils.permanentPrivateBrowsing &&
+ !extension.privateBrowsingAllowed
+ ) {
+ return;
+ }
+
+ // Used by runtime messaging to wait for background page listeners.
+ let bgStartupPromise = new Promise(resolve => {
+ let done = () => {
+ extension.off("background-page-started", done);
+ extension.off("background-page-aborted", done);
+ extension.off("shutdown", done);
+ resolve();
+ };
+ extension.on("background-page-started", done);
+ extension.on("background-page-aborted", done);
+ extension.on("shutdown", done);
+ });
+
+ extension.wakeupBackground = () => {
+ extension.emit("background-page-event");
+ extension.wakeupBackground = () => bgStartupPromise;
+ return bgStartupPromise;
+ };
+
+ if (extension.startupReason !== "APP_STARTUP" || !DELAYED_STARTUP) {
+ return this.build();
+ }
+
+ EventManager.primeListeners(extension);
+
+ extension.once("start-background-page", async () => {
+ if (!this.extension) {
+ // Extension was shut down. Don't build the background page.
+ // Primed listeners have been cleared in onShutdown.
+ return;
+ }
+ await this.build();
+ });
+
+ // There are two ways to start the background page:
+ // 1. If a primed event fires, then start the background page as
+ // soon as we have painted a browser window. Note that we have
+ // to touch browserPaintedPromise here to initialize the listener
+ // or else we can miss it if the event occurs after the first
+ // window is painted but before #2
+ // 2. After all windows have been restored.
+ extension.once("background-page-event", async () => {
+ await ExtensionParent.browserPaintedPromise;
+ extension.emit("start-background-page");
+ });
+
+ ExtensionParent.browserStartupPromise.then(() => {
+ extension.emit("start-background-page");
+ });
+ }
+
+ onShutdown() {
+ if (this.bgInstance) {
+ this.bgInstance.shutdown();
+ this.bgInstance = null;
+ } else {
+ EventManager.clearPrimedListeners(this.extension, false);
+ }
+ }
+};