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,
|
UPDATE_STATUS_UNKNOWN_FORMAT: -4,
|
||||||
// The update information was not correctly signed or there was an SSL error.
|
// The update information was not correctly signed or there was an SSL error.
|
||||||
UPDATE_STATUS_SECURITY_ERROR: -5,
|
UPDATE_STATUS_SECURITY_ERROR: -5,
|
||||||
|
// The update was cancelled.
|
||||||
|
UPDATE_STATUS_CANCELLED: -6,
|
||||||
|
|
||||||
// Constants to indicate why an update check is being performed
|
// Constants to indicate why an update check is being performed
|
||||||
// Update check has been requested by the user.
|
// Update check has been requested by the user.
|
||||||
|
@ -490,8 +490,14 @@ UpdateParser.prototype = {
|
|||||||
this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
|
this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ("onUpdateCheckComplete" in this.observer)
|
if ("onUpdateCheckComplete" in this.observer) {
|
||||||
this.observer.onUpdateCheckComplete(results);
|
try {
|
||||||
|
this.observer.onUpdateCheckComplete(results);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
WARN("onUpdateCheckComplete notification failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -534,8 +540,14 @@ UpdateParser.prototype = {
|
|||||||
* Helper method to notify the observer that an error occured.
|
* Helper method to notify the observer that an error occured.
|
||||||
*/
|
*/
|
||||||
notifyError: function UP_notifyError(aStatus) {
|
notifyError: function UP_notifyError(aStatus) {
|
||||||
if ("onUpdateCheckError" in this.observer)
|
if ("onUpdateCheckError" in this.observer) {
|
||||||
this.observer.onUpdateCheckError(aStatus);
|
try {
|
||||||
|
this.observer.onUpdateCheckError(aStatus);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
WARN("onUpdateCheckError notification failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -549,6 +561,17 @@ UpdateParser.prototype = {
|
|||||||
WARN("Request timed out");
|
WARN("Request timed out");
|
||||||
|
|
||||||
this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
|
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,
|
ERROR_UNKNOWN_FORMAT: -4,
|
||||||
// The update information was not correctly signed or there was an SSL error.
|
// The update information was not correctly signed or there was an SSL error.
|
||||||
ERROR_SECURITY_ERROR: -5,
|
ERROR_SECURITY_ERROR: -5,
|
||||||
|
// The update was cancelled
|
||||||
|
ERROR_CANCELLED: -6,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the best matching compatibility update for the application from
|
* 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
|
* The URL of the add-on's update manifest
|
||||||
* @param aObserver
|
* @param aObserver
|
||||||
* An observer to notify of results
|
* 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,
|
checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl,
|
||||||
aObserver) {
|
aObserver) {
|
||||||
new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
|
return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1683,6 +1683,24 @@ function directoryStateDiffers(aState, aCache)
|
|||||||
return false;
|
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 = {
|
var XPIProvider = {
|
||||||
// An array of known install locations
|
// An array of known install locations
|
||||||
@ -1734,6 +1752,32 @@ var XPIProvider = {
|
|||||||
this._telemetryDetails[aId][aName] = aValue;
|
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.
|
* Adds or updates a URI mapping for an Addon.id.
|
||||||
*
|
*
|
||||||
@ -2076,6 +2120,9 @@ var XPIProvider = {
|
|||||||
shutdown: function XPI_shutdown() {
|
shutdown: function XPI_shutdown() {
|
||||||
LOG("shutdown");
|
LOG("shutdown");
|
||||||
|
|
||||||
|
// Stop anything we were doing asynchronously
|
||||||
|
this.cancelAll();
|
||||||
|
|
||||||
this.bootstrappedAddons = {};
|
this.bootstrappedAddons = {};
|
||||||
this.bootstrapScopes = {};
|
this.bootstrapScopes = {};
|
||||||
this.enabledAddons = null;
|
this.enabledAddons = null;
|
||||||
@ -3308,7 +3355,7 @@ var XPIProvider = {
|
|||||||
locMigrateData = XPIDatabase.migrateData[installLocation.name];
|
locMigrateData = XPIDatabase.migrateData[installLocation.name];
|
||||||
for (let id in addonStates) {
|
for (let id in addonStates) {
|
||||||
changed = addMetadata(installLocation, id, addonStates[id],
|
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) {
|
getAddonByID: function XPI_getAddonByID(aId, aCallback) {
|
||||||
XPIDatabase.getVisibleAddonForID (aId, function getAddonByID_getVisibleAddonForID(aAddon) {
|
XPIDatabase.getVisibleAddonForID (aId, function getAddonByID_getVisibleAddonForID(aAddon) {
|
||||||
if (aAddon)
|
aCallback(createWrapper(aAddon));
|
||||||
aCallback(createWrapper(aAddon));
|
|
||||||
else
|
|
||||||
aCallback(null);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -3673,10 +3717,7 @@ var XPIProvider = {
|
|||||||
*/
|
*/
|
||||||
getAddonBySyncGUID: function XPI_getAddonBySyncGUID(aGUID, aCallback) {
|
getAddonBySyncGUID: function XPI_getAddonBySyncGUID(aGUID, aCallback) {
|
||||||
XPIDatabase.getAddonBySyncGUID(aGUID, function getAddonBySyncGUID_getAddonBySyncGUID(aAddon) {
|
XPIDatabase.getAddonBySyncGUID(aGUID, function getAddonBySyncGUID_getAddonBySyncGUID(aAddon) {
|
||||||
if (aAddon)
|
aCallback(createWrapper(aAddon));
|
||||||
aCallback(createWrapper(aAddon));
|
|
||||||
else
|
|
||||||
aCallback(null);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -4382,6 +4423,11 @@ var XPIProvider = {
|
|||||||
if ("_hasResourceCache" in aAddon)
|
if ("_hasResourceCache" in aAddon)
|
||||||
aAddon._hasResourceCache = new Map();
|
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
|
// Inactive add-ons don't require a restart to uninstall
|
||||||
let requiresRestart = this.uninstallRequiresRestart(aAddon);
|
let requiresRestart = this.uninstallRequiresRestart(aAddon);
|
||||||
|
|
||||||
@ -4631,6 +4677,7 @@ AddonInstall.prototype = {
|
|||||||
* The callback to pass the initialised AddonInstall to
|
* The callback to pass the initialised AddonInstall to
|
||||||
*/
|
*/
|
||||||
initLocalInstall: function AI_initLocalInstall(aCallback) {
|
initLocalInstall: function AI_initLocalInstall(aCallback) {
|
||||||
|
aCallback = makeSafe(aCallback);
|
||||||
this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
|
this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
|
||||||
if (!this.file.exists()) {
|
if (!this.file.exists()) {
|
||||||
@ -4746,7 +4793,7 @@ AddonInstall.prototype = {
|
|||||||
AddonManagerPrivate.callInstallListeners("onNewInstall", this.listeners,
|
AddonManagerPrivate.callInstallListeners("onNewInstall", this.listeners,
|
||||||
this.wrapper);
|
this.wrapper);
|
||||||
|
|
||||||
aCallback(this);
|
makeSafe(aCallback)(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -4946,7 +4993,7 @@ AddonInstall.prototype = {
|
|||||||
|
|
||||||
if (!addon) {
|
if (!addon) {
|
||||||
// No valid add-on was found
|
// No valid add-on was found
|
||||||
aCallback();
|
makeSafe(aCallback)();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4995,7 +5042,7 @@ AddonInstall.prototype = {
|
|||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
aCallback();
|
makeSafe(aCallback)();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -5009,6 +5056,7 @@ AddonInstall.prototype = {
|
|||||||
* XPI is incorrectly signed
|
* XPI is incorrectly signed
|
||||||
*/
|
*/
|
||||||
loadManifest: function AI_loadManifest(aCallback) {
|
loadManifest: function AI_loadManifest(aCallback) {
|
||||||
|
aCallback = makeSafe(aCallback);
|
||||||
let self = this;
|
let self = this;
|
||||||
function addRepositoryData(aAddon) {
|
function addRepositoryData(aAddon) {
|
||||||
// Try to load from the existing cache first
|
// Try to load from the existing cache first
|
||||||
@ -5680,7 +5728,7 @@ AddonInstall.createInstall = function AI_createInstall(aCallback, aFile) {
|
|||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
ERROR("Error creating install", 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");
|
Components.utils.import("resource://gre/modules/AddonUpdateChecker.jsm");
|
||||||
|
|
||||||
this.addon = aAddon;
|
this.addon = aAddon;
|
||||||
|
aAddon._updateCheck = this;
|
||||||
|
XPIProvider.doing(this);
|
||||||
this.listener = aListener;
|
this.listener = aListener;
|
||||||
this.appVersion = aAppVersion;
|
this.appVersion = aAppVersion;
|
||||||
this.platformVersion = aPlatformVersion;
|
this.platformVersion = aPlatformVersion;
|
||||||
@ -5842,8 +5892,8 @@ function UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion
|
|||||||
aReason |= UPDATE_TYPE_NEWVERSION;
|
aReason |= UPDATE_TYPE_NEWVERSION;
|
||||||
|
|
||||||
let url = escapeAddonURI(aAddon, updateURL, aReason, aAppVersion);
|
let url = escapeAddonURI(aAddon, updateURL, aReason, aAppVersion);
|
||||||
AddonUpdateChecker.checkForUpdates(aAddon.id, aAddon.updateKey,
|
this._parser = AddonUpdateChecker.checkForUpdates(aAddon.id, aAddon.updateKey,
|
||||||
url, this);
|
url, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateChecker.prototype = {
|
UpdateChecker.prototype = {
|
||||||
@ -5868,7 +5918,7 @@ UpdateChecker.prototype = {
|
|||||||
this.listener[aMethod].apply(this.listener, aArgs);
|
this.listener[aMethod].apply(this.listener, aArgs);
|
||||||
}
|
}
|
||||||
catch (e) {
|
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
|
* The list of update details for the add-on
|
||||||
*/
|
*/
|
||||||
onUpdateCheckComplete: function UC_onUpdateCheckComplete(aUpdates) {
|
onUpdateCheckComplete: function UC_onUpdateCheckComplete(aUpdates) {
|
||||||
|
XPIProvider.done(this.addon._updateCheck);
|
||||||
|
this.addon._updateCheck = null;
|
||||||
let AUC = AddonUpdateChecker;
|
let AUC = AddonUpdateChecker;
|
||||||
|
|
||||||
let ignoreMaxVersion = false;
|
let ignoreMaxVersion = false;
|
||||||
@ -5979,9 +6031,23 @@ UpdateChecker.prototype = {
|
|||||||
* An error status
|
* An error status
|
||||||
*/
|
*/
|
||||||
onUpdateCheckError: function UC_onUpdateCheckError(aError) {
|
onUpdateCheckError: function UC_onUpdateCheckError(aError) {
|
||||||
|
XPIProvider.done(this.addon._updateCheck);
|
||||||
|
this.addon._updateCheck = null;
|
||||||
this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
|
this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
|
||||||
this.callListener("onNoUpdateAvailable", createWrapper(this.addon));
|
this.callListener("onNoUpdateAvailable", createWrapper(this.addon));
|
||||||
this.callListener("onUpdateFinished", createWrapper(this.addon), aError);
|
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);
|
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) {
|
this.hasResource = function AddonWrapper_hasResource(aPath) {
|
||||||
if (aAddon._hasResourceCache.has(aPath))
|
if (aAddon._hasResourceCache.has(aPath))
|
||||||
return aAddon._hasResourceCache.get(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
|
* Wrap an API-supplied function in an exception handler to make it safe to call
|
||||||
*/
|
*/
|
||||||
function safeCallback(aCallback) {
|
function makeSafe(aCallback) {
|
||||||
return function(...aArgs) {
|
return function(...aArgs) {
|
||||||
try {
|
try {
|
||||||
aCallback.apply(null, aArgs);
|
aCallback(...aArgs);
|
||||||
}
|
}
|
||||||
catch(ex) {
|
catch(ex) {
|
||||||
WARN("XPI Database callback failed", ex);
|
WARN("XPI Database callback failed", ex);
|
||||||
@ -1057,12 +1057,12 @@ this.XPIDatabase = {
|
|||||||
this.asyncLoadDB().then(
|
this.asyncLoadDB().then(
|
||||||
addonDB => {
|
addonDB => {
|
||||||
let addonList = _filterDB(addonDB, aFilter);
|
let addonList = _filterDB(addonDB, aFilter);
|
||||||
asyncMap(addonList, getRepositoryAddon, safeCallback(aCallback));
|
asyncMap(addonList, getRepositoryAddon, makeSafe(aCallback));
|
||||||
})
|
})
|
||||||
.then(null,
|
.then(null,
|
||||||
error => {
|
error => {
|
||||||
ERROR("getAddonList failed", e);
|
ERROR("getAddonList failed", e);
|
||||||
safeCallback(aCallback)([]);
|
makeSafe(aCallback)([]);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1077,12 +1077,12 @@ this.XPIDatabase = {
|
|||||||
getAddon: function(aFilter, aCallback) {
|
getAddon: function(aFilter, aCallback) {
|
||||||
return this.asyncLoadDB().then(
|
return this.asyncLoadDB().then(
|
||||||
addonDB => {
|
addonDB => {
|
||||||
getRepositoryAddon(_findAddon(addonDB, aFilter), safeCallback(aCallback));
|
getRepositoryAddon(_findAddon(addonDB, aFilter), makeSafe(aCallback));
|
||||||
})
|
})
|
||||||
.then(null,
|
.then(null,
|
||||||
error => {
|
error => {
|
||||||
ERROR("getAddon failed", e);
|
ERROR("getAddon failed", e);
|
||||||
safeCallback(aCallback)(null);
|
makeSafe(aCallback)(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1112,7 +1112,7 @@ this.XPIDatabase = {
|
|||||||
getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
|
getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
|
||||||
this.asyncLoadDB().then(
|
this.asyncLoadDB().then(
|
||||||
addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId),
|
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
|
// Forcibly end the test if it runs longer than 15 minutes
|
||||||
const TIMEOUT_MS = 900000;
|
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/AddonRepository.jsm");
|
||||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
||||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||||
Components.utils.import("resource://gre/modules/NetUtil.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 gInternalManager = null;
|
||||||
var gAppInfo = null;
|
var gAppInfo = null;
|
||||||
var gAddonsList;
|
var gAddonsList;
|
||||||
@ -403,11 +419,16 @@ function shutdownManager() {
|
|||||||
let shutdownDone = false;
|
let shutdownDone = false;
|
||||||
|
|
||||||
Services.obs.notifyObservers(null, "quit-application-granted", null);
|
Services.obs.notifyObservers(null, "quit-application-granted", null);
|
||||||
let scope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
MockAsyncShutdown.hook().then(
|
||||||
scope.AddonManagerInternal.shutdown()
|
() => shutdownDone = true,
|
||||||
.then(
|
err => shutdownDone = true);
|
||||||
() => shutdownDone = true,
|
|
||||||
err => shutdownDone = true);
|
let thr = Services.tm.mainThread;
|
||||||
|
|
||||||
|
// Wait until we observe the shutdown notifications
|
||||||
|
while (!shutdownDone) {
|
||||||
|
thr.processNextEvent(true);
|
||||||
|
}
|
||||||
|
|
||||||
gInternalManager = null;
|
gInternalManager = null;
|
||||||
|
|
||||||
@ -417,20 +438,13 @@ function shutdownManager() {
|
|||||||
// Clear any crash report annotations
|
// Clear any crash report annotations
|
||||||
gAppInfo.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
|
// Force the XPIProvider provider to reload to better
|
||||||
// simulate real-world usage.
|
// 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
|
// This would be cleaner if I could get it as the rejection reason from
|
||||||
// the AddonManagerInternal.shutdown() promise
|
// the AddonManagerInternal.shutdown() promise
|
||||||
gXPISaveError = scope.XPIProvider._shutdownError;
|
gXPISaveError = XPIscope.XPIProvider._shutdownError;
|
||||||
AddonManagerPrivate.unregisterProvider(scope.XPIProvider);
|
AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
|
||||||
Components.utils.unload("resource://gre/modules/XPIProvider.jsm");
|
Components.utils.unload("resource://gre/modules/XPIProvider.jsm");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1090,16 +1104,24 @@ if ("nsIWindowsRegKey" in AM_Ci) {
|
|||||||
var MockRegistry = {
|
var MockRegistry = {
|
||||||
LOCAL_MACHINE: {},
|
LOCAL_MACHINE: {},
|
||||||
CURRENT_USER: {},
|
CURRENT_USER: {},
|
||||||
|
CLASSES_ROOT: {},
|
||||||
|
|
||||||
setValue: function(aRoot, aPath, aName, aValue) {
|
getRoot: function(aRoot) {
|
||||||
switch (aRoot) {
|
switch (aRoot) {
|
||||||
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE:
|
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE:
|
||||||
var rootKey = MockRegistry.LOCAL_MACHINE;
|
return MockRegistry.LOCAL_MACHINE;
|
||||||
break
|
|
||||||
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER:
|
case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER:
|
||||||
rootKey = MockRegistry.CURRENT_USER;
|
return MockRegistry.CURRENT_USER;
|
||||||
break
|
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)) {
|
if (!(aPath in rootKey)) {
|
||||||
rootKey[aPath] = [];
|
rootKey[aPath] = [];
|
||||||
@ -1141,14 +1163,7 @@ if ("nsIWindowsRegKey" in AM_Ci) {
|
|||||||
|
|
||||||
// --- Overridden nsIWindowsRegKey interface functions ---
|
// --- Overridden nsIWindowsRegKey interface functions ---
|
||||||
open: function(aRootKey, aRelPath, aMode) {
|
open: function(aRootKey, aRelPath, aMode) {
|
||||||
switch (aRootKey) {
|
let rootKey = MockRegistry.getRoot(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
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(aRelPath in rootKey))
|
if (!(aRelPath in rootKey))
|
||||||
rootKey[aRelPath] = [];
|
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 data = "";
|
||||||
let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
||||||
createInstance(Components.interfaces.nsIFileInputStream);
|
createInstance(Components.interfaces.nsIFileInputStream);
|
||||||
@ -1416,6 +1431,14 @@ function loadJSON(aFile) {
|
|||||||
} while (read != 0);
|
} while (read != 0);
|
||||||
}
|
}
|
||||||
cstream.close();
|
cstream.close();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw load of a JSON file
|
||||||
|
*/
|
||||||
|
function loadJSON(aFile) {
|
||||||
|
let data = loadFile(aFile);
|
||||||
do_print("Loaded JSON file " + aFile.path);
|
do_print("Loaded JSON file " + aFile.path);
|
||||||
return(JSON.parse(data));
|
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"
|
skip-if = os == "android"
|
||||||
[test_DeferredSave.js]
|
[test_DeferredSave.js]
|
||||||
[test_LightweightThemeManager.js]
|
[test_LightweightThemeManager.js]
|
||||||
|
[test_XPIcancel.js]
|
||||||
[test_backgroundupdate.js]
|
[test_backgroundupdate.js]
|
||||||
[test_bad_json.js]
|
[test_bad_json.js]
|
||||||
[test_badschema.js]
|
[test_badschema.js]
|
||||||
@ -230,6 +231,7 @@ fail-if = os == "android"
|
|||||||
[test_update.js]
|
[test_update.js]
|
||||||
# Bug 676992: test consistently hangs on Android
|
# Bug 676992: test consistently hangs on Android
|
||||||
skip-if = os == "android"
|
skip-if = os == "android"
|
||||||
|
[test_updateCancel.js]
|
||||||
[test_update_strictcompat.js]
|
[test_update_strictcompat.js]
|
||||||
# Bug 676992: test consistently hangs on Android
|
# Bug 676992: test consistently hangs on Android
|
||||||
skip-if = os == "android"
|
skip-if = os == "android"
|
||||||
|
Loading…
Reference in New Issue
Block a user