"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( ` `); // 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 */