diff options
author | wolfbeast <mcwerewolf@wolfbeast.com> | 2019-04-05 20:01:10 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@wolfbeast.com> | 2019-04-05 20:01:10 +0200 |
commit | c3b63b831cd2c64700e875b28540212c7c881ac6 (patch) | |
tree | edd98fcbd2004d3b562904f822bf6c3322fc7f52 /toolkit/components/webextensions/Extension.jsm | |
parent | d432e068a21c815d5d5e7bcbc1cc8c6e77a7d1e0 (diff) | |
parent | cc07da9cb4d6e7a53f8d953427ffc2bca2e0c2df (diff) | |
download | UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar.gz UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar.lz UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.tar.xz UXP-c3b63b831cd2c64700e875b28540212c7c881ac6.zip |
Merge branch 'master' into 816
Diffstat (limited to 'toolkit/components/webextensions/Extension.jsm')
-rw-r--r-- | toolkit/components/webextensions/Extension.jsm | 902 |
1 files changed, 0 insertions, 902 deletions
diff --git a/toolkit/components/webextensions/Extension.jsm b/toolkit/components/webextensions/Extension.jsm deleted file mode 100644 index 3468f2594..000000000 --- a/toolkit/components/webextensions/Extension.jsm +++ /dev/null @@ -1,902 +0,0 @@ -/* 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 = ["Extension", "ExtensionData"]; - -/* globals Extension ExtensionData */ - -/* - * This file is the main entry point for extensions. When an extension - * loads, its bootstrap.js file creates a Extension instance - * and calls .startup() on it. It calls .shutdown() when the extension - * unloads. Extension manages any extension-specific state in - * the chrome process. - */ - -const Ci = Components.interfaces; -const Cc = Components.classes; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.importGlobalProperties(["TextEncoder"]); - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", - "resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", - "resource://gre/modules/AppConstants.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs", - "resource://gre/modules/ExtensionAPI.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage", - "resource://gre/modules/ExtensionStorage.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestCommon", - "resource://testing-common/ExtensionTestCommon.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Locale", - "resource://gre/modules/Locale.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Log", - "resource://gre/modules/Log.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MatchGlobs", - "resource://gre/modules/MatchPattern.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", - "resource://gre/modules/MatchPattern.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", - "resource://gre/modules/MessageChannel.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Preferences", - "resource://gre/modules/Preferences.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "require", - "resource://devtools/shared/Loader.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Schemas", - "resource://gre/modules/Schemas.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -Cu.import("resource://gre/modules/ExtensionContent.jsm"); -Cu.import("resource://gre/modules/ExtensionManagement.jsm"); -Cu.import("resource://gre/modules/ExtensionParent.jsm"); -Cu.import("resource://gre/modules/ExtensionUtils.jsm"); - -XPCOMUtils.defineLazyServiceGetter(this, "uuidGen", - "@mozilla.org/uuid-generator;1", - "nsIUUIDGenerator"); - -var { - GlobalManager, - ParentAPIManager, - apiManager: Management, -} = ExtensionParent; - -const { - EventEmitter, - LocaleData, - getUniqueId, -} = ExtensionUtils; - -XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole); - -const LOGGER_ID_BASE = "addons.webextension."; -const UUID_MAP_PREF = "extensions.webextensions.uuids"; -const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall"; -const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall"; - -const COMMENT_REGEXP = new RegExp(String.raw` - ^ - ( - (?: - [^"\n] | - " (?:[^"\\\n] | \\.)* " - )*? - ) - - //.* - `.replace(/\s+/g, ""), "gm"); - -// All moz-extension URIs use a machine-specific UUID rather than the -// extension's own ID in the host component. This makes it more -// difficult for web pages to detect whether a user has a given add-on -// installed (by trying to load a moz-extension URI referring to a -// web_accessible_resource from the extension). UUIDMap.get() -// returns the UUID for a given add-on ID. -var UUIDMap = { - _read() { - let pref = Preferences.get(UUID_MAP_PREF, "{}"); - try { - return JSON.parse(pref); - } catch (e) { - Cu.reportError(`Error parsing ${UUID_MAP_PREF}.`); - return {}; - } - }, - - _write(map) { - Preferences.set(UUID_MAP_PREF, JSON.stringify(map)); - }, - - get(id, create = true) { - let map = this._read(); - - if (id in map) { - return map[id]; - } - - let uuid = null; - if (create) { - uuid = uuidGen.generateUUID().number; - uuid = uuid.slice(1, -1); // Strip { and } off the UUID. - - map[id] = uuid; - this._write(map); - } - return uuid; - }, - - remove(id) { - let map = this._read(); - delete map[id]; - this._write(map); - }, -}; - -// This is the old interface that UUIDMap replaced, to be removed when -// the references listed in bug 1291399 are updated. -/* exported getExtensionUUID */ -function getExtensionUUID(id) { - return UUIDMap.get(id, true); -} - -// For extensions that have called setUninstallURL(), send an event -// so the browser can display the URL. -var UninstallObserver = { - initialized: false, - - init() { - if (!this.initialized) { - AddonManager.addAddonListener(this); - XPCOMUtils.defineLazyPreferenceGetter(this, "leaveStorage", LEAVE_STORAGE_PREF, false); - XPCOMUtils.defineLazyPreferenceGetter(this, "leaveUuid", LEAVE_UUID_PREF, false); - this.initialized = true; - } - }, - - onUninstalling(addon) { - let extension = GlobalManager.extensionMap.get(addon.id); - if (extension) { - // Let any other interested listeners respond - // (e.g., display the uninstall URL) - Management.emit("uninstall", extension); - } - }, - - onUninstalled(addon) { - let uuid = UUIDMap.get(addon.id, false); - if (!uuid) { - return; - } - - if (!this.leaveStorage) { - // Clear browser.local.storage - ExtensionStorage.clear(addon.id); - - // Clear any IndexedDB storage created by the extension - let baseURI = NetUtil.newURI(`moz-extension://${uuid}/`); - let principal = Services.scriptSecurityManager.createCodebasePrincipal( - baseURI, {addonId: addon.id} - ); - Services.qms.clearStoragesForPrincipal(principal); - - // Clear localStorage created by the extension - let attrs = JSON.stringify({addonId: addon.id}); - Services.obs.notifyObservers(null, "clear-origin-attributes-data", attrs); - } - - if (!this.leaveUuid) { - // Clear the entry in the UUID map - UUIDMap.remove(addon.id); - } - }, -}; - -UninstallObserver.init(); - -// Represents the data contained in an extension, contained either -// in a directory or a zip file, which may or may not be installed. -// This class implements the functionality of the Extension class, -// primarily related to manifest parsing and localization, which is -// useful prior to extension installation or initialization. -// -// No functionality of this class is guaranteed to work before -// |readManifest| has been called, and completed. -this.ExtensionData = class { - constructor(rootURI) { - this.rootURI = rootURI; - - this.manifest = null; - this.id = null; - this.uuid = null; - this.localeData = null; - this._promiseLocales = null; - - this.apiNames = new Set(); - this.dependencies = new Set(); - this.permissions = new Set(); - - this.errors = []; - } - - get builtinMessages() { - return null; - } - - get logger() { - let id = this.id || "<unknown>"; - return Log.repository.getLogger(LOGGER_ID_BASE + id); - } - - // Report an error about the extension's manifest file. - manifestError(message) { - this.packagingError(`Reading manifest: ${message}`); - } - - // Report an error about the extension's general packaging. - packagingError(message) { - this.errors.push(message); - this.logger.error(`Loading extension '${this.id}': ${message}`); - } - - /** - * Returns the moz-extension: URL for the given path within this - * extension. - * - * Must not be called unless either the `id` or `uuid` property has - * already been set. - * - * @param {string} path The path portion of the URL. - * @returns {string} - */ - getURL(path = "") { - if (!(this.id || this.uuid)) { - throw new Error("getURL may not be called before an `id` or `uuid` has been set"); - } - if (!this.uuid) { - this.uuid = UUIDMap.get(this.id); - } - return `moz-extension://${this.uuid}/${path}`; - } - - readDirectory(path) { - return Task.spawn(function* () { - if (this.rootURI instanceof Ci.nsIFileURL) { - let uri = NetUtil.newURI(this.rootURI.resolve("./" + path)); - let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path; - - let iter = new OS.File.DirectoryIterator(fullPath); - let results = []; - - try { - yield iter.forEach(entry => { - results.push(entry); - }); - } catch (e) { - // Always return a list, even if the directory does not exist (or is - // not a directory) for symmetry with the ZipReader behavior. - } - iter.close(); - - return results; - } - - // FIXME: We need a way to do this without main thread IO. - - let uri = this.rootURI.QueryInterface(Ci.nsIJARURI); - - let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file; - let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader); - zipReader.open(file); - try { - let results = []; - - // Normalize the directory path. - path = `${uri.JAREntry}/${path}`; - path = path.replace(/\/\/+/g, "/").replace(/^\/|\/$/g, "") + "/"; - - // Escape pattern metacharacters. - let pattern = path.replace(/[[\]()?*~|$\\]/g, "\\$&"); - - let enumerator = zipReader.findEntries(pattern + "*"); - while (enumerator.hasMore()) { - let name = enumerator.getNext(); - if (!name.startsWith(path)) { - throw new Error("Unexpected ZipReader entry"); - } - - // The enumerator returns the full path of all entries. - // Trim off the leading path, and filter out entries from - // subdirectories. - name = name.slice(path.length); - if (name && !/\/./.test(name)) { - results.push({ - name: name.replace("/", ""), - isDir: name.endsWith("/"), - }); - } - } - - return results; - } finally { - zipReader.close(); - } - }.bind(this)); - } - - readJSON(path) { - return new Promise((resolve, reject) => { - let uri = this.rootURI.resolve(`./${path}`); - - NetUtil.asyncFetch({uri, loadUsingSystemPrincipal: true}, (inputStream, status) => { - if (!Components.isSuccessCode(status)) { - // Convert status code to a string - let e = Components.Exception("", status); - reject(new Error(`Error while loading '${uri}' (${e.name})`)); - return; - } - try { - let text = NetUtil.readInputStreamToString(inputStream, inputStream.available(), - {charset: "utf-8"}); - - text = text.replace(COMMENT_REGEXP, "$1"); - - resolve(JSON.parse(text)); - } catch (e) { - reject(e); - } - }); - }); - } - - // Reads the extension's |manifest.json| file, and stores its - // parsed contents in |this.manifest|. - readManifest() { - return Promise.all([ - this.readJSON("manifest.json"), - Management.lazyInit(), - ]).then(([manifest]) => { - this.manifest = manifest; - this.rawManifest = manifest; - - if (manifest && manifest.default_locale) { - return this.initLocale(); - } - }).then(() => { - let context = { - url: this.baseURI && this.baseURI.spec, - - principal: this.principal, - - logError: error => { - this.logger.warn(`Loading extension '${this.id}': Reading manifest: ${error}`); - }, - - preprocessors: {}, - }; - - if (this.localeData) { - context.preprocessors.localize = (value, context) => this.localize(value); - } - - let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context); - if (normalized.error) { - this.manifestError(normalized.error); - } else { - this.manifest = normalized.value; - } - - try { - // Do not override the add-on id that has been already assigned. - if (!this.id && this.manifest.applications.gecko.id) { - this.id = this.manifest.applications.gecko.id; - } - } catch (e) { - // Errors are handled by the type checks above. - } - - let permissions = this.manifest.permissions || []; - - let whitelist = []; - for (let perm of permissions) { - this.permissions.add(perm); - - let match = /^(\w+)(?:\.(\w+)(?:\.\w+)*)?$/.exec(perm); - if (!match) { - whitelist.push(perm); - } else if (match[1] == "experiments" && match[2]) { - this.apiNames.add(match[2]); - } - } - this.whiteListedHosts = new MatchPattern(whitelist); - - for (let api of this.apiNames) { - this.dependencies.add(`${api}@experiments.addons.mozilla.org`); - } - - return this.manifest; - }); - } - - localizeMessage(...args) { - return this.localeData.localizeMessage(...args); - } - - localize(...args) { - return this.localeData.localize(...args); - } - - // If a "default_locale" is specified in that manifest, returns it - // as a Gecko-compatible locale string. Otherwise, returns null. - get defaultLocale() { - if (this.manifest.default_locale != null) { - return this.normalizeLocaleCode(this.manifest.default_locale); - } - - return null; - } - - // Normalizes a Chrome-compatible locale code to the appropriate - // Gecko-compatible variant. Currently, this means simply - // replacing underscores with hyphens. - normalizeLocaleCode(locale) { - return String.replace(locale, /_/g, "-"); - } - - // Reads the locale file for the given Gecko-compatible locale code, and - // stores its parsed contents in |this.localeMessages.get(locale)|. - readLocaleFile(locale) { - return Task.spawn(function* () { - let locales = yield this.promiseLocales(); - let dir = locales.get(locale) || locale; - let file = `_locales/${dir}/messages.json`; - - try { - let messages = yield this.readJSON(file); - return this.localeData.addLocale(locale, messages, this); - } catch (e) { - this.packagingError(`Loading locale file ${file}: ${e}`); - return new Map(); - } - }.bind(this)); - } - - // Reads the list of locales available in the extension, and returns a - // Promise which resolves to a Map upon completion. - // Each map key is a Gecko-compatible locale code, and each value is the - // "_locales" subdirectory containing that locale: - // - // Map(gecko-locale-code -> locale-directory-name) - promiseLocales() { - if (!this._promiseLocales) { - this._promiseLocales = Task.spawn(function* () { - let locales = new Map(); - - let entries = yield this.readDirectory("_locales"); - for (let file of entries) { - if (file.isDir) { - let locale = this.normalizeLocaleCode(file.name); - locales.set(locale, file.name); - } - } - - this.localeData = new LocaleData({ - defaultLocale: this.defaultLocale, - locales, - builtinMessages: this.builtinMessages, - }); - - return locales; - }.bind(this)); - } - - return this._promiseLocales; - } - - // Reads the locale messages for all locales, and returns a promise which - // resolves to a Map of locale messages upon completion. Each key in the map - // is a Gecko-compatible locale code, and each value is a locale data object - // as returned by |readLocaleFile|. - initAllLocales() { - return Task.spawn(function* () { - let locales = yield this.promiseLocales(); - - yield Promise.all(Array.from(locales.keys(), - locale => this.readLocaleFile(locale))); - - let defaultLocale = this.defaultLocale; - if (defaultLocale) { - if (!locales.has(defaultLocale)) { - this.manifestError('Value for "default_locale" property must correspond to ' + - 'a directory in "_locales/". Not found: ' + - JSON.stringify(`_locales/${this.manifest.default_locale}/`)); - } - } else if (locales.size) { - this.manifestError('The "default_locale" property is required when a ' + - '"_locales/" directory is present.'); - } - - return this.localeData.messages; - }.bind(this)); - } - - // Reads the locale file for the given Gecko-compatible locale code, or the - // default locale if no locale code is given, and sets it as the currently - // selected locale on success. - // - // Pre-loads the default locale for fallback message processing, regardless - // of the locale specified. - // - // If no locales are unavailable, resolves to |null|. - initLocale(locale = this.defaultLocale) { - return Task.spawn(function* () { - if (locale == null) { - return null; - } - - let promises = [this.readLocaleFile(locale)]; - - let {defaultLocale} = this; - if (locale != defaultLocale && !this.localeData.has(defaultLocale)) { - promises.push(this.readLocaleFile(defaultLocale)); - } - - let results = yield Promise.all(promises); - - this.localeData.selectedLocale = locale; - return results[0]; - }.bind(this)); - } -}; - -let _browserUpdated = false; - -const PROXIED_EVENTS = new Set(["test-harness-message"]); - -// We create one instance of this class per extension. |addonData| -// comes directly from bootstrap.js when initializing. -this.Extension = class extends ExtensionData { - constructor(addonData, startupReason) { - super(addonData.resourceURI); - - this.uuid = UUIDMap.get(addonData.id); - this.instanceId = getUniqueId(); - - this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`; - Services.ppmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this); - - if (addonData.cleanupFile) { - Services.obs.addObserver(this, "xpcom-shutdown", false); - this.cleanupFile = addonData.cleanupFile || null; - delete addonData.cleanupFile; - } - - this.addonData = addonData; - this.startupReason = startupReason; - - this.id = addonData.id; - this.baseURI = NetUtil.newURI(this.getURL("")).QueryInterface(Ci.nsIURL); - this.principal = this.createPrincipal(); - - this.onStartup = null; - - this.hasShutdown = false; - this.onShutdown = new Set(); - - this.uninstallURL = null; - - this.apis = []; - this.whiteListedHosts = null; - this.webAccessibleResources = null; - - this.emitter = new EventEmitter(); - } - - static set browserUpdated(updated) { - _browserUpdated = updated; - } - - static get browserUpdated() { - return _browserUpdated; - } - - static generateXPI(data) { - return ExtensionTestCommon.generateXPI(data); - } - - static generateZipFile(files, baseName = "generated-extension.xpi") { - return ExtensionTestCommon.generateZipFile(files, baseName); - } - - static generate(data) { - return ExtensionTestCommon.generate(data); - } - - on(hook, f) { - return this.emitter.on(hook, f); - } - - off(hook, f) { - return this.emitter.off(hook, f); - } - - emit(event, ...args) { - if (PROXIED_EVENTS.has(event)) { - Services.ppmm.broadcastAsyncMessage(this.MESSAGE_EMIT_EVENT, {event, args}); - } - - return this.emitter.emit(event, ...args); - } - - receiveMessage({name, data}) { - if (name === this.MESSAGE_EMIT_EVENT) { - this.emitter.emit(data.event, ...data.args); - } - } - - testMessage(...args) { - this.emit("test-harness-message", ...args); - } - - createPrincipal(uri = this.baseURI) { - return Services.scriptSecurityManager.createCodebasePrincipal( - uri, {addonId: this.id}); - } - - // Checks that the given URL is a child of our baseURI. - isExtensionURL(url) { - let uri = Services.io.newURI(url, null, null); - - let common = this.baseURI.getCommonBaseSpec(uri); - return common == this.baseURI.spec; - } - - readManifest() { - return super.readManifest().then(manifest => { - if (AppConstants.RELEASE_OR_BETA) { - return manifest; - } - - // Load Experiments APIs that this extension depends on. - return Promise.all( - Array.from(this.apiNames, api => ExtensionAPIs.load(api)) - ).then(apis => { - for (let API of apis) { - this.apis.push(new API(this)); - } - - return manifest; - }); - }); - } - - // Representation of the extension to send to content - // processes. This should include anything the content process might - // need. - serialize() { - return { - id: this.id, - uuid: this.uuid, - instanceId: this.instanceId, - manifest: this.manifest, - resourceURL: this.addonData.resourceURI.spec, - baseURL: this.baseURI.spec, - content_scripts: this.manifest.content_scripts || [], // eslint-disable-line camelcase - webAccessibleResources: this.webAccessibleResources.serialize(), - whiteListedHosts: this.whiteListedHosts.serialize(), - localeData: this.localeData.serialize(), - permissions: this.permissions, - principal: this.principal, - }; - } - - broadcast(msg, data) { - return new Promise(resolve => { - let count = Services.ppmm.childCount; - Services.ppmm.addMessageListener(msg + "Complete", function listener() { - count--; - if (count == 0) { - Services.ppmm.removeMessageListener(msg + "Complete", listener); - resolve(); - } - }); - Services.ppmm.broadcastAsyncMessage(msg, data); - }); - } - - runManifest(manifest) { - // Strip leading slashes from web_accessible_resources. - let strippedWebAccessibleResources = []; - if (manifest.web_accessible_resources) { - strippedWebAccessibleResources = manifest.web_accessible_resources.map(path => path.replace(/^\/+/, "")); - } - - this.webAccessibleResources = new MatchGlobs(strippedWebAccessibleResources); - - let promises = []; - for (let directive in manifest) { - if (manifest[directive] !== null) { - promises.push(Management.emit(`manifest_${directive}`, directive, this, manifest)); - } - } - - let data = Services.ppmm.initialProcessData; - if (!data["Extension:Extensions"]) { - data["Extension:Extensions"] = []; - } - let serial = this.serialize(); - data["Extension:Extensions"].push(serial); - - return this.broadcast("Extension:Startup", serial).then(() => { - return Promise.all(promises); - }); - } - - callOnClose(obj) { - this.onShutdown.add(obj); - } - - forgetOnClose(obj) { - this.onShutdown.delete(obj); - } - - get builtinMessages() { - return new Map([ - ["@@extension_id", this.uuid], - ]); - } - - // Reads the locale file for the given Gecko-compatible locale code, or if - // no locale is given, the available locale closest to the UI locale. - // Sets the currently selected locale on success. - initLocale(locale = undefined) { - // Ugh. - let super_ = super.initLocale.bind(this); - - return Task.spawn(function* () { - if (locale === undefined) { - let locales = yield this.promiseLocales(); - - let localeList = Array.from(locales.keys(), locale => { - return {name: locale, locales: [locale]}; - }); - - let match = Locale.findClosestLocale(localeList); - locale = match ? match.name : this.defaultLocale; - } - - return super_(locale); - }.bind(this)); - } - - startup() { - let started = false; - return this.readManifest().then(() => { - ExtensionManagement.startupExtension(this.uuid, this.addonData.resourceURI, this); - started = true; - - if (!this.hasShutdown) { - return this.initLocale(); - } - }).then(() => { - if (this.errors.length) { - return Promise.reject({errors: this.errors}); - } - - if (this.hasShutdown) { - return; - } - - GlobalManager.init(this); - - // The "startup" Management event sent on the extension instance itself - // is emitted just before the Management "startup" event, - // and it is used to run code that needs to be executed before - // any of the "startup" listeners. - this.emit("startup", this); - Management.emit("startup", this); - - return this.runManifest(this.manifest); - }).then(() => { - Management.emit("ready", this); - }).catch(e => { - dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`); - Cu.reportError(e); - - if (started) { - ExtensionManagement.shutdownExtension(this.uuid); - } - - this.cleanupGeneratedFile(); - - throw e; - }); - } - - cleanupGeneratedFile() { - if (!this.cleanupFile) { - return; - } - - let file = this.cleanupFile; - this.cleanupFile = null; - - Services.obs.removeObserver(this, "xpcom-shutdown"); - - this.broadcast("Extension:FlushJarCache", {path: file.path}).then(() => { - // We can't delete this file until everyone using it has - // closed it (because Windows is dumb). So we wait for all the - // child processes (including the parent) to flush their JAR - // caches. These caches may keep the file open. - file.remove(false); - }); - } - - shutdown() { - this.hasShutdown = true; - - Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this); - - if (!this.manifest) { - ExtensionManagement.shutdownExtension(this.uuid); - - this.cleanupGeneratedFile(); - return; - } - - GlobalManager.uninit(this); - - for (let obj of this.onShutdown) { - obj.close(); - } - - for (let api of this.apis) { - api.destroy(); - } - - ParentAPIManager.shutdownExtension(this.id); - - Management.emit("shutdown", this); - - Services.ppmm.broadcastAsyncMessage("Extension:Shutdown", {id: this.id}); - - MessageChannel.abortResponses({extensionId: this.id}); - - ExtensionManagement.shutdownExtension(this.uuid); - - this.cleanupGeneratedFile(); - } - - observe(subject, topic, data) { - if (topic == "xpcom-shutdown") { - this.cleanupGeneratedFile(); - } - } - - hasPermission(perm) { - let match = /^manifest:(.*)/.exec(perm); - if (match) { - return this.manifest[match[1]] != null; - } - - return this.permissions.has(perm); - } - - get name() { - return this.manifest.name; - } -}; |