зеркало из https://github.com/mozilla/gecko-dev.git
Bug 633062 p5 - Remove event loop spinning from addonutils.js. r=markh
MozReview-Commit-ID: 1PSX4tOieEH --HG-- extra : rebase_source : e8804e1428c78d47fbc776e74da61d5de4e5f402
This commit is contained in:
Родитель
3e4633d6e3
Коммит
5491e15085
|
@ -25,21 +25,13 @@ AddonUtilsInternal.prototype = {
|
|||
/**
|
||||
* Obtain an AddonInstall object from an AddonSearchResult instance.
|
||||
*
|
||||
* The callback will be invoked with the result of the operation. The
|
||||
* callback receives 2 arguments, error and result. Error will be falsy
|
||||
* on success or some kind of error value otherwise. The result argument
|
||||
* will be an AddonInstall on success or null on failure. It is possible
|
||||
* for the error to be falsy but result to be null. This could happen if
|
||||
* an install was not found.
|
||||
* The returned promise will be an AddonInstall on success or null (failure or
|
||||
* addon not found)
|
||||
*
|
||||
* @param addon
|
||||
* AddonSearchResult to obtain install from.
|
||||
* @param cb
|
||||
* Function to be called with result of operation.
|
||||
*/
|
||||
getInstallFromSearchResult:
|
||||
function getInstallFromSearchResult(addon, cb) {
|
||||
|
||||
getInstallFromSearchResult(addon) {
|
||||
this._log.debug("Obtaining install for " + addon.id);
|
||||
|
||||
// We should theoretically be able to obtain (and use) addon.install if
|
||||
|
@ -47,11 +39,9 @@ AddonUtilsInternal.prototype = {
|
|||
// reflected in the AddonInstall, so we can't use it. If we ever get rid
|
||||
// of sourceURI rewriting, we can avoid having to reconstruct the
|
||||
// AddonInstall.
|
||||
AddonManager.getInstallForURL(
|
||||
return AddonManager.getInstallForURL(
|
||||
addon.sourceURI.spec,
|
||||
function handleInstall(install) {
|
||||
cb(null, install);
|
||||
},
|
||||
null,
|
||||
"application/x-xpinstall",
|
||||
undefined,
|
||||
addon.name,
|
||||
|
@ -70,11 +60,6 @@ AddonUtilsInternal.prototype = {
|
|||
* enabled - Boolean indicating whether the add-on should be enabled upon
|
||||
* install.
|
||||
*
|
||||
* When complete it calls a callback with 2 arguments, error and result.
|
||||
*
|
||||
* If error is falsy, result is an object. If error is truthy, result is
|
||||
* null.
|
||||
*
|
||||
* The result object has the following keys:
|
||||
*
|
||||
* id ID of add-on that was installed.
|
||||
|
@ -85,28 +70,20 @@ AddonUtilsInternal.prototype = {
|
|||
* AddonSearchResult to install add-on from.
|
||||
* @param options
|
||||
* Object with additional metadata describing how to install add-on.
|
||||
* @param cb
|
||||
* Function to be invoked with result of operation.
|
||||
*/
|
||||
installAddonFromSearchResult:
|
||||
function installAddonFromSearchResult(addon, options, cb) {
|
||||
async installAddonFromSearchResult(addon, options) {
|
||||
this._log.info("Trying to install add-on from search result: " + addon.id);
|
||||
|
||||
this.getInstallFromSearchResult(addon, (error, install) => {
|
||||
if (error) {
|
||||
cb(error, null);
|
||||
return;
|
||||
}
|
||||
|
||||
const install = await this.getInstallFromSearchResult(addon);
|
||||
if (!install) {
|
||||
cb(new Error("AddonInstall not available: " + addon.id), null);
|
||||
return;
|
||||
throw new Error("AddonInstall not available: " + addon.id);
|
||||
}
|
||||
|
||||
try {
|
||||
this._log.info("Installing " + addon.id);
|
||||
let log = this._log;
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
let listener = {
|
||||
onInstallStarted: function onInstallStarted(install) {
|
||||
if (!options) {
|
||||
|
@ -130,26 +107,26 @@ AddonUtilsInternal.prototype = {
|
|||
onInstallEnded(install, addon) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(null, {id: addon.id, install, addon});
|
||||
res({id: addon.id, install, addon});
|
||||
},
|
||||
onInstallFailed(install) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(new Error("Install failed: " + install.error), null);
|
||||
rej(new Error("Install failed: " + install.error));
|
||||
},
|
||||
onDownloadFailed(install) {
|
||||
install.removeListener(listener);
|
||||
|
||||
cb(new Error("Download failed: " + install.error), null);
|
||||
rej(new Error("Download failed: " + install.error));
|
||||
}
|
||||
};
|
||||
install.addListener(listener);
|
||||
install.install();
|
||||
});
|
||||
} catch (ex) {
|
||||
this._log.error("Error installing add-on", ex);
|
||||
cb(ex, null);
|
||||
throw ex;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -220,21 +197,24 @@ AddonUtilsInternal.prototype = {
|
|||
*
|
||||
* @param installs
|
||||
* Array of objects describing add-ons to install.
|
||||
* @param cb
|
||||
* Function to be called when all actions are complete.
|
||||
*/
|
||||
installAddons: function installAddons(installs, cb) {
|
||||
if (!cb) {
|
||||
throw new Error("Invalid argument: cb is not defined.");
|
||||
}
|
||||
|
||||
async installAddons(installs) {
|
||||
let ids = [];
|
||||
for (let addon of installs) {
|
||||
ids.push(addon.id);
|
||||
}
|
||||
|
||||
const {addons, addonsLength} = await new Promise((res, rej) => {
|
||||
AddonRepository.getAddonsByIDs(ids, {
|
||||
searchSucceeded: (addons, addonsLength, total) => {
|
||||
res({addons, addonsLength});
|
||||
},
|
||||
searchFailed() {
|
||||
rej(new Error("AddonRepository search failed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._log.info("Found " + addonsLength + "/" + ids.length +
|
||||
" add-ons during repository search.");
|
||||
|
||||
|
@ -247,32 +227,9 @@ AddonUtilsInternal.prototype = {
|
|||
};
|
||||
|
||||
if (!addonsLength) {
|
||||
cb(null, ourResult);
|
||||
return;
|
||||
return ourResult;
|
||||
}
|
||||
|
||||
let expectedInstallCount = 0;
|
||||
let finishedCount = 0;
|
||||
let installCallback = function installCallback(error, result) {
|
||||
finishedCount++;
|
||||
|
||||
if (error) {
|
||||
ourResult.errors.push(error);
|
||||
} else {
|
||||
ourResult.installedIDs.push(result.id);
|
||||
ourResult.installs.push(result.install);
|
||||
ourResult.addons.push(result.addon);
|
||||
}
|
||||
|
||||
if (finishedCount >= expectedInstallCount) {
|
||||
if (ourResult.errors.length > 0) {
|
||||
cb(new Error("1 or more add-ons failed to install"), ourResult);
|
||||
} else {
|
||||
cb(null, ourResult);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let toInstall = [];
|
||||
|
||||
// Rewrite the "src" query string parameter of the source URI to note
|
||||
|
@ -320,13 +277,11 @@ AddonUtilsInternal.prototype = {
|
|||
addon.sourceURI.query = params.join("&");
|
||||
}
|
||||
|
||||
expectedInstallCount = toInstall.length;
|
||||
|
||||
if (!expectedInstallCount) {
|
||||
cb(null, ourResult);
|
||||
return;
|
||||
if (!toInstall.length) {
|
||||
return ourResult;
|
||||
}
|
||||
|
||||
const installPromises = [];
|
||||
// Start all the installs asynchronously. They will report back to us
|
||||
// as they finish, eventually triggering the global callback.
|
||||
for (let addon of toInstall) {
|
||||
|
@ -338,15 +293,24 @@ AddonUtilsInternal.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
this.installAddonFromSearchResult(addon, options, installCallback);
|
||||
installPromises.push((async () => {
|
||||
try {
|
||||
const result = await this.installAddonFromSearchResult(addon, options);
|
||||
ourResult.installedIDs.push(result.id);
|
||||
ourResult.installs.push(result.install);
|
||||
ourResult.addons.push(result.addon);
|
||||
} catch (error) {
|
||||
ourResult.errors.push(error);
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
},
|
||||
await Promise.all(installPromises);
|
||||
|
||||
searchFailed: function searchFailed() {
|
||||
cb(new Error("AddonRepository search failed"), null);
|
||||
},
|
||||
});
|
||||
if (ourResult.errors.length > 0) {
|
||||
throw new Error("1 or more add-ons failed to install");
|
||||
}
|
||||
return ourResult;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -305,17 +305,14 @@ AddonsStore.prototype = {
|
|||
* Provides core Store API to create/install an add-on from a record.
|
||||
*/
|
||||
async create(record) {
|
||||
let cb = Async.makeSpinningCallback();
|
||||
AddonUtils.installAddons([{
|
||||
// This will throw if there was an error. This will get caught by the sync
|
||||
// engine and the record will try to be applied later.
|
||||
const results = await AddonUtils.installAddons([{
|
||||
id: record.addonID,
|
||||
syncGUID: record.id,
|
||||
enabled: record.enabled,
|
||||
requireSecureURI: this._extensionsPrefs.get("install.requireSecureOrigin", true),
|
||||
}], cb);
|
||||
|
||||
// This will throw if there was an error. This will get caught by the sync
|
||||
// engine and the record will try to be applied later.
|
||||
let results = cb.wait();
|
||||
}]);
|
||||
|
||||
if (results.skipped.includes(record.addonID)) {
|
||||
this._log.info("Add-on skipped: " + record.addonID);
|
||||
|
@ -370,7 +367,7 @@ AddonsStore.prototype = {
|
|||
// First, the reconciler could know about an add-on that was uninstalled
|
||||
// and no longer present in the add-ons manager.
|
||||
if (!addon) {
|
||||
this.create(record);
|
||||
await this.create(record);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,16 +46,14 @@ function run_test() {
|
|||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_handle_empty_source_uri() {
|
||||
add_task(async function test_handle_empty_source_uri() {
|
||||
_("Ensure that search results without a sourceURI are properly ignored.");
|
||||
|
||||
let server = createAndStartHTTPServer();
|
||||
|
||||
const ID = "missing-sourceuri@tests.mozilla.org";
|
||||
|
||||
let cb = Async.makeSpinningCallback();
|
||||
AddonUtils.installAddons([{id: ID, requireSecureURI: false}], cb);
|
||||
let result = cb.wait();
|
||||
const result = await AddonUtils.installAddons([{id: ID, requireSecureURI: false}]);
|
||||
|
||||
Assert.ok("installedIDs" in result);
|
||||
Assert.equal(0, result.installedIDs.length);
|
||||
|
@ -63,7 +61,7 @@ add_test(function test_handle_empty_source_uri() {
|
|||
Assert.ok("skipped" in result);
|
||||
Assert.ok(result.skipped.includes(ID));
|
||||
|
||||
server.stop(run_next_test);
|
||||
await promiseStopServer(server);
|
||||
});
|
||||
|
||||
add_test(function test_ignore_untrusted_source_uris() {
|
||||
|
@ -93,7 +91,7 @@ add_test(function test_ignore_untrusted_source_uris() {
|
|||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_source_uri_rewrite() {
|
||||
add_task(async function test_source_uri_rewrite() {
|
||||
_("Ensure that a 'src=api' query string is rewritten to 'src=sync'");
|
||||
|
||||
// This tests for conformance with bug 708134 so server-side metrics aren't
|
||||
|
@ -104,34 +102,29 @@ add_test(function test_source_uri_rewrite() {
|
|||
|
||||
let installCalled = false;
|
||||
AddonUtils.__proto__.installAddonFromSearchResult =
|
||||
function testInstallAddon(addon, metadata, cb) {
|
||||
async function testInstallAddon(addon, metadata) {
|
||||
|
||||
Assert.equal(SERVER_ADDRESS + "/require.xpi?src=sync",
|
||||
addon.sourceURI.spec);
|
||||
|
||||
installCalled = true;
|
||||
|
||||
AddonUtils.getInstallFromSearchResult(addon, function(error, install) {
|
||||
Assert.equal(null, error);
|
||||
const install = await AddonUtils.getInstallFromSearchResult(addon);
|
||||
Assert.equal(SERVER_ADDRESS + "/require.xpi?src=sync",
|
||||
install.sourceURI.spec);
|
||||
|
||||
cb(null, {id: addon.id, addon, install});
|
||||
}, false);
|
||||
return {id: addon.id, addon, install};
|
||||
};
|
||||
|
||||
let server = createAndStartHTTPServer();
|
||||
|
||||
let installCallback = Async.makeSpinningCallback();
|
||||
let installOptions = {
|
||||
id: "rewrite@tests.mozilla.org",
|
||||
requireSecureURI: false,
|
||||
};
|
||||
AddonUtils.installAddons([installOptions], installCallback);
|
||||
await AddonUtils.installAddons([installOptions]);
|
||||
|
||||
installCallback.wait();
|
||||
Assert.ok(installCalled);
|
||||
AddonUtils.__proto__.installAddonFromSearchResult = oldFunction;
|
||||
|
||||
server.stop(run_next_test);
|
||||
await promiseStopServer(server);
|
||||
});
|
||||
|
|
|
@ -10,7 +10,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
|||
ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/addons/AddonRepository.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
|
||||
ChromeUtils.import("resource://services-common/async.js");
|
||||
ChromeUtils.import("resource://services-sync/addonutils.js");
|
||||
ChromeUtils.import("resource://services-sync/util.js");
|
||||
ChromeUtils.import("resource://tps/logger.jsm");
|
||||
|
@ -85,13 +84,11 @@ Addon.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
install: function install() {
|
||||
async install() {
|
||||
// For Install, the id parameter initially passed is really the filename
|
||||
// for the addon's install .xml; we'll read the actual id from the .xml.
|
||||
|
||||
let cb = Async.makeSpinningCallback();
|
||||
AddonUtils.installAddons([{id: this.id, requireSecureURI: false}], cb);
|
||||
let result = cb.wait();
|
||||
const result = await AddonUtils.installAddons([{id: this.id, requireSecureURI: false}]);
|
||||
|
||||
Logger.AssertEqual(1, result.installedIDs.length, "Exactly 1 add-on was installed.");
|
||||
Logger.AssertEqual(this.id, result.installedIDs[0],
|
||||
|
|
|
@ -484,7 +484,7 @@ var TPS = {
|
|||
let addon = new Addon(this, entry);
|
||||
switch (action) {
|
||||
case ACTION_ADD:
|
||||
addon.install();
|
||||
await addon.install();
|
||||
break;
|
||||
case ACTION_DELETE:
|
||||
await addon.uninstall();
|
||||
|
|
Загрузка…
Ссылка в новой задаче