diff options
Diffstat (limited to 'toolkit/components/webextensions/ext-backgroundPage.js')
-rw-r--r-- | toolkit/components/webextensions/ext-backgroundPage.js | 147 |
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 */ |