mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 925389: Cancel XPI update check if the add-on is uninstalled while the check is in progress; r=Unfocused
This commit is contained in:
parent
a083ecd946
commit
8ab0829977
@ -2279,6 +2279,8 @@ this.AddonManager = {
|
||||
UPDATE_STATUS_UNKNOWN_FORMAT: -4,
|
||||
// The update information was not correctly signed or there was an SSL error.
|
||||
UPDATE_STATUS_SECURITY_ERROR: -5,
|
||||
// The update was cancelled.
|
||||
UPDATE_STATUS_CANCELLED: -6,
|
||||
|
||||
// Constants to indicate why an update check is being performed
|
||||
// Update check has been requested by the user.
|
||||
|
@ -490,8 +490,14 @@ UpdateParser.prototype = {
|
||||
this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
|
||||
return;
|
||||
}
|
||||
if ("onUpdateCheckComplete" in this.observer)
|
||||
this.observer.onUpdateCheckComplete(results);
|
||||
if ("onUpdateCheckComplete" in this.observer) {
|
||||
try {
|
||||
this.observer.onUpdateCheckComplete(results);
|
||||
}
|
||||
catch (e) {
|
||||
WARN("onUpdateCheckComplete notification failed", e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -534,8 +540,14 @@ UpdateParser.prototype = {
|
||||
* Helper method to notify the observer that an error occured.
|
||||
*/
|
||||
notifyError: function UP_notifyError(aStatus) {
|
||||
if ("onUpdateCheckError" in this.observer)
|
||||
this.observer.onUpdateCheckError(aStatus);
|
||||
if ("onUpdateCheckError" in this.observer) {
|
||||
try {
|
||||
this.observer.onUpdateCheckError(aStatus);
|
||||
}
|
||||
catch (e) {
|
||||
WARN("onUpdateCheckError notification failed", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -549,6 +561,17 @@ UpdateParser.prototype = {
|
||||
WARN("Request timed out");
|
||||
|
||||
this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called to cancel an in-progress update check.
|
||||
*/
|
||||
cancel: function UP_cancel() {
|
||||
this.timer.cancel();
|
||||
this.timer = null;
|
||||
this.request.abort();
|
||||
this.request = null;
|
||||
this.notifyError(AddonUpdateChecker.ERROR_CANCELLED);
|
||||
}
|
||||
};
|
||||
|
||||
@ -610,6 +633,8 @@ this.AddonUpdateChecker = {
|
||||
ERROR_UNKNOWN_FORMAT: -4,
|
||||
// The update information was not correctly signed or there was an SSL error.
|
||||
ERROR_SECURITY_ERROR: -5,
|
||||
// The update was cancelled
|
||||
ERROR_CANCELLED: -6,
|
||||
|
||||
/**
|
||||
* Retrieves the best matching compatibility update for the application from
|
||||
@ -721,9 +746,11 @@ this.AddonUpdateChecker = {
|
||||
* The URL of the add-on's update manifest
|
||||
* @param aObserver
|
||||
* An observer to notify of results
|
||||
* @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
|
||||
* down in-progress update requests
|
||||
*/
|
||||
checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl,
|
||||
aObserver) {
|
||||
new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
|
||||
return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
|
||||
}
|
||||
};
|
||||
|
@ -1683,6 +1683,24 @@ function directoryStateDiffers(aState, aCache)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a function in an exception handler to protect against exceptions inside callbacks
|
||||
* @param aFunction function(args...)
|
||||
* @return function(args...), a function that takes the same arguments as aFunction
|
||||
* and returns the same result unless aFunction throws, in which case it logs
|
||||
* a warning and returns undefined.
|
||||
*/
|
||||
function makeSafe(aFunction) {
|
||||
return function(...aArgs) {
|
||||
try {
|
||||
return aFunction(...aArgs);
|
||||
}
|
||||
catch(ex) {
|
||||
WARN("XPIProvider callback failed", ex);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
var XPIProvider = {
|
||||
// An array of known install locations
|
||||
@ -1734,6 +1752,32 @@ var XPIProvider = {
|
||||
this._telemetryDetails[aId][aName] = aValue;
|
||||
},
|
||||
|
||||
// Keep track of in-progress operations that support cancel()
|
||||
_inProgress: new Set(),
|
||||
|
||||
doing: function XPI_doing(aCancellable) {
|
||||
this._inProgress.add(aCancellable);
|
||||
},
|
||||
|
||||
done: function XPI_done(aCancellable) {
|
||||
return this._inProgress.delete(aCancellable);
|
||||
},
|
||||
|
||||
cancelAll: function XPI_cancelAll() {
|
||||
// Cancelling one may alter _inProgress, so restart the iterator after each
|
||||
while (this._inProgress.size > 0) {
|
||||
for (let c of this._inProgress) {
|
||||
try {
|
||||
c.cancel();
|
||||
}
|
||||
catch (e) {
|
||||
WARN("Cancel failed", e);
|
||||
}
|
||||
this._inProgress.delete(c);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds or updates a URI mapping for an Addon.id.
|
||||
*
|
||||
@ -2076,6 +2120,9 @@ var XPIProvider = {
|
||||
shutdown: function XPI_shutdown() {
|
||||
LOG("shutdown");
|
||||
|
||||
// Stop anything we were doing asynchronously
|
||||
this.cancelAll();
|
||||
|
||||
this.bootstrappedAddons = {};
|
||||
this.bootstrapScopes = {};
|
||||
this.enabledAddons = null;
|
||||
@ -3308,7 +3355,7 @@ var XPIProvider = {
|
||||
locMigrateData = XPIDatabase.migrateData[installLocation.name];
|
||||
for (let id in addonStates) {
|
||||
changed = addMetadata(installLocation, id, addonStates[id],
|
||||
locMigrateData[id] || null) || changed;
|
||||
(locMigrateData[id] || null)) || changed;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3642,10 +3689,7 @@ var XPIProvider = {
|
||||
*/
|
||||
getAddonByID: function XPI_getAddonByID(aId, aCallback) {
|
||||
XPIDatabase.getVisibleAddonForID (aId, function getAddonByID_getVisibleAddonForID(aAddon) {
|
||||
if (aAddon)
|
||||
aCallback(createWrapper(aAddon));
|
||||
else
|
||||
aCallback(null);
|
||||
aCallback(createWrapper(aAddon));
|
||||
});
|
||||
},
|
||||
|
||||
@ -3673,10 +3717,7 @@ var XPIProvider = {
|
||||
*/
|
||||
getAddonBySyncGUID: function XPI_getAddonBySyncGUID(aGUID, aCallback) {
|
||||
XPIDatabase.getAddonBySyncGUID(aGUID, function getAddonBySyncGUID_getAddonBySyncGUID(aAddon) {
|
||||
if (aAddon)
|
||||
aCallback(createWrapper(aAddon));
|
||||
else
|
||||
aCallback(null);
|
||||
aCallback(createWrapper(aAddon));
|
||||
});
|
||||
},
|
||||
|
||||
@ -4382,6 +4423,11 @@ var XPIProvider = {
|
||||
if ("_hasResourceCache" in aAddon)
|
||||
aAddon._hasResourceCache = new Map();
|
||||
|
||||
if (aAddon._updateCheck) {
|
||||
LOG("Cancel in-progress update check for " + aAddon.id);
|
||||
aAddon._updateCheck.cancel();
|
||||
}
|
||||
|
||||
// Inactive add-ons don't require a restart to uninstall
|
||||
let requiresRestart = this.uninstallRequiresRestart(aAddon);
|
||||
|
||||
@ -4631,6 +4677,7 @@ AddonInstall.prototype = {
|
||||
* The callback to pass the initialised AddonInstall to
|
||||
*/
|
||||
initLocalInstall: function AI_initLocalInstall(aCallback) {
|
||||
aCallback = makeSafe(aCallback);
|
||||
this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
|
||||
|
||||
if (!this.file.exists()) {
|
||||
@ -4746,7 +4793,7 @@ AddonInstall.prototype = {
|
||||
AddonManagerPrivate.callInstallListeners("onNewInstall", this.listeners,
|
||||
this.wrapper);
|
||||
|
||||
aCallback(this);
|
||||
makeSafe(aCallback)(this);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -4946,7 +4993,7 @@ AddonInstall.prototype = {
|
||||
|
||||
if (!addon) {
|
||||
// No valid add-on was found
|
||||
aCallback();
|
||||
makeSafe(aCallback)();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4995,7 +5042,7 @@ AddonInstall.prototype = {
|
||||
}, this);
|
||||
}
|
||||
else {
|
||||
aCallback();
|
||||
makeSafe(aCallback)();
|
||||
}
|
||||
},
|
||||
|
||||
@ -5009,6 +5056,7 @@ AddonInstall.prototype = {
|
||||
* XPI is incorrectly signed
|
||||
*/
|
||||
loadManifest: function AI_loadManifest(aCallback) {
|
||||
aCallback = makeSafe(aCallback);
|
||||
let self = this;
|
||||
function addRepositoryData(aAddon) {
|
||||
// Try to load from the existing cache first
|
||||
@ -5680,7 +5728,7 @@ AddonInstall.createInstall = function AI_createInstall(aCallback, aFile) {
|
||||
}
|
||||
catch(e) {
|
||||
ERROR("Error creating install", e);
|
||||
aCallback(null);
|
||||
makeSafe(aCallback)(null);
|
||||
}
|
||||
};
|
||||
|
||||
@ -5819,6 +5867,8 @@ function UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion
|
||||
Components.utils.import("resource://gre/modules/AddonUpdateChecker.jsm");
|
||||
|
||||
this.addon = aAddon;
|
||||
aAddon._updateCheck = this;
|
||||
XPIProvider.doing(this);
|
||||
this.listener = aListener;
|
||||
this.appVersion = aAppVersion;
|
||||
this.platformVersion = aPlatformVersion;
|
||||
@ -5842,8 +5892,8 @@ function UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion
|
||||
aReason |= UPDATE_TYPE_NEWVERSION;
|
||||
|
||||
let url = escapeAddonURI(aAddon, updateURL, aReason, aAppVersion);
|
||||
AddonUpdateChecker.checkForUpdates(aAddon.id, aAddon.updateKey,
|
||||
url, this);
|
||||
this._parser = AddonUpdateChecker.checkForUpdates(aAddon.id, aAddon.updateKey,
|
||||
url, this);
|
||||
}
|
||||
|
||||
UpdateChecker.prototype = {
|
||||
@ -5868,7 +5918,7 @@ UpdateChecker.prototype = {
|
||||
this.listener[aMethod].apply(this.listener, aArgs);
|
||||
}
|
||||
catch (e) {
|
||||
LOG("Exception calling UpdateListener method " + aMethod + ": " + e);
|
||||
WARN("Exception calling UpdateListener method " + aMethod, e);
|
||||
}
|
||||
},
|
||||
|
||||
@ -5879,6 +5929,8 @@ UpdateChecker.prototype = {
|
||||
* The list of update details for the add-on
|
||||
*/
|
||||
onUpdateCheckComplete: function UC_onUpdateCheckComplete(aUpdates) {
|
||||
XPIProvider.done(this.addon._updateCheck);
|
||||
this.addon._updateCheck = null;
|
||||
let AUC = AddonUpdateChecker;
|
||||
|
||||
let ignoreMaxVersion = false;
|
||||
@ -5979,9 +6031,23 @@ UpdateChecker.prototype = {
|
||||
* An error status
|
||||
*/
|
||||
onUpdateCheckError: function UC_onUpdateCheckError(aError) {
|
||||
XPIProvider.done(this.addon._updateCheck);
|
||||
this.addon._updateCheck = null;
|
||||
this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
|
||||
this.callListener("onNoUpdateAvailable", createWrapper(this.addon));
|
||||
this.callListener("onUpdateFinished", createWrapper(this.addon), aError);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called to cancel an in-progress update check
|
||||
*/
|
||||
cancel: function UC_cancel() {
|
||||
let parser = this._parser;
|
||||
if (parser) {
|
||||
this._parser = null;
|
||||
// This will call back to onUpdateCheckError with a CANCELLED error
|
||||
parser.cancel();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -6635,6 +6701,15 @@ function AddonWrapper(aAddon) {
|
||||
new UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion);
|
||||
};
|
||||
|
||||
// Returns true if there was an update in progress, false if there was no update to cancel
|
||||
this.cancelUpdate = function AddonWrapper_cancelUpdate() {
|
||||
if (aAddon._updateCheck) {
|
||||
aAddon._updateCheck.cancel();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.hasResource = function AddonWrapper_hasResource(aPath) {
|
||||
if (aAddon._hasResourceCache.has(aPath))
|
||||
return aAddon._hasResourceCache.get(aPath);
|
||||
|
@ -146,10 +146,10 @@ function getRepositoryAddon(aAddon, aCallback) {
|
||||
/**
|
||||
* Wrap an API-supplied function in an exception handler to make it safe to call
|
||||
*/
|
||||
function safeCallback(aCallback) {
|
||||
function makeSafe(aCallback) {
|
||||
return function(...aArgs) {
|
||||
try {
|
||||
aCallback.apply(null, aArgs);
|
||||
aCallback(...aArgs);
|
||||
}
|
||||
catch(ex) {
|
||||
WARN("XPI Database callback failed", ex);
|
||||
@ -1057,12 +1057,12 @@ this.XPIDatabase = {
|
||||
this.asyncLoadDB().then(
|
||||
addonDB => {
|
||||
let addonList = _filterDB(addonDB, aFilter);
|
||||
asyncMap(addonList, getRepositoryAddon, safeCallback(aCallback));
|
||||
asyncMap(addonList, getRepositoryAddon, makeSafe(aCallback));
|
||||
})
|
||||
.then(null,
|
||||
error => {
|
||||
ERROR("getAddonList failed", e);
|
||||
safeCallback(aCallback)([]);
|
||||
makeSafe(aCallback)([]);
|
||||
});
|
||||
},
|
||||
|
||||
@ -1077,12 +1077,12 @@ this.XPIDatabase = {
|
||||
getAddon: function(aFilter, aCallback) {
|
||||
return this.asyncLoadDB().then(
|
||||
addonDB => {
|
||||
getRepositoryAddon(_findAddon(addonDB, aFilter), safeCallback(aCallback));
|
||||
getRepositoryAddon(_findAddon(addonDB, aFilter), makeSafe(aCallback));
|
||||
})
|
||||
.then(null,
|
||||
error => {
|
||||
ERROR("getAddon failed", e);
|
||||
safeCallback(aCallback)(null);
|
||||
makeSafe(aCallback)(null);
|
||||
});
|
||||
},
|
||||
|
||||
@ -1112,7 +1112,7 @@ this.XPIDatabase = {
|
||||
getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
|
||||
this.asyncLoadDB().then(
|
||||
addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId),
|
||||
safeCallback(aCallback)));
|
||||
makeSafe(aCallback)));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -16,13 +16,29 @@ const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVer
|
||||
// Forcibly end the test if it runs longer than 15 minutes
|
||||
const TIMEOUT_MS = 900000;
|
||||
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
Components.utils.import("resource://gre/modules/AddonRepository.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
// We need some internal bits of AddonManager
|
||||
let AMscope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
let AddonManager = AMscope.AddonManager;
|
||||
let AddonManagerInternal = AMscope.AddonManagerInternal;
|
||||
// Mock out AddonManager's reference to the AsyncShutdown module so we can shut
|
||||
// down AddonManager from the test
|
||||
let MockAsyncShutdown = {
|
||||
hook: null,
|
||||
profileBeforeChange: {
|
||||
addBlocker: function(aName, aBlocker) {
|
||||
do_print("Mock profileBeforeChange blocker for '" + aName + "'");
|
||||
MockAsyncShutdown.hook = aBlocker;
|
||||
}
|
||||
}
|
||||
};
|
||||
AMscope.AsyncShutdown = MockAsyncShutdown;
|
||||
|
||||
var gInternalManager = null;
|
||||
var gAppInfo = null;
|
||||
var gAddonsList;
|
||||
@ -403,11 +419,16 @@ function shutdownManager() {
|
||||
let shutdownDone = false;
|
||||
|
||||
Services.obs.notifyObservers(null, "quit-application-granted", null);
|
||||
let scope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
scope.AddonManagerInternal.shutdown()
|
||||
.then(
|
||||
() => shutdownDone = true,
|
||||
err => shutdownDone = true);
|
||||
MockAsyncShutdown.hook().then(
|
||||
() => shutdownDone = true,
|
||||
err => shutdownDone = true);
|
||||
|
||||
let thr = Services.tm.mainThread;
|
||||
|
||||
// Wait until we observe the shutdown notifications
|
||||
while (!shutdownDone) {
|
||||
thr.processNextEvent(true);
|
||||
}
|
||||
|
||||
gInternalManager = null;
|
||||
|
||||
@ -417,20 +438,13 @@ function shutdownManager() {
|
||||
// Clear any crash report annotations
|
||||
gAppInfo.annotations = {};
|
||||
|
||||
let thr = Services.tm.mainThread;
|
||||
|
||||
// Wait until we observe the shutdown notifications
|
||||
while (!shutdownDone) {
|
||||
thr.processNextEvent(true);
|
||||
}
|
||||
|
||||
// Force the XPIProvider provider to reload to better
|
||||
// simulate real-world usage.
|
||||
scope = Components.utils.import("resource://gre/modules/XPIProvider.jsm");
|
||||
let XPIscope = Components.utils.import("resource://gre/modules/XPIProvider.jsm");
|
||||
// This would be cleaner if I could get it as the rejection reason from
|
||||
// the AddonManagerInternal.shutdown() promise
|
||||
gXPISaveError = scope.XPIProvider._shutdownError;
|
||||
AddonManagerPrivate.unregisterProvider(scope.XPIProvider);
|
||||
gXPISaveError = XPIscope.XPIProvider._shutdownError;
|
||||
AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
|
||||
Components.utils.unload("resource://gre/modules/XPIProvider.jsm");
|
||||
}
|
||||
|
||||
@ -1090,16 +1104,24 @@ if ("nsIWindowsRegKey" in AM_Ci) {
|
||||
var MockRegistry = {
|
||||
LOCAL_MACHINE: {},
|
||||
CURRENT_USER: {},
|
||||
CLASSES_ROOT: {},
|
||||
|
||||
setValue: function(aRoot, aPath, aName, aValue) {
|
||||
getRoot: function(aRoot) {
|
||||
switch (aRoot) {
|
||||
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE:
|
||||
var rootKey = MockRegistry.LOCAL_MACHINE;
|
||||
break
|
||||
return MockRegistry.LOCAL_MACHINE;
|
||||
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER:
|
||||
rootKey = MockRegistry.CURRENT_USER;
|
||||
break
|
||||
return MockRegistry.CURRENT_USER;
|
||||
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT:
|
||||
return MockRegistry.CLASSES_ROOT;
|
||||
default:
|
||||
do_throw("Unknown root " + aRootKey);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
setValue: function(aRoot, aPath, aName, aValue) {
|
||||
let rootKey = MockRegistry.getRoot(aRoot);
|
||||
|
||||
if (!(aPath in rootKey)) {
|
||||
rootKey[aPath] = [];
|
||||
@ -1141,14 +1163,7 @@ if ("nsIWindowsRegKey" in AM_Ci) {
|
||||
|
||||
// --- Overridden nsIWindowsRegKey interface functions ---
|
||||
open: function(aRootKey, aRelPath, aMode) {
|
||||
switch (aRootKey) {
|
||||
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE:
|
||||
var rootKey = MockRegistry.LOCAL_MACHINE;
|
||||
break
|
||||
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER:
|
||||
rootKey = MockRegistry.CURRENT_USER;
|
||||
break
|
||||
}
|
||||
let rootKey = MockRegistry.getRoot(aRootKey);
|
||||
|
||||
if (!(aRelPath in rootKey))
|
||||
rootKey[aRelPath] = [];
|
||||
@ -1398,9 +1413,9 @@ function changeXPIDBVersion(aNewVersion) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw load of a JSON file
|
||||
* Load a file into a string
|
||||
*/
|
||||
function loadJSON(aFile) {
|
||||
function loadFile(aFile) {
|
||||
let data = "";
|
||||
let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
@ -1416,6 +1431,14 @@ function loadJSON(aFile) {
|
||||
} while (read != 0);
|
||||
}
|
||||
cstream.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw load of a JSON file
|
||||
*/
|
||||
function loadJSON(aFile) {
|
||||
let data = loadFile(aFile);
|
||||
do_print("Loaded JSON file " + aFile.path);
|
||||
return(JSON.parse(data));
|
||||
}
|
||||
|
66
toolkit/mozapps/extensions/test/xpcshell/test_XPIcancel.js
Normal file
66
toolkit/mozapps/extensions/test/xpcshell/test_XPIcancel.js
Normal file
@ -0,0 +1,66 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Test the cancellable doing/done/cancelAll API in XPIProvider
|
||||
|
||||
let scope = Components.utils.import("resource://gre/modules/XPIProvider.jsm");
|
||||
let XPIProvider = scope.XPIProvider;
|
||||
|
||||
function run_test() {
|
||||
// Check that cancelling with nothing in progress doesn't blow up
|
||||
XPIProvider.cancelAll();
|
||||
|
||||
// Check that a basic object gets cancelled
|
||||
let getsCancelled = {
|
||||
isCancelled: false,
|
||||
cancel: function () {
|
||||
if (this.isCancelled)
|
||||
do_throw("Already cancelled");
|
||||
this.isCancelled = true;
|
||||
}
|
||||
};
|
||||
XPIProvider.doing(getsCancelled);
|
||||
XPIProvider.cancelAll();
|
||||
do_check_true(getsCancelled.isCancelled);
|
||||
|
||||
// Check that if we complete a cancellable, it doesn't get cancelled
|
||||
let doesntGetCancelled = {
|
||||
cancel: () => do_throw("This should not have been cancelled")
|
||||
};
|
||||
XPIProvider.doing(doesntGetCancelled);
|
||||
do_check_true(XPIProvider.done(doesntGetCancelled));
|
||||
XPIProvider.cancelAll();
|
||||
|
||||
// A cancellable that adds a cancellable
|
||||
getsCancelled.isCancelled = false;
|
||||
let addsAnother = {
|
||||
isCancelled: false,
|
||||
cancel: function () {
|
||||
if (this.isCancelled)
|
||||
do_throw("Already cancelled");
|
||||
this.isCancelled = true;
|
||||
XPIProvider.doing(getsCancelled);
|
||||
}
|
||||
}
|
||||
XPIProvider.doing(addsAnother);
|
||||
XPIProvider.cancelAll();
|
||||
do_check_true(addsAnother.isCancelled);
|
||||
do_check_true(getsCancelled.isCancelled);
|
||||
|
||||
// A cancellable that removes another. This assumes that Set() iterates in the
|
||||
// order that members were added
|
||||
let removesAnother = {
|
||||
isCancelled: false,
|
||||
cancel: function () {
|
||||
if (this.isCancelled)
|
||||
do_throw("Already cancelled");
|
||||
this.isCancelled = true;
|
||||
XPIProvider.done(doesntGetCancelled);
|
||||
}
|
||||
}
|
||||
XPIProvider.doing(removesAnother);
|
||||
XPIProvider.doing(doesntGetCancelled);
|
||||
XPIProvider.cancelAll();
|
||||
do_check_true(removesAnother.isCancelled);
|
||||
}
|
149
toolkit/mozapps/extensions/test/xpcshell/test_updateCancel.js
Normal file
149
toolkit/mozapps/extensions/test/xpcshell/test_updateCancel.js
Normal file
@ -0,0 +1,149 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Test cancelling add-on update checks while in progress (bug 925389)
|
||||
|
||||
Components.utils.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
// The test extension uses an insecure update url.
|
||||
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
|
||||
Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||
|
||||
// Set up an HTTP server to respond to update requests
|
||||
Components.utils.import("resource://testing-common/httpd.js");
|
||||
|
||||
const profileDir = gProfD.clone();
|
||||
profileDir.append("extensions");
|
||||
|
||||
// Return a promise that resolves with an addon retrieved by
|
||||
// AddonManager.getAddonByID()
|
||||
function promiseGetAddon(aID) {
|
||||
let p = Promise.defer();
|
||||
AddonManager.getAddonByID(aID, p.resolve);
|
||||
return p.promise;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
// Kick off the task-based tests...
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// Install one extension
|
||||
// Start download of update check (but delay HTTP response)
|
||||
// Cancel update check
|
||||
// - ensure we get cancel notification
|
||||
// complete HTTP response
|
||||
// - ensure no callbacks after cancel
|
||||
// - ensure update is gone
|
||||
|
||||
// Create an addon update listener containing a promise
|
||||
// that resolves when the update is cancelled
|
||||
function makeCancelListener() {
|
||||
let updated = Promise.defer();
|
||||
return {
|
||||
onUpdateAvailable: function(addon, install) {
|
||||
updated.reject("Should not have seen onUpdateAvailable notification");
|
||||
},
|
||||
|
||||
onUpdateFinished: function(aAddon, aError) {
|
||||
do_print("onUpdateCheckFinished: " + aAddon.id + " " + aError);
|
||||
updated.resolve(aError);
|
||||
},
|
||||
promise: updated.promise
|
||||
};
|
||||
}
|
||||
|
||||
// Set up the HTTP server so that we can control when it responds
|
||||
let httpReceived = Promise.defer();
|
||||
function dataHandler(aRequest, aResponse) {
|
||||
asyncResponse = aResponse;
|
||||
aResponse.processAsync();
|
||||
httpReceived.resolve([aRequest, aResponse]);
|
||||
}
|
||||
var testserver = new HttpServer();
|
||||
testserver.registerDirectory("/addons/", do_get_file("addons"));
|
||||
testserver.registerPathHandler("/data/test_update.rdf", dataHandler);
|
||||
testserver.start(-1);
|
||||
gPort = testserver.identity.primaryPort;
|
||||
|
||||
// Set up an add-on for update check
|
||||
writeInstallRDFForExtension({
|
||||
id: "addon1@tests.mozilla.org",
|
||||
version: "1.0",
|
||||
updateURL: "http://localhost:" + gPort + "/data/test_update.rdf",
|
||||
targetApplications: [{
|
||||
id: "xpcshell@tests.mozilla.org",
|
||||
minVersion: "1",
|
||||
maxVersion: "1"
|
||||
}],
|
||||
name: "Test Addon 1",
|
||||
}, profileDir);
|
||||
|
||||
add_task(function cancel_during_check() {
|
||||
startupManager();
|
||||
|
||||
let a1 = yield promiseGetAddon("addon1@tests.mozilla.org");
|
||||
do_check_neq(a1, null);
|
||||
|
||||
let listener = makeCancelListener();
|
||||
a1.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
|
||||
|
||||
// Wait for the http request to arrive
|
||||
let [request, response] = yield httpReceived.promise;
|
||||
|
||||
// cancelUpdate returns true if there is an update check in progress
|
||||
do_check_true(a1.cancelUpdate());
|
||||
|
||||
let updateResult = yield listener.promise;
|
||||
do_check_eq(AddonManager.UPDATE_STATUS_CANCELLED, updateResult);
|
||||
|
||||
// Now complete the HTTP request
|
||||
let file = do_get_cwd();
|
||||
file.append("data");
|
||||
file.append("test_update.rdf");
|
||||
let data = loadFile(file);
|
||||
response.write(data);
|
||||
response.finish();
|
||||
|
||||
// trying to cancel again should return false, i.e. nothing to cancel
|
||||
do_check_false(a1.cancelUpdate());
|
||||
|
||||
yield true;
|
||||
});
|
||||
|
||||
// Test that update check is cancelled if the XPI provider shuts down while
|
||||
// the update check is in progress
|
||||
add_task(function shutdown_during_check() {
|
||||
// Reset our HTTP listener
|
||||
httpReceived = Promise.defer();
|
||||
|
||||
let a1 = yield promiseGetAddon("addon1@tests.mozilla.org");
|
||||
do_check_neq(a1, null);
|
||||
|
||||
let listener = makeCancelListener();
|
||||
a1.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
|
||||
|
||||
// Wait for the http request to arrive
|
||||
let [request, response] = yield httpReceived.promise;
|
||||
|
||||
shutdownManager();
|
||||
|
||||
let updateResult = yield listener.promise;
|
||||
do_check_eq(AddonManager.UPDATE_STATUS_CANCELLED, updateResult);
|
||||
|
||||
// Now complete the HTTP request
|
||||
let file = do_get_cwd();
|
||||
file.append("data");
|
||||
file.append("test_update.rdf");
|
||||
let data = loadFile(file);
|
||||
response.write(data);
|
||||
response.finish();
|
||||
|
||||
// trying to cancel again should return false, i.e. nothing to cancel
|
||||
do_check_false(a1.cancelUpdate());
|
||||
|
||||
yield testserver.stop(Promise.defer().resolve);
|
||||
});
|
@ -11,6 +11,7 @@ run-sequentially = Uses hardcoded ports in xpi files.
|
||||
skip-if = os == "android"
|
||||
[test_DeferredSave.js]
|
||||
[test_LightweightThemeManager.js]
|
||||
[test_XPIcancel.js]
|
||||
[test_backgroundupdate.js]
|
||||
[test_bad_json.js]
|
||||
[test_badschema.js]
|
||||
@ -230,6 +231,7 @@ fail-if = os == "android"
|
||||
[test_update.js]
|
||||
# Bug 676992: test consistently hangs on Android
|
||||
skip-if = os == "android"
|
||||
[test_updateCancel.js]
|
||||
[test_update_strictcompat.js]
|
||||
# Bug 676992: test consistently hangs on Android
|
||||
skip-if = os == "android"
|
||||
|
Loading…
Reference in New Issue
Block a user