--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -441,100 +441,98 @@ class ViewPopup extends BasePopup {
*
* @param {Element} viewNode
* The node to attach the browser to.
* @returns {Promise<boolean>}
* Resolves when the browser is ready. Resolves to `false` if the
* browser was destroyed before it was fully loaded, and the popup
* should be closed, or `true` otherwise.
*/
- attach(viewNode) {
- return (async () => {
- this.viewNode = viewNode;
- this.viewNode.addEventListener(this.DESTROY_EVENT, this);
+ async attach(viewNode) {
+ this.viewNode = viewNode;
+ this.viewNode.addEventListener(this.DESTROY_EVENT, this);
- // Wait until the browser element is fully initialized, and give it at least
- // a short grace period to finish loading its initial content, if necessary.
- //
- // In practice, the browser that was created by the mousdown handler should
- // nearly always be ready by this point.
- await Promise.all([
- this.browserReady,
- Promise.race([
- // This promise may be rejected if the popup calls window.close()
- // before it has fully loaded.
- this.browserLoaded.catch(() => {}),
- new Promise(resolve => setTimeout(resolve, POPUP_LOAD_TIMEOUT_MS)),
- ]),
- ]);
+ // Wait until the browser element is fully initialized, and give it at least
+ // a short grace period to finish loading its initial content, if necessary.
+ //
+ // In practice, the browser that was created by the mousdown handler should
+ // nearly always be ready by this point.
+ await Promise.all([
+ this.browserReady,
+ Promise.race([
+ // This promise may be rejected if the popup calls window.close()
+ // before it has fully loaded.
+ this.browserLoaded.catch(() => {}),
+ new Promise(resolve => setTimeout(resolve, POPUP_LOAD_TIMEOUT_MS)),
+ ]),
+ ]);
- if (!this.destroyed && !this.panel) {
- this.destroy();
- }
+ if (!this.destroyed && !this.panel) {
+ this.destroy();
+ }
- if (this.destroyed) {
- CustomizableUI.hidePanelForNode(viewNode);
- return false;
- }
+ if (this.destroyed) {
+ CustomizableUI.hidePanelForNode(viewNode);
+ return false;
+ }
- this.attached = true;
+ this.attached = true;
- // Store the initial height of the view, so that we never resize menu panel
- // sub-views smaller than the initial height of the menu.
- this.viewHeight = this.viewNode.boxObject.height;
+ // Store the initial height of the view, so that we never resize menu panel
+ // sub-views smaller than the initial height of the menu.
+ this.viewHeight = this.viewNode.boxObject.height;
- // Calculate the extra height available on the screen above and below the
- // menu panel. Use that to calculate the how much the sub-view may grow.
- let popupRect = this.panel.getBoundingClientRect();
+ // Calculate the extra height available on the screen above and below the
+ // menu panel. Use that to calculate the how much the sub-view may grow.
+ let popupRect = this.panel.getBoundingClientRect();
- this.setBackground(this.background);
+ this.setBackground(this.background);
- let win = this.window;
- let popupBottom = win.mozInnerScreenY + popupRect.bottom;
- let popupTop = win.mozInnerScreenY + popupRect.top;
+ let win = this.window;
+ let popupBottom = win.mozInnerScreenY + popupRect.bottom;
+ let popupTop = win.mozInnerScreenY + popupRect.top;
- let screenBottom = win.screen.availTop + win.screen.availHeight;
- this.extraHeight = {
- bottom: Math.max(0, screenBottom - popupBottom),
- top: Math.max(0, popupTop - win.screen.availTop),
- };
+ let screenBottom = win.screen.availTop + win.screen.availHeight;
+ this.extraHeight = {
+ bottom: Math.max(0, screenBottom - popupBottom),
+ top: Math.max(0, popupTop - win.screen.availTop),
+ };
- // Create a new browser in the real popup.
- let browser = this.browser;
- await this.createBrowser(this.viewNode);
+ // Create a new browser in the real popup.
+ let browser = this.browser;
+ await this.createBrowser(this.viewNode);
- this.ignoreResizes = false;
+ this.ignoreResizes = false;
- this.browser.swapDocShells(browser);
- this.destroyBrowser(browser);
+ this.browser.swapDocShells(browser);
+ this.destroyBrowser(browser);
- if (this.dimensions && !this.fixedWidth) {
- this.resizeBrowser(this.dimensions);
- }
+ if (this.dimensions && !this.fixedWidth) {
+ this.resizeBrowser(this.dimensions);
+ }
- this.tempPanel.remove();
- this.tempPanel = null;
+ this.tempPanel.remove();
+ this.tempPanel = null;
- this.shown = true;
+ this.shown = true;
- if (this.destroyed) {
- this.closePopup();
- this.destroy();
- return false;
- }
+ if (this.destroyed) {
+ this.closePopup();
+ this.destroy();
+ return false;
+ }
- let event = new this.window.CustomEvent("WebExtPopupLoaded", {
- bubbles: true,
- detail: {extension: this.extension},
- });
- this.browser.dispatchEvent(event);
+ let event = new this.window.CustomEvent("WebExtPopupLoaded", {
+ bubbles: true,
+ detail: {extension: this.extension},
+ });
+ this.browser.dispatchEvent(event);
- return true;
- })();
+ return true;
}
destroy() {
return super.destroy().then(() => {
if (this.tempPanel) {
this.tempPanel.remove();
this.tempPanel = null;
}
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -171,30 +171,32 @@ function incrementListeners() {
PlacesUtils.bookmarks.addObserver(observer);
}
}
this.bookmarks = class extends ExtensionAPI {
getAPI(context) {
return {
bookmarks: {
- get: function(idOrIdList) {
+ async get(idOrIdList) {
let list = Array.isArray(idOrIdList) ? idOrIdList : [idOrIdList];
- return (async function() {
+ try {
let bookmarks = [];
for (let id of list) {
let bookmark = await PlacesUtils.bookmarks.fetch({guid: id});
if (!bookmark) {
throw new Error("Bookmark not found");
}
bookmarks.push(convert(bookmark));
}
return bookmarks;
- })().catch(error => Promise.reject({message: error.message}));
+ } catch (error) {
+ return Promise.reject({message: error.message});
+ }
},
getChildren: function(id) {
// TODO: We should optimize this.
return getTree(id, true);
},
getTree: function() {
--- a/browser/components/extensions/ext-devtools.js
+++ b/browser/components/extensions/ext-devtools.js
@@ -30,38 +30,36 @@ let initDevTools;
* the first time that it is accessed).
*
* @param {DevToolsExtensionPageContextParent} context
* A devtools extension proxy context.
*
* @returns {Promise<TabTarget>}
* The cloned devtools target associated to the context.
*/
-global.getDevToolsTargetForContext = (context) => {
- return (async function asyncGetTabTarget() {
- if (context.devToolsTarget) {
- await context.devToolsTarget.makeRemote();
- return context.devToolsTarget;
- }
+global.getDevToolsTargetForContext = async (context) => {
+ if (context.devToolsTarget) {
+ await context.devToolsTarget.makeRemote();
+ return context.devToolsTarget;
+ }
- if (!context.devToolsToolbox || !context.devToolsToolbox.target) {
- throw new Error("Unable to get a TabTarget for a context not associated to any toolbox");
- }
+ if (!context.devToolsToolbox || !context.devToolsToolbox.target) {
+ throw new Error("Unable to get a TabTarget for a context not associated to any toolbox");
+ }
- if (!context.devToolsToolbox.target.isLocalTab) {
- throw new Error("Unexpected target type: only local tabs are currently supported.");
- }
-
- const {TabTarget} = require("devtools/client/framework/target");
+ if (!context.devToolsToolbox.target.isLocalTab) {
+ throw new Error("Unexpected target type: only local tabs are currently supported.");
+ }
- context.devToolsTarget = new TabTarget(context.devToolsToolbox.target.tab);
- await context.devToolsTarget.makeRemote();
+ const {TabTarget} = require("devtools/client/framework/target");
- return context.devToolsTarget;
- })();
+ context.devToolsTarget = new TabTarget(context.devToolsToolbox.target.tab);
+ await context.devToolsTarget.makeRemote();
+
+ return context.devToolsTarget;
};
/**
* Retrieve the devtools target for the devtools extension proxy context
* (lazily cloned from the target of the toolbox associated to the context
* the first time that it is accessed).
*
* @param {Toolbox} toolbox
@@ -112,47 +110,45 @@ class DevToolsPage extends HiddenExtensi
this.unwatchExtensionProxyContextLoad = null;
this.waitForTopLevelContext = new Promise(resolve => {
this.resolveTopLevelContext = resolve;
});
}
- build() {
- return (async () => {
- await this.createBrowserElement();
+ async build() {
+ await this.createBrowserElement();
- // Listening to new proxy contexts.
- this.unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
- // Keep track of the toolbox and target associated to the context, which is
- // needed by the API methods implementation.
- context.devToolsToolbox = this.toolbox;
+ // Listening to new proxy contexts.
+ this.unwatchExtensionProxyContextLoad = watchExtensionProxyContextLoad(this, context => {
+ // Keep track of the toolbox and target associated to the context, which is
+ // needed by the API methods implementation.
+ context.devToolsToolbox = this.toolbox;
- if (!this.topLevelContext) {
- this.topLevelContext = context;
+ if (!this.topLevelContext) {
+ this.topLevelContext = context;
- // Ensure this devtools page is destroyed, when the top level context proxy is
- // closed.
- this.topLevelContext.callOnClose(this);
+ // Ensure this devtools page is destroyed, when the top level context proxy is
+ // closed.
+ this.topLevelContext.callOnClose(this);
- this.resolveTopLevelContext(context);
- }
- });
+ this.resolveTopLevelContext(context);
+ }
+ });
- extensions.emit("extension-browser-inserted", this.browser, {
- devtoolsToolboxInfo: {
- inspectedWindowTabId: getTargetTabIdForToolbox(this.toolbox),
- },
- });
+ extensions.emit("extension-browser-inserted", this.browser, {
+ devtoolsToolboxInfo: {
+ inspectedWindowTabId: getTargetTabIdForToolbox(this.toolbox),
+ },
+ });
- this.browser.loadURI(this.url);
+ this.browser.loadURI(this.url);
- await this.waitForTopLevelContext;
- })();
+ await this.waitForTopLevelContext;
}
close() {
if (this.closed) {
throw new Error("Unable to shutdown a closed DevToolsPage instance");
}
this.closed = true;
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -311,79 +311,77 @@ this.ExtensionData = class {
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 (async () => {
- if (this.rootURI instanceof Ci.nsIFileURL) {
- let uri = NetUtil.newURI(this.rootURI.resolve("./" + path));
- let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path;
+ async readDirectory(path) {
+ 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 = [];
- let iter = new OS.File.DirectoryIterator(fullPath);
- let results = [];
+ try {
+ await 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);
- try {
- await iter.forEach(entry => {
- results.push(entry);
+ 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("/"),
});
- } 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();
- }
- })();
+ return results;
+ } finally {
+ zipReader.close();
+ }
}
readJSON(path) {
return new Promise((resolve, reject) => {
let uri = this.rootURI.resolve(`./${path}`);
NetUtil.asyncFetch({uri, loadUsingSystemPrincipal: true}, (inputStream, status) => {
if (!Components.isSuccessCode(status)) {
@@ -564,30 +562,28 @@ this.ExtensionData = class {
// Gecko-compatible variant. Currently, this means simply
// replacing underscores with hyphens.
normalizeLocaleCode(locale) {
return locale.replace(/_/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 (async () => {
- let locales = await this.promiseLocales();
- let dir = locales.get(locale) || locale;
- let file = `_locales/${dir}/messages.json`;
+ async readLocaleFile(locale) {
+ let locales = await this.promiseLocales();
+ let dir = locales.get(locale) || locale;
+ let file = `_locales/${dir}/messages.json`;
- try {
- let messages = await this.readJSON(file);
- return this.localeData.addLocale(locale, messages, this);
- } catch (e) {
- this.packagingError(`Loading locale file ${file}: ${e}`);
- return new Map();
- }
- })();
+ try {
+ let messages = await this.readJSON(file);
+ return this.localeData.addLocale(locale, messages, this);
+ } catch (e) {
+ this.packagingError(`Loading locale file ${file}: ${e}`);
+ return new Map();
+ }
}
// 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)
@@ -616,65 +612,61 @@ this.ExtensionData = class {
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 (async () => {
- let locales = await this.promiseLocales();
+ async initAllLocales() {
+ let locales = await this.promiseLocales();
- await Promise.all(Array.from(locales.keys(),
- locale => this.readLocaleFile(locale)));
+ await 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.');
+ 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;
- })();
+ return this.localeData.messages;
}
// 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 (async () => {
- if (locale == null) {
- return null;
- }
+ async initLocale(locale = this.defaultLocale) {
+ if (locale == null) {
+ return null;
+ }
- let promises = [this.readLocaleFile(locale)];
+ let promises = [this.readLocaleFile(locale)];
- let {defaultLocale} = this;
- if (locale != defaultLocale && !this.localeData.has(defaultLocale)) {
- promises.push(this.readLocaleFile(defaultLocale));
- }
+ let {defaultLocale} = this;
+ if (locale != defaultLocale && !this.localeData.has(defaultLocale)) {
+ promises.push(this.readLocaleFile(defaultLocale));
+ }
- let results = await Promise.all(promises);
+ let results = await Promise.all(promises);
- this.localeData.selectedLocale = locale;
- return results[0];
- })();
+ this.localeData.selectedLocale = locale;
+ return results[0];
}
};
const PROXIED_EVENTS = new Set(["test-harness-message", "add-permissions", "remove-permissions"]);
// We create one instance of this class per extension. |addonData|
// comes directly from bootstrap.js when initializing.
this.Extension = class extends ExtensionData {
--- a/toolkit/components/extensions/ExtensionStorageSync.jsm
+++ b/toolkit/components/extensions/ExtensionStorageSync.jsm
@@ -169,86 +169,80 @@ const getKBHash = async function(fxaServ
/**
* A "remote transformer" that the Kinto library will use to
* encrypt/decrypt records when syncing.
*
* This is an "abstract base class". Subclass this and override
* getKeys() to use it.
*/
class EncryptionRemoteTransformer {
- encode(record) {
- const self = this;
- return (async function() {
- const keyBundle = await self.getKeys();
- if (record.ciphertext) {
- throw new Error("Attempt to reencrypt??");
- }
- let id = await self.getEncodedRecordId(record);
- if (!id) {
- throw new Error("Record ID is missing or invalid");
- }
+ async encode(record) {
+ const keyBundle = await this.getKeys();
+ if (record.ciphertext) {
+ throw new Error("Attempt to reencrypt??");
+ }
+ let id = await this.getEncodedRecordId(record);
+ if (!id) {
+ throw new Error("Record ID is missing or invalid");
+ }
- let IV = Svc.Crypto.generateRandomIV();
- let ciphertext = Svc.Crypto.encrypt(JSON.stringify(record),
- keyBundle.encryptionKeyB64, IV);
- let hmac = ciphertextHMAC(keyBundle, id, IV, ciphertext);
- const encryptedResult = {ciphertext, IV, hmac, id};
+ let IV = Svc.Crypto.generateRandomIV();
+ let ciphertext = Svc.Crypto.encrypt(JSON.stringify(record),
+ keyBundle.encryptionKeyB64, IV);
+ let hmac = ciphertextHMAC(keyBundle, id, IV, ciphertext);
+ const encryptedResult = {ciphertext, IV, hmac, id};
- // Copy over the _status field, so that we handle concurrency
- // headers (If-Match, If-None-Match) correctly.
- // DON'T copy over "deleted" status, because then we'd leak
- // plaintext deletes.
- encryptedResult._status = record._status == "deleted" ? "updated" : record._status;
- if (record.hasOwnProperty("last_modified")) {
- encryptedResult.last_modified = record.last_modified;
- }
+ // Copy over the _status field, so that we handle concurrency
+ // headers (If-Match, If-None-Match) correctly.
+ // DON'T copy over "deleted" status, because then we'd leak
+ // plaintext deletes.
+ encryptedResult._status = record._status == "deleted" ? "updated" : record._status;
+ if (record.hasOwnProperty("last_modified")) {
+ encryptedResult.last_modified = record.last_modified;
+ }
- return encryptedResult;
- })();
+ return encryptedResult;
}
- decode(record) {
- const self = this;
- return (async function() {
- if (!record.ciphertext) {
- // This can happen for tombstones if a record is deleted.
- if (record.deleted) {
- return record;
- }
- throw new Error("No ciphertext: nothing to decrypt?");
+ async decode(record) {
+ if (!record.ciphertext) {
+ // This can happen for tombstones if a record is deleted.
+ if (record.deleted) {
+ return record;
}
- const keyBundle = await self.getKeys();
- // Authenticate the encrypted blob with the expected HMAC
- let computedHMAC = ciphertextHMAC(keyBundle, record.id, record.IV, record.ciphertext);
+ throw new Error("No ciphertext: nothing to decrypt?");
+ }
+ const keyBundle = await this.getKeys();
+ // Authenticate the encrypted blob with the expected HMAC
+ let computedHMAC = ciphertextHMAC(keyBundle, record.id, record.IV, record.ciphertext);
- if (computedHMAC != record.hmac) {
- Utils.throwHMACMismatch(record.hmac, computedHMAC);
- }
+ if (computedHMAC != record.hmac) {
+ Utils.throwHMACMismatch(record.hmac, computedHMAC);
+ }
- // Handle invalid data here. Elsewhere we assume that cleartext is an object.
- let cleartext = Svc.Crypto.decrypt(record.ciphertext,
- keyBundle.encryptionKeyB64, record.IV);
- let jsonResult = JSON.parse(cleartext);
- if (!jsonResult || typeof jsonResult !== "object") {
- throw new Error("Decryption failed: result is <" + jsonResult + ">, not an object.");
- }
+ // Handle invalid data here. Elsewhere we assume that cleartext is an object.
+ let cleartext = Svc.Crypto.decrypt(record.ciphertext,
+ keyBundle.encryptionKeyB64, record.IV);
+ let jsonResult = JSON.parse(cleartext);
+ if (!jsonResult || typeof jsonResult !== "object") {
+ throw new Error("Decryption failed: result is <" + jsonResult + ">, not an object.");
+ }
- if (record.hasOwnProperty("last_modified")) {
- jsonResult.last_modified = record.last_modified;
- }
+ if (record.hasOwnProperty("last_modified")) {
+ jsonResult.last_modified = record.last_modified;
+ }
- // _status: deleted records were deleted on a client, but
- // uploaded as an encrypted blob so we don't leak deletions.
- // If we get such a record, flag it as deleted.
- if (jsonResult._status == "deleted") {
- jsonResult.deleted = true;
- }
+ // _status: deleted records were deleted on a client, but
+ // uploaded as an encrypted blob so we don't leak deletions.
+ // If we get such a record, flag it as deleted.
+ if (jsonResult._status == "deleted") {
+ jsonResult.deleted = true;
+ }
- return jsonResult;
- })();
+ return jsonResult;
}
/**
* Retrieve keys to use during encryption.
*
* Returns a Promise<KeyBundle>.
*/
getKeys() {
@@ -304,23 +298,20 @@ class KeyRingEncryptionRemoteTransformer
bundle.keyPair = [keyMaterial.slice(0, 32), keyMaterial.slice(32, 64)];
return bundle;
})();
}
// Pass through the kbHash field from the unencrypted record. If
// encryption fails, we can use this to try to detect whether we are
// being compromised or if the record here was encoded with a
// different kB.
- encode(record) {
- const encodePromise = super.encode(record);
- return (async function() {
- const encoded = await encodePromise;
- encoded.kbHash = record.kbHash;
- return encoded;
- })();
+ async encode(record) {
+ const encoded = await super.encode(record);
+ encoded.kbHash = record.kbHash;
+ return encoded;
}
async decode(record) {
if (record === null) {
// XXX: This is a hack that detects a situation that should
// never happen by using a technique that shouldn't actually
// work. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=1359879 for
@@ -675,28 +666,25 @@ this.CryptoCollection = CryptoCollection
*/
let CollectionKeyEncryptionRemoteTransformer = class extends EncryptionRemoteTransformer {
constructor(cryptoCollection, extensionId) {
super();
this.cryptoCollection = cryptoCollection;
this.extensionId = extensionId;
}
- getKeys() {
- const self = this;
- return (async function() {
- // FIXME: cache the crypto record for the duration of a sync cycle?
- const collectionKeys = await self.cryptoCollection.getKeyRing();
- if (!collectionKeys.hasKeysFor([self.extensionId])) {
- // This should never happen. Keys should be created (and
- // synced) at the beginning of the sync cycle.
- throw new Error(`tried to encrypt records for ${this.extensionId}, but key is not present`);
- }
- return collectionKeys.keyForCollection(self.extensionId);
- })();
+ async getKeys() {
+ // FIXME: cache the crypto record for the duration of a sync cycle?
+ const collectionKeys = await this.cryptoCollection.getKeyRing();
+ if (!collectionKeys.hasKeysFor([this.extensionId])) {
+ // This should never happen. Keys should be created (and
+ // synced) at the beginning of the sync cycle.
+ throw new Error(`tried to encrypt records for ${this.extensionId}, but key is not present`);
+ }
+ return collectionKeys.keyForCollection(this.extensionId);
}
getEncodedRecordId(record) {
// It isn't really clear whether kinto.js record IDs are
// bytestrings or strings that happen to only contain ASCII
// characters, so encode them to be sure.
const id = CommonUtils.encodeUTF8(record.id);
// Like extensionIdToCollectionId, the rules about Kinto record
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -2082,31 +2082,29 @@ function escapeAddonURI(aAddon, aUri, aU
compatMode = "ignore";
else if (AddonManager.strictCompatibility)
compatMode = "strict";
uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);
return uri;
}
-function removeAsync(aFile) {
- return (async function() {
- let info = null;
- try {
- info = await OS.File.stat(aFile.path);
- if (info.isDir)
- await OS.File.removeDir(aFile.path);
- else
- await OS.File.remove(aFile.path);
- } catch (e) {
- if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile)
- throw e;
- // The file has already gone away
- }
- })();
+async function removeAsync(aFile) {
+ let info = null;
+ try {
+ info = await OS.File.stat(aFile.path);
+ if (info.isDir)
+ await OS.File.removeDir(aFile.path);
+ else
+ await OS.File.remove(aFile.path);
+ } catch (e) {
+ if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile)
+ throw e;
+ // The file has already gone away
+ }
}
/**
* Recursively removes a directory or file fixing permissions when necessary.
*
* @param aFile
* The nsIFile to remove
*/
@@ -5940,120 +5938,118 @@ class AddonInstall {
* Called after the add-on is a local file and the signature and install
* manifest can be read.
*
* @param aCallback
* A function to call when the manifest has been loaded
* @throws if the add-on does not contain a valid install manifest or the
* XPI is incorrectly signed
*/
- loadManifest(file) {
- return (async () => {
- let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
- createInstance(Ci.nsIZipReader);
- try {
- zipreader.open(file);
- } catch (e) {
+ async loadManifest(file) {
+ let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ try {
+ zipreader.open(file);
+ } catch (e) {
+ zipreader.close();
+ return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
+ }
+
+ try {
+ // loadManifestFromZipReader performs the certificate verification for us
+ this.addon = await loadManifestFromZipReader(zipreader, this.installLocation);
+ } catch (e) {
+ zipreader.close();
+ return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
+ }
+
+ if (!this.addon.id) {
+ let err = new Error(`Cannot find id for addon ${file.path}`);
+ return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, err]);
+ }
+
+ if (this.existingAddon) {
+ // Check various conditions related to upgrades
+ if (this.addon.id != this.existingAddon.id) {
zipreader.close();
- return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
- }
-
- try {
- // loadManifestFromZipReader performs the certificate verification for us
- this.addon = await loadManifestFromZipReader(zipreader, this.installLocation);
- } catch (e) {
+ return Promise.reject([AddonManager.ERROR_INCORRECT_ID,
+ `Refusing to upgrade addon ${this.existingAddon.id} to different ID ${this.addon.id}`]);
+ }
+
+ if (isWebExtension(this.existingAddon.type) && !isWebExtension(this.addon.type)) {
zipreader.close();
- return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
- }
-
- if (!this.addon.id) {
- let err = new Error(`Cannot find id for addon ${file.path}`);
- return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, err]);
- }
-
- if (this.existingAddon) {
- // Check various conditions related to upgrades
- if (this.addon.id != this.existingAddon.id) {
- zipreader.close();
- return Promise.reject([AddonManager.ERROR_INCORRECT_ID,
- `Refusing to upgrade addon ${this.existingAddon.id} to different ID ${this.addon.id}`]);
- }
-
- if (isWebExtension(this.existingAddon.type) && !isWebExtension(this.addon.type)) {
+ return Promise.reject([AddonManager.ERROR_UNEXPECTED_ADDON_TYPE,
+ "WebExtensions may not be upated to other extension types"]);
+ }
+ }
+
+ if (mustSign(this.addon.type)) {
+ if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
+ // This add-on isn't properly signed by a signature that chains to the
+ // trusted root.
+ let state = this.addon.signedState;
+ this.addon = null;
+ zipreader.close();
+
+ if (state == AddonManager.SIGNEDSTATE_MISSING)
+ return Promise.reject([AddonManager.ERROR_SIGNEDSTATE_REQUIRED,
+ "signature is required but missing"])
+
+ return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
+ "signature verification failed"])
+ }
+ } else if (this.addon.signedState == AddonManager.SIGNEDSTATE_UNKNOWN ||
+ this.addon.signedState == AddonManager.SIGNEDSTATE_NOT_REQUIRED) {
+ // Check object signing certificate, if any
+ let x509 = zipreader.getSigningCert(null);
+ if (x509) {
+ logger.debug("Verifying XPI signature");
+ if (verifyZipSigning(zipreader, x509)) {
+ this.certificate = x509;
+ if (this.certificate.commonName.length > 0) {
+ this.certName = this.certificate.commonName;
+ } else {
+ this.certName = this.certificate.organization;
+ }
+ } else {
zipreader.close();
- return Promise.reject([AddonManager.ERROR_UNEXPECTED_ADDON_TYPE,
- "WebExtensions may not be upated to other extension types"]);
- }
- }
-
- if (mustSign(this.addon.type)) {
- if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
- // This add-on isn't properly signed by a signature that chains to the
- // trusted root.
- let state = this.addon.signedState;
- this.addon = null;
- zipreader.close();
-
- if (state == AddonManager.SIGNEDSTATE_MISSING)
- return Promise.reject([AddonManager.ERROR_SIGNEDSTATE_REQUIRED,
- "signature is required but missing"])
-
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
- "signature verification failed"])
- }
- } else if (this.addon.signedState == AddonManager.SIGNEDSTATE_UNKNOWN ||
- this.addon.signedState == AddonManager.SIGNEDSTATE_NOT_REQUIRED) {
- // Check object signing certificate, if any
- let x509 = zipreader.getSigningCert(null);
- if (x509) {
- logger.debug("Verifying XPI signature");
- if (verifyZipSigning(zipreader, x509)) {
- this.certificate = x509;
- if (this.certificate.commonName.length > 0) {
- this.certName = this.certificate.commonName;
- } else {
- this.certName = this.certificate.organization;
- }
- } else {
- zipreader.close();
- return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
- "XPI is incorrectly signed"]);
- }
+ "XPI is incorrectly signed"]);
}
}
-
- zipreader.close();
-
- this.updateAddonURIs();
-
- this.addon._install = this;
- this.name = this.addon.selectedLocale.name;
- this.type = this.addon.type;
- this.version = this.addon.version;
-
- // Setting the iconURL to something inside the XPI locks the XPI and
- // makes it impossible to delete on Windows.
-
- // Try to load from the existing cache first
- let repoAddon = await new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
-
- // It wasn't there so try to re-download it
- if (!repoAddon) {
- await new Promise(resolve => AddonRepository.cacheAddons([this.addon.id], resolve));
- repoAddon = await new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
- }
-
- this.addon._repositoryAddon = repoAddon;
- this.name = this.name || this.addon._repositoryAddon.name;
- this.addon.compatibilityOverrides = repoAddon ?
- repoAddon.compatibilityOverrides :
- null;
- this.addon.appDisabled = !isUsableAddon(this.addon);
- return undefined;
- })();
+ }
+
+ zipreader.close();
+
+ this.updateAddonURIs();
+
+ this.addon._install = this;
+ this.name = this.addon.selectedLocale.name;
+ this.type = this.addon.type;
+ this.version = this.addon.version;
+
+ // Setting the iconURL to something inside the XPI locks the XPI and
+ // makes it impossible to delete on Windows.
+
+ // Try to load from the existing cache first
+ let repoAddon = await new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
+
+ // It wasn't there so try to re-download it
+ if (!repoAddon) {
+ await new Promise(resolve => AddonRepository.cacheAddons([this.addon.id], resolve));
+ repoAddon = await new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
+ }
+
+ this.addon._repositoryAddon = repoAddon;
+ this.name = this.name || this.addon._repositoryAddon.name;
+ this.addon.compatibilityOverrides = repoAddon ?
+ repoAddon.compatibilityOverrides :
+ null;
+ this.addon.appDisabled = !isUsableAddon(this.addon);
+ return undefined;
}
getIcon(desiredSize = 64) {
if (!this.addon.icons || !this.file) {
return null;
}
let {icon} = IconDetails.getPreferredIcon(this.addon.icons, null, desiredSize);
@@ -6290,54 +6286,52 @@ class AddonInstall {
this.removeTemporaryFile();
return this.installLocation.releaseStagingDir();
});
}
/**
* Stages an upgrade for next application restart.
*/
- stageInstall(restartRequired, stagedAddon, isUpgrade) {
- return (async () => {
- let stagedJSON = stagedAddon.clone();
- stagedJSON.leafName = this.addon.id + ".json";
-
- let installedUnpacked = 0;
-
- // First stage the file regardless of whether restarting is necessary
- if (this.addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) {
- logger.debug("Addon " + this.addon.id + " will be installed as " +
- "an unpacked directory");
- stagedAddon.leafName = this.addon.id;
- await OS.File.makeDir(stagedAddon.path);
- await ZipUtils.extractFilesAsync(this.file, stagedAddon);
- installedUnpacked = 1;
- } else {
- logger.debug(`Addon ${this.addon.id} will be installed as a packed xpi`);
- stagedAddon.leafName = this.addon.id + ".xpi";
-
- await OS.File.copy(this.file.path, stagedAddon.path);
- }
-
- if (restartRequired) {
- // Point the add-on to its extracted files as the xpi may get deleted
- this.addon._sourceBundle = stagedAddon;
-
- // Cache the AddonInternal as it may have updated compatibility info
- writeStringToFile(stagedJSON, JSON.stringify(this.addon));
-
- logger.debug("Staged install of " + this.addon.id + " from " + this.sourceURI.spec + " ready; waiting for restart.");
- if (isUpgrade) {
- delete this.existingAddon.pendingUpgrade;
- this.existingAddon.pendingUpgrade = this.addon;
- }
- }
-
- return installedUnpacked;
- })();
+ async stageInstall(restartRequired, stagedAddon, isUpgrade) {
+ let stagedJSON = stagedAddon.clone();
+ stagedJSON.leafName = this.addon.id + ".json";
+
+ let installedUnpacked = 0;
+
+ // First stage the file regardless of whether restarting is necessary
+ if (this.addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) {
+ logger.debug("Addon " + this.addon.id + " will be installed as " +
+ "an unpacked directory");
+ stagedAddon.leafName = this.addon.id;
+ await OS.File.makeDir(stagedAddon.path);
+ await ZipUtils.extractFilesAsync(this.file, stagedAddon);
+ installedUnpacked = 1;
+ } else {
+ logger.debug(`Addon ${this.addon.id} will be installed as a packed xpi`);
+ stagedAddon.leafName = this.addon.id + ".xpi";
+
+ await OS.File.copy(this.file.path, stagedAddon.path);
+ }
+
+ if (restartRequired) {
+ // Point the add-on to its extracted files as the xpi may get deleted
+ this.addon._sourceBundle = stagedAddon;
+
+ // Cache the AddonInternal as it may have updated compatibility info
+ writeStringToFile(stagedJSON, JSON.stringify(this.addon));
+
+ logger.debug("Staged install of " + this.addon.id + " from " + this.sourceURI.spec + " ready; waiting for restart.");
+ if (isUpgrade) {
+ delete this.existingAddon.pendingUpgrade;
+ this.existingAddon.pendingUpgrade = this.addon;
+ }
+ }
+
+ return installedUnpacked;
}
/**
* Removes any previously staged upgrade.
*/
async unstageInstall(stagedAddon) {
let stagedJSON = getFile(`${this.addon.id}.json`, stagedAddon);
if (stagedJSON.exists()) {
@@ -6399,105 +6393,103 @@ class AddonInstall {
class LocalAddonInstall extends AddonInstall {
/**
* Initialises this install to be an install from a local file.
*
* @returns Promise
* A Promise that resolves when the object is ready to use.
*/
- init() {
- return (async () => {
- this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
-
- if (!this.file.exists()) {
- logger.warn("XPI file " + this.file.path + " does not exist");
+ async init() {
+ this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
+
+ if (!this.file.exists()) {
+ logger.warn("XPI file " + this.file.path + " does not exist");
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_NETWORK_FAILURE;
+ XPIProvider.removeActiveInstall(this);
+ return;
+ }
+
+ this.state = AddonManager.STATE_DOWNLOADED;
+ this.progress = this.file.fileSize;
+ this.maxProgress = this.file.fileSize;
+
+ if (this.hash) {
+ let crypto = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ try {
+ crypto.initWithString(this.hash.algorithm);
+ } catch (e) {
+ logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
- this.error = AddonManager.ERROR_NETWORK_FAILURE;
+ this.error = AddonManager.ERROR_INCORRECT_HASH;
+ XPIProvider.removeActiveInstall(this);
+ return;
+ }
+
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(this.file, -1, -1, false);
+ crypto.updateFromStream(fis, this.file.fileSize);
+ let calculatedHash = getHashStringForCrypto(crypto);
+ if (calculatedHash != this.hash.data) {
+ logger.warn("File hash (" + calculatedHash + ") did not match provided hash (" +
+ this.hash.data + ")");
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_INCORRECT_HASH;
XPIProvider.removeActiveInstall(this);
return;
}
-
- this.state = AddonManager.STATE_DOWNLOADED;
- this.progress = this.file.fileSize;
- this.maxProgress = this.file.fileSize;
-
- if (this.hash) {
- let crypto = Cc["@mozilla.org/security/hash;1"].
- createInstance(Ci.nsICryptoHash);
- try {
- crypto.initWithString(this.hash.algorithm);
- } catch (e) {
- logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
- this.state = AddonManager.STATE_DOWNLOAD_FAILED;
- this.error = AddonManager.ERROR_INCORRECT_HASH;
- XPIProvider.removeActiveInstall(this);
- return;
- }
-
- let fis = Cc["@mozilla.org/network/file-input-stream;1"].
- createInstance(Ci.nsIFileInputStream);
- fis.init(this.file, -1, -1, false);
- crypto.updateFromStream(fis, this.file.fileSize);
- let calculatedHash = getHashStringForCrypto(crypto);
- if (calculatedHash != this.hash.data) {
- logger.warn("File hash (" + calculatedHash + ") did not match provided hash (" +
- this.hash.data + ")");
- this.state = AddonManager.STATE_DOWNLOAD_FAILED;
- this.error = AddonManager.ERROR_INCORRECT_HASH;
- XPIProvider.removeActiveInstall(this);
- return;
- }
- }
-
- try {
- await this.loadManifest(this.file);
- } catch ([error, message]) {
- logger.warn("Invalid XPI", message);
- this.state = AddonManager.STATE_DOWNLOAD_FAILED;
- this.error = error;
- XPIProvider.removeActiveInstall(this);
- AddonManagerPrivate.callInstallListeners("onNewInstall",
- this.listeners,
- this.wrapper);
- flushJarCache(this.file);
- return;
- }
-
- let addon = await new Promise(resolve => {
- XPIDatabase.getVisibleAddonForID(this.addon.id, resolve);
+ }
+
+ try {
+ await this.loadManifest(this.file);
+ } catch ([error, message]) {
+ logger.warn("Invalid XPI", message);
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = error;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onNewInstall",
+ this.listeners,
+ this.wrapper);
+ flushJarCache(this.file);
+ return;
+ }
+
+ let addon = await new Promise(resolve => {
+ XPIDatabase.getVisibleAddonForID(this.addon.id, resolve);
+ });
+
+ this.existingAddon = addon;
+ if (addon)
+ applyBlocklistChanges(addon, this.addon);
+ this.addon.updateDate = Date.now();
+ this.addon.installDate = addon ? addon.installDate : this.addon.updateDate;
+
+ if (!this.addon.isCompatible) {
+ this.state = AddonManager.STATE_CHECKING;
+
+ await new Promise(resolve => {
+ new UpdateChecker(this.addon, {
+ onUpdateFinished: aAddon => {
+ this.state = AddonManager.STATE_DOWNLOADED;
+ AddonManagerPrivate.callInstallListeners("onNewInstall",
+ this.listeners,
+ this.wrapper);
+ resolve();
+ }
+ }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
});
-
- this.existingAddon = addon;
- if (addon)
- applyBlocklistChanges(addon, this.addon);
- this.addon.updateDate = Date.now();
- this.addon.installDate = addon ? addon.installDate : this.addon.updateDate;
-
- if (!this.addon.isCompatible) {
- this.state = AddonManager.STATE_CHECKING;
-
- await new Promise(resolve => {
- new UpdateChecker(this.addon, {
- onUpdateFinished: aAddon => {
- this.state = AddonManager.STATE_DOWNLOADED;
- AddonManagerPrivate.callInstallListeners("onNewInstall",
- this.listeners,
- this.wrapper);
- resolve();
- }
- }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
- });
- } else {
- AddonManagerPrivate.callInstallListeners("onNewInstall",
- this.listeners,
- this.wrapper);
-
- }
- })();
+ } else {
+ AddonManagerPrivate.callInstallListeners("onNewInstall",
+ this.listeners,
+ this.wrapper);
+
+ }
}
install() {
if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
// For a local install, this state means that verification of the
// file failed (e.g., the hash or signature or manifest contents
// were invalid). It doesn't make sense to retry anything in this
// case but we have callers who don't know if their AddonInstall