summaryrefslogtreecommitdiffstats
path: root/toolkit/components/webextensions/ext-backgroundPage.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/webextensions/ext-backgroundPage.js')
-rw-r--r--toolkit/components/webextensions/ext-backgroundPage.js147
1 files changed, 147 insertions, 0 deletions
diff --git a/toolkit/components/webextensions/ext-backgroundPage.js b/toolkit/components/webextensions/ext-backgroundPage.js
new file mode 100644
index 000000000..fce6100ca
--- /dev/null
+++ b/toolkit/components/webextensions/ext-backgroundPage.js
@@ -0,0 +1,147 @@
+"use strict";
+
+var {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+const {
+ promiseDocumentLoaded,
+ promiseObserved,
+} = ExtensionUtils;
+
+const XUL_URL = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + encodeURI(
+ `<?xml version="1.0"?>
+ <window id="documentElement"/>`);
+
+// WeakMap[Extension -> BackgroundPage]
+var backgroundPagesMap = new WeakMap();
+
+// Responsible for the background_page section of the manifest.
+function BackgroundPage(options, extension) {
+ this.extension = extension;
+ this.page = options.page || null;
+ this.isGenerated = !!options.scripts;
+ this.windowlessBrowser = null;
+ this.webNav = null;
+}
+
+BackgroundPage.prototype = {
+ build: Task.async(function* () {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
+ this.windowlessBrowser = windowlessBrowser;
+
+ let url;
+ if (this.page) {
+ url = this.extension.baseURI.resolve(this.page);
+ } else if (this.isGenerated) {
+ url = this.extension.baseURI.resolve("_generated_background_page.html");
+ }
+
+ if (!this.extension.isExtensionURL(url)) {
+ this.extension.manifestError("Background page must be a file within the extension");
+ url = this.extension.baseURI.resolve("_blank.html");
+ }
+
+ let system = Services.scriptSecurityManager.getSystemPrincipal();
+
+ // The windowless browser is a thin wrapper around a docShell that keeps
+ // its related resources alive. It implements nsIWebNavigation and
+ // forwards its methods to the underlying docShell, but cannot act as a
+ // docShell itself. Calling `getInterface(nsIDocShell)` gives us the
+ // underlying docShell, and `QueryInterface(nsIWebNavigation)` gives us
+ // access to the webNav methods that are already available on the
+ // windowless browser, but contrary to appearances, they are not the same
+ // object.
+ let chromeShell = windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIWebNavigation);
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ let attrs = chromeShell.getOriginAttributes();
+ attrs.privateBrowsingId = 1;
+ chromeShell.setOriginAttributes(attrs);
+ }
+
+ chromeShell.useGlobalHistory = false;
+ chromeShell.createAboutBlankContentViewer(system);
+ chromeShell.loadURI(XUL_URL, 0, null, null, null);
+
+
+ yield promiseObserved("chrome-document-global-created",
+ win => win.document == chromeShell.document);
+
+ let chromeDoc = yield promiseDocumentLoaded(chromeShell.document);
+
+ let browser = chromeDoc.createElement("browser");
+ browser.setAttribute("type", "content");
+ browser.setAttribute("disableglobalhistory", "true");
+ chromeDoc.documentElement.appendChild(browser);
+
+ extensions.emit("extension-browser-inserted", browser);
+ browser.messageManager.sendAsyncMessage("Extension:InitExtensionView", {
+ viewType: "background",
+ url,
+ });
+
+ yield new Promise(resolve => {
+ browser.messageManager.addMessageListener("Extension:ExtensionViewLoaded", function onLoad() {
+ browser.messageManager.removeMessageListener("Extension:ExtensionViewLoaded", onLoad);
+ resolve();
+ });
+ });
+
+ // TODO(robwu): This is not webext-oop compatible.
+ this.webNav = browser.docShell.QueryInterface(Ci.nsIWebNavigation);
+ let window = this.webNav.document.defaultView;
+
+
+ // Set the add-on's main debugger global, for use in the debugger
+ // console.
+ if (this.extension.addonData.instanceID) {
+ AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
+ .then(addon => addon.setDebugGlobal(window));
+ }
+
+ this.extension.emit("startup");
+ }),
+
+ shutdown() {
+ if (this.extension.addonData.instanceID) {
+ AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID)
+ .then(addon => addon.setDebugGlobal(null));
+ }
+
+ // Navigate away from the background page to invalidate any
+ // setTimeouts or other callbacks.
+ if (this.webNav) {
+ this.webNav.loadURI("about:blank", 0, null, null, null);
+ this.webNav = null;
+ }
+
+ this.windowlessBrowser.loadURI("about:blank", 0, null, null, null);
+ this.windowlessBrowser.close();
+ this.windowlessBrowser = null;
+ },
+};
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_background", (type, directive, extension, manifest) => {
+ let bgPage = new BackgroundPage(manifest.background, extension);
+ backgroundPagesMap.set(extension, bgPage);
+ return bgPage.build();
+});
+
+extensions.on("shutdown", (type, extension) => {
+ if (backgroundPagesMap.has(extension)) {
+ backgroundPagesMap.get(extension).shutdown();
+ backgroundPagesMap.delete(extension);
+ }
+});
+/* eslint-enable mozilla/balanced-listeners */