зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1255040 - Implement mozAddonManager.createInstall(). r=rhelmer
MozReview-Commit-ID: JLrhGywROzt --HG-- extra : transplant_source : h%01%A8%D1%89%C0IO%1E%879C%01%25%ECW%1Dg%1D%7C
This commit is contained in:
Родитель
5f6c846326
Коммит
dc4b339b75
|
@ -2784,13 +2784,110 @@ var AddonManagerInternal = {
|
|||
},
|
||||
|
||||
webAPI: {
|
||||
getAddonByID(id) {
|
||||
// installs maps integer ids to AddonInstall instances.
|
||||
installs: new Map(),
|
||||
nextInstall: 0,
|
||||
|
||||
sendEvent: null,
|
||||
setEventHandler(fn) {
|
||||
this.sendEvent = fn;
|
||||
},
|
||||
|
||||
getAddonByID(target, id) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getAddonByID(id, (addon) => {
|
||||
resolve(webAPIForAddon(addon));
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// helper to copy (and convert) the properties we care about
|
||||
copyProps(install, obj) {
|
||||
obj.state = AddonManager.stateToString(install.state);
|
||||
obj.error = AddonManager.errorToString(install.error);
|
||||
obj.progress = install.progress;
|
||||
obj.maxProgress = install.maxProgress;
|
||||
},
|
||||
|
||||
makeListener(id, target) {
|
||||
const events = [
|
||||
"onDownloadStarted",
|
||||
"onDownloadProgress",
|
||||
"onDownloadEnded",
|
||||
"onDownloadCancelled",
|
||||
"onDownloadFailed",
|
||||
"onInstallStarted",
|
||||
"onInstallEnded",
|
||||
"onInstallCancelled",
|
||||
"onInstallFailed",
|
||||
];
|
||||
|
||||
let listener = {};
|
||||
events.forEach(event => {
|
||||
listener[event] = (install) => {
|
||||
let data = {event, id};
|
||||
AddonManager.webAPI.copyProps(install, data);
|
||||
this.sendEvent(target, data);
|
||||
}
|
||||
});
|
||||
return listener;
|
||||
},
|
||||
|
||||
forgetInstall(id) {
|
||||
let info = this.installs.get(id);
|
||||
if (!info) {
|
||||
throw new Error(`forgetInstall cannot find ${id}`);
|
||||
}
|
||||
info.install.removeListener(info.listener);
|
||||
this.installs.delete(id);
|
||||
},
|
||||
|
||||
createInstall(target, options) {
|
||||
return new Promise((resolve) => {
|
||||
let newInstall = install => {
|
||||
let id = this.nextInstall++;
|
||||
let listener = this.makeListener(id, target);
|
||||
install.addListener(listener);
|
||||
|
||||
this.installs.set(id, {install, target, listener});
|
||||
|
||||
let result = {id};
|
||||
this.copyProps(install, result);
|
||||
resolve(result);
|
||||
};
|
||||
AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall");
|
||||
});
|
||||
},
|
||||
|
||||
addonInstallDoInstall(target, id) {
|
||||
let state = this.installs.get(id);
|
||||
if (!state) {
|
||||
return Promise.reject(`invalid id ${id}`);
|
||||
}
|
||||
return Promise.resolve(state.install.install());
|
||||
},
|
||||
|
||||
addonInstallCancel(target, id) {
|
||||
let state = this.installs.get(id);
|
||||
if (!state) {
|
||||
return Promise.reject(`invalid id ${id}`);
|
||||
}
|
||||
return Promise.resolve(state.install.cancel());
|
||||
},
|
||||
|
||||
clearInstalls(ids) {
|
||||
for (let id of ids) {
|
||||
this.forgetInstall(id);
|
||||
}
|
||||
},
|
||||
|
||||
clearInstallsFrom(mm) {
|
||||
for (let [id, info] of this.installs) {
|
||||
if (info.target == mm) {
|
||||
this.forgetInstall(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -2951,39 +3048,45 @@ this.AddonManagerPrivate = {
|
|||
*/
|
||||
this.AddonManager = {
|
||||
// Constants for the AddonInstall.state property
|
||||
// The install is available for download.
|
||||
STATE_AVAILABLE: 0,
|
||||
// The install is being downloaded.
|
||||
STATE_DOWNLOADING: 1,
|
||||
// The install is checking for compatibility information.
|
||||
STATE_CHECKING: 2,
|
||||
// The install is downloaded and ready to install.
|
||||
STATE_DOWNLOADED: 3,
|
||||
// The download failed.
|
||||
STATE_DOWNLOAD_FAILED: 4,
|
||||
// The add-on is being installed.
|
||||
STATE_INSTALLING: 5,
|
||||
// The add-on has been installed.
|
||||
STATE_INSTALLED: 6,
|
||||
// The install failed.
|
||||
STATE_INSTALL_FAILED: 7,
|
||||
// The install has been cancelled.
|
||||
STATE_CANCELLED: 8,
|
||||
// These will show up as AddonManager.STATE_* (eg, STATE_AVAILABLE)
|
||||
_states: new Map([
|
||||
// The install is available for download.
|
||||
["STATE_AVAILABLE", 0],
|
||||
// The install is being downloaded.
|
||||
["STATE_DOWNLOADING", 1],
|
||||
// The install is checking for compatibility information.
|
||||
["STATE_CHECKING", 2],
|
||||
// The install is downloaded and ready to install.
|
||||
["STATE_DOWNLOADED", 3],
|
||||
// The download failed.
|
||||
["STATE_DOWNLOAD_FAILED", 4],
|
||||
// The add-on is being installed.
|
||||
["STATE_INSTALLING", 5],
|
||||
// The add-on has been installed.
|
||||
["STATE_INSTALLED", 6],
|
||||
// The install failed.
|
||||
["STATE_INSTALL_FAILED", 7],
|
||||
// The install has been cancelled.
|
||||
["STATE_CANCELLED", 8],
|
||||
]),
|
||||
|
||||
// Constants representing different types of errors while downloading an
|
||||
// add-on.
|
||||
// The download failed due to network problems.
|
||||
ERROR_NETWORK_FAILURE: -1,
|
||||
// The downloaded file did not match the provided hash.
|
||||
ERROR_INCORRECT_HASH: -2,
|
||||
// The downloaded file seems to be corrupted in some way.
|
||||
ERROR_CORRUPT_FILE: -3,
|
||||
// An error occured trying to write to the filesystem.
|
||||
ERROR_FILE_ACCESS: -4,
|
||||
// The add-on must be signed and isn't.
|
||||
ERROR_SIGNEDSTATE_REQUIRED: -5,
|
||||
// The downloaded add-on had a different type than expected.
|
||||
ERROR_UNEXPECTED_ADDON_TYPE: -6,
|
||||
// These will show up as AddonManager.ERROR_* (eg, ERROR_NETWORK_FAILURE)
|
||||
_errors: new Map([
|
||||
// The download failed due to network problems.
|
||||
["ERROR_NETWORK_FAILURE", -1],
|
||||
// The downloaded file did not match the provided hash.
|
||||
["ERROR_INCORRECT_HASH", -2],
|
||||
// The downloaded file seems to be corrupted in some way.
|
||||
["ERROR_CORRUPT_FILE", -3],
|
||||
// An error occured trying to write to the filesystem.
|
||||
["ERROR_FILE_ACCESS", -4],
|
||||
// The add-on must be signed and isn't.
|
||||
["ERROR_SIGNEDSTATE_REQUIRED", -5],
|
||||
// The downloaded add-on had a different type than expected.
|
||||
["ERROR_UNEXPECTED_ADDON_TYPE", -6],
|
||||
]),
|
||||
|
||||
// These must be kept in sync with AddonUpdateChecker.
|
||||
// No error was encountered.
|
||||
|
@ -3165,6 +3268,27 @@ this.AddonManager = {
|
|||
return gStartupComplete && !gShutdownInProgress;
|
||||
},
|
||||
|
||||
init() {
|
||||
this._stateToString = new Map();
|
||||
for (let [name, value] of this._states) {
|
||||
this[name] = value;
|
||||
this._stateToString.set(value, name);
|
||||
}
|
||||
this._errorToString = new Map();
|
||||
for (let [name, value] of this._errors) {
|
||||
this[name] = value;
|
||||
this._errorToString.set(value, name);
|
||||
}
|
||||
},
|
||||
|
||||
stateToString(state) {
|
||||
return this._stateToString.get(state);
|
||||
},
|
||||
|
||||
errorToString(err) {
|
||||
return err ? this._errorToString.get(err) : null;
|
||||
},
|
||||
|
||||
getInstallForURL: function(aUrl, aCallback, aMimetype,
|
||||
aHash, aName, aIcons,
|
||||
aVersion, aBrowser) {
|
||||
|
@ -3382,6 +3506,8 @@ this.AddonManager = {
|
|||
},
|
||||
};
|
||||
|
||||
this.AddonManager.init();
|
||||
|
||||
// load the timestamps module into AddonManagerInternal
|
||||
Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal);
|
||||
Object.freeze(AddonManagerInternal);
|
||||
|
|
|
@ -26,6 +26,8 @@ const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
|
|||
|
||||
const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
|
||||
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
|
||||
const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
|
||||
const MSG_INSTALL_CLEANUP = "WebAPICleanup";
|
||||
|
||||
const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";
|
||||
|
||||
|
@ -48,6 +50,12 @@ function amManager() {
|
|||
gParentMM = Services.ppmm;
|
||||
gParentMM.addMessageListener(MSG_INSTALL_ENABLED, this);
|
||||
gParentMM.addMessageListener(MSG_PROMISE_REQUEST, this);
|
||||
gParentMM.addMessageListener(MSG_INSTALL_CLEANUP, this);
|
||||
|
||||
Services.obs.addObserver(this, "message-manager-close", false);
|
||||
Services.obs.addObserver(this, "message-manager-disconnect", false);
|
||||
|
||||
AddonManager.webAPI.setEventHandler(this.sendEvent);
|
||||
|
||||
// Needed so receiveMessage can be called directly by JS callers
|
||||
this.wrappedJSObject = this;
|
||||
|
@ -55,8 +63,16 @@ function amManager() {
|
|||
|
||||
amManager.prototype = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic == "addons-startup")
|
||||
AddonManagerPrivate.startup();
|
||||
switch (aTopic) {
|
||||
case "addons-startup":
|
||||
AddonManagerPrivate.startup();
|
||||
break;
|
||||
|
||||
case "message-manager-close":
|
||||
case "message-manager-disconnect":
|
||||
AddonManager.webAPI.clearInstallsFrom(aSubject);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -193,17 +209,26 @@ amManager.prototype = {
|
|||
|
||||
let API = AddonManager.webAPI;
|
||||
if (payload.type in API) {
|
||||
API[payload.type](...payload.args).then(resolve, reject);
|
||||
API[payload.type](aMessage.target, ...payload.args).then(resolve, reject);
|
||||
}
|
||||
else {
|
||||
reject("Unknown Add-on API request.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_INSTALL_CLEANUP: {
|
||||
AddonManager.webAPI.clearInstalls(payload.ids);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
sendEvent(target, data) {
|
||||
target.sendAsyncMessage(MSG_INSTALL_EVENT, data);
|
||||
},
|
||||
|
||||
classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
|
||||
_xpcom_factory: {
|
||||
createInstance: function(aOuter, aIid) {
|
||||
|
|
|
@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/Task.jsm");
|
|||
|
||||
const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
|
||||
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
|
||||
const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
|
||||
const MSG_INSTALL_CLEANUP = "WebAPICleanup";
|
||||
|
||||
const APIBroker = {
|
||||
_nextID: 0,
|
||||
|
@ -19,7 +21,11 @@ const APIBroker = {
|
|||
init() {
|
||||
this._promises = new Map();
|
||||
|
||||
// _installMap maps integer ids to DOM AddonInstall instances
|
||||
this._installMap = new Map();
|
||||
|
||||
Services.cpmm.addMessageListener(MSG_PROMISE_RESULT, this);
|
||||
Services.cpmm.addMessageListener(MSG_INSTALL_EVENT, this);
|
||||
},
|
||||
|
||||
receiveMessage(message) {
|
||||
|
@ -40,6 +46,17 @@ const APIBroker = {
|
|||
reject(payload.reject);
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_INSTALL_EVENT: {
|
||||
let install = this._installMap.get(payload.id);
|
||||
if (!install) {
|
||||
let err = new Error(`Got install event for unknown install ${payload.id}`);
|
||||
Cu.reportError(err);
|
||||
return;
|
||||
}
|
||||
install._dispatch(payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -51,6 +68,10 @@ const APIBroker = {
|
|||
Services.cpmm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args });
|
||||
});
|
||||
},
|
||||
|
||||
sendCleanup: function(ids) {
|
||||
Services.cpmm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
|
||||
},
|
||||
};
|
||||
|
||||
APIBroker.init();
|
||||
|
@ -67,6 +88,18 @@ function Addon(win, properties) {
|
|||
};
|
||||
}
|
||||
|
||||
function AddonInstall(window, properties) {
|
||||
let id = properties.id;
|
||||
APIBroker._installMap.set(id, this);
|
||||
|
||||
this.window = window;
|
||||
this.handlers = new Map();
|
||||
|
||||
for (let key of Object.keys(properties)) {
|
||||
this[key] = properties[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API methods should return promises from the page, this is a simple wrapper
|
||||
* to make sure of that. It also automatically wraps objects when necessary.
|
||||
|
@ -81,6 +114,9 @@ function WebAPITask(generator) {
|
|||
if (obj instanceof Addon) {
|
||||
return win.Addon._create(win, obj);
|
||||
}
|
||||
if (obj instanceof AddonInstall) {
|
||||
return win.AddonInstall._create(win, obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@ -92,12 +128,50 @@ function WebAPITask(generator) {
|
|||
}
|
||||
}
|
||||
|
||||
const INSTALL_EVENTS = [
|
||||
"onDownloadStarted",
|
||||
"onDownloadProgress",
|
||||
"onDownloadEnded",
|
||||
"onDownloadCancelled",
|
||||
"onDownloadFailed",
|
||||
"onInstallStarted",
|
||||
"onInstallEnded",
|
||||
"onInstallCancelled",
|
||||
"onInstallFailed",
|
||||
];
|
||||
|
||||
AddonInstall.prototype = {
|
||||
_dispatch(data) {
|
||||
// The message for the event includes updated copies of all install
|
||||
// properties. Use the usual "let webidl filter visible properties" trick.
|
||||
for (let key of Object.keys(data)) {
|
||||
this[key] = data[key];
|
||||
}
|
||||
|
||||
let event = new this.window.Event(data.event);
|
||||
this.__DOM_IMPL__.dispatchEvent(event);
|
||||
},
|
||||
|
||||
install: WebAPITask(function*() {
|
||||
yield APIBroker.sendRequest("addonInstallDoInstall", this.id);
|
||||
}),
|
||||
|
||||
cancel: WebAPITask(function*() {
|
||||
yield APIBroker.sendRequest("addonInstallCancel", this.id);
|
||||
}),
|
||||
};
|
||||
|
||||
function WebAPI() {
|
||||
}
|
||||
|
||||
WebAPI.prototype = {
|
||||
init(window) {
|
||||
this.window = window;
|
||||
this.allInstalls = [];
|
||||
|
||||
window.addEventListener("unload", event => {
|
||||
APIBroker.sendCleanup(this.allInstalls);
|
||||
});
|
||||
},
|
||||
|
||||
getAddonByID: WebAPITask(function*(id) {
|
||||
|
@ -105,10 +179,15 @@ WebAPI.prototype = {
|
|||
return addonInfo ? new Addon(this.window, addonInfo) : null;
|
||||
}),
|
||||
|
||||
createInstall() {
|
||||
let err = new this.window.Error("not yet implemented");
|
||||
return this.window.Promise.reject(err);
|
||||
},
|
||||
createInstall: WebAPITask(function*(options) {
|
||||
let installInfo = yield APIBroker.sendRequest("createInstall", options);
|
||||
if (!installInfo) {
|
||||
return null;
|
||||
}
|
||||
let install = new AddonInstall(this.window, installInfo);
|
||||
this.allInstalls.push(installInfo.id);
|
||||
return install;
|
||||
}),
|
||||
|
||||
classID: Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"),
|
||||
contractID: "@mozilla.org/addon-web-api/manager;1",
|
||||
|
|
Двоичные данные
toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install.xpi
Normal file
Двоичные данные
toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install.xpi
Normal file
Двоичный файл не отображается.
9
toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install/bootstrap.js
поставляемый
Normal file
9
toolkit/mozapps/extensions/test/browser/addons/browser_webapi_install/bootstrap.js
поставляемый
Normal file
|
@ -0,0 +1,9 @@
|
|||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function startup(data, reason) {
|
||||
Services.prefs.setIntPref("webapitest.active_version", 1);
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
Services.prefs.setIntPref("webapitest.active_version", 0);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>webapi_install@tests.mozilla.org</em:id>
|
||||
<em:version>1.1</em:version>
|
||||
<em:name>AddonManger web API test</em:name>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>0.3</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
</Description>
|
||||
</RDF>
|
|
@ -66,5 +66,6 @@ skip-if = require_signing
|
|||
[browser_update.js]
|
||||
[browser_webapi.js]
|
||||
[browser_webapi_access.js]
|
||||
[browser_webapi_install.js]
|
||||
|
||||
[include:browser-common.ini]
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
|
||||
const XPI_URL = `${SECURE_TESTROOT}addons/browser_webapi_install.xpi`;
|
||||
|
||||
const ID = "webapi_install@tests.mozilla.org";
|
||||
// eh, would be good to just stat the real file instead of this...
|
||||
const XPI_LEN = 4782;
|
||||
|
||||
Services.prefs.setBoolPref("extensions.webapi.testing", true);
|
||||
Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("extensions.webapi.testing");
|
||||
Services.prefs.clearUserPref("extensions.install.requireBuiltInCerts");
|
||||
});
|
||||
|
||||
function waitForClear() {
|
||||
const MSG = "WebAPICleanup";
|
||||
return new Promise(resolve => {
|
||||
let listener = {
|
||||
receiveMessage: function(msg) {
|
||||
if (msg.name == MSG) {
|
||||
Services.ppmm.removeMessageListener(MSG, listener);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Services.ppmm.addMessageListener(MSG, listener);
|
||||
});
|
||||
}
|
||||
|
||||
// Wrapper around a common task to run in the content process to test
|
||||
// the mozAddonManager API. Takes a URL for the XPI to install and an
|
||||
// array of steps, each of which can either be an action to take
|
||||
// (i.e., start or cancel the install) or an install event to wait for.
|
||||
// Steps that look for a specific event may also include a "props" property
|
||||
// with properties that the AddonInstall object is expected to have when
|
||||
// that event is triggered.
|
||||
function* testInstall(browser, url, steps, description) {
|
||||
let success = yield ContentTask.spawn(browser, {url, steps}, function* (opts) {
|
||||
let { url, steps } = opts;
|
||||
let install = yield content.navigator.mozAddonManager.createInstall({url});
|
||||
if (!install) {
|
||||
yield Promise.reject("createInstall() did not return an install object");
|
||||
}
|
||||
|
||||
// Check that the initial state of the AddonInstall is sane.
|
||||
if (install.state != "STATE_AVAILABLE") {
|
||||
yield Promise.reject("new install should be in STATE_AVAILABLE");
|
||||
}
|
||||
if (install.error != null) {
|
||||
yield Promise.reject("new install should have null error");
|
||||
}
|
||||
|
||||
const events = [
|
||||
"onDownloadStarted",
|
||||
"onDownloadProgress",
|
||||
"onDownloadEnded",
|
||||
"onDownloadCancelled",
|
||||
"onDownloadFailed",
|
||||
"onInstallStarted",
|
||||
"onInstallEnded",
|
||||
"onInstallCancelled",
|
||||
"onInstallFailed",
|
||||
];
|
||||
let eventWaiter = null;
|
||||
let receivedEvents = [];
|
||||
events.forEach(event => {
|
||||
install.addEventListener(event, e => {
|
||||
receivedEvents.push({
|
||||
event,
|
||||
state: install.state,
|
||||
error: install.error,
|
||||
progress: install.progress,
|
||||
maxProgress: install.maxProgress,
|
||||
});
|
||||
if (eventWaiter) {
|
||||
eventWaiter();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Returns a promise that is resolved when the given event occurs
|
||||
// or rejects if a different event comes first or if props is supplied
|
||||
// and properties on the AddonInstall don't match those in props.
|
||||
function expectEvent(event, props) {
|
||||
return new Promise((resolve, reject) => {
|
||||
function check() {
|
||||
let received = receivedEvents.shift();
|
||||
if (received.event != event) {
|
||||
let err = new Error(`expected ${event} but got ${received.event}`);
|
||||
reject(err);
|
||||
}
|
||||
if (props) {
|
||||
for (let key of Object.keys(props)) {
|
||||
if (received[key] != props[key]) {
|
||||
throw new Error(`AddonInstall property ${key} was ${received[key]} but expected ${props[key]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
if (receivedEvents.length > 0) {
|
||||
check();
|
||||
} else {
|
||||
eventWaiter = () => {
|
||||
eventWaiter = null;
|
||||
check();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
while (steps.length > 0) {
|
||||
let nextStep = steps.shift();
|
||||
if (nextStep.action) {
|
||||
if (nextStep.action == "install") {
|
||||
yield install.install();
|
||||
} else if (nextStep.action == "cancel") {
|
||||
yield install.cancel();
|
||||
} else {
|
||||
throw new Error(`unknown action ${nextStep.action}`);
|
||||
}
|
||||
} else {
|
||||
yield expectEvent(nextStep.event, nextStep.props);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
is(success, true, description);
|
||||
}
|
||||
|
||||
function makeInstallTest(task) {
|
||||
return function*() {
|
||||
// withNewTab() will close the test tab before returning, at which point
|
||||
// the cleanup event will come from the content process. We need to see
|
||||
// that event but don't want to race to install a listener for it after
|
||||
// the tab is closed. So set up the listener now but don't yield the
|
||||
// listening promise until below.
|
||||
let clearPromise = waitForClear();
|
||||
|
||||
yield BrowserTestUtils.withNewTab(TESTPAGE, task);
|
||||
|
||||
yield clearPromise;
|
||||
is(AddonManager.webAPI.installs.size, 0, "AddonInstall was cleaned up");
|
||||
};
|
||||
}
|
||||
|
||||
// Check the happy path for installing an add-on using the mozAddonManager API.
|
||||
add_task(makeInstallTest(function* (browser) {
|
||||
let steps = [
|
||||
{action: "install"},
|
||||
{
|
||||
event: "onDownloadStarted",
|
||||
props: {state: "STATE_DOWNLOADING"},
|
||||
},
|
||||
{
|
||||
event: "onDownloadProgress",
|
||||
props: {maxProgress: XPI_LEN},
|
||||
},
|
||||
{
|
||||
event: "onDownloadEnded",
|
||||
props: {
|
||||
state: "STATE_DOWNLOADED",
|
||||
progress: XPI_LEN,
|
||||
maxProgress: XPI_LEN,
|
||||
},
|
||||
},
|
||||
{
|
||||
event: "onInstallStarted",
|
||||
props: {state: "STATE_INSTALLING"},
|
||||
},
|
||||
{
|
||||
event: "onInstallEnded",
|
||||
props: {state: "STATE_INSTALLED"},
|
||||
},
|
||||
];
|
||||
|
||||
yield testInstall(browser, XPI_URL, steps, "a basic install works");
|
||||
|
||||
let version = Services.prefs.getIntPref("webapitest.active_version");
|
||||
is(version, 1, "the install really did work");
|
||||
|
||||
// Sanity check to ensure that the test in makeInstallTest() that
|
||||
// installs.size == 0 means we actually did clean up.
|
||||
ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
|
||||
|
||||
let addons = yield promiseAddonsByIDs([ID]);
|
||||
isnot(addons[0], null, "Found the addon");
|
||||
|
||||
yield addons[0].uninstall();
|
||||
|
||||
addons = yield promiseAddonsByIDs([ID]);
|
||||
is(addons[0], null, "Addon was uninstalled");
|
||||
}));
|
||||
|
||||
add_task(makeInstallTest(function* (browser) {
|
||||
let steps = [
|
||||
{action: "cancel"},
|
||||
{
|
||||
event: "onDownloadCancelled",
|
||||
props: {
|
||||
state: "STATE_CANCELLED",
|
||||
error: null,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
yield testInstall(browser, XPI_URL, steps, "canceling an install works");
|
||||
|
||||
let addons = yield promiseAddonsByIDs([ID]);
|
||||
is(addons[0], null, "The addon was not installed");
|
||||
|
||||
ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
|
||||
}));
|
||||
|
||||
add_task(makeInstallTest(function* (browser) {
|
||||
let steps = [
|
||||
{action: "install"},
|
||||
{
|
||||
event: "onDownloadStarted",
|
||||
props: {state: "STATE_DOWNLOADING"},
|
||||
},
|
||||
{event: "onDownloadProgress"},
|
||||
{
|
||||
event: "onDownloadFailed",
|
||||
props: {
|
||||
state: "STATE_DOWNLOAD_FAILED",
|
||||
error: "ERROR_NETWORK_FAILURE",
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
yield testInstall(browser, XPI_URL + "bogus", steps, "a basic install works");
|
||||
|
||||
let addons = yield promiseAddonsByIDs([ID]);
|
||||
is(addons[0], null, "The addon was not installed");
|
||||
|
||||
ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
|
||||
}));
|
||||
|
|
@ -10,7 +10,8 @@ const IGNORE = ["getPreferredIconURL", "escapeAddonURI",
|
|||
"addAddonListener", "removeAddonListener",
|
||||
"addInstallListener", "removeInstallListener",
|
||||
"addManagerListener", "removeManagerListener",
|
||||
"mapURIToAddonID", "shutdown"];
|
||||
"mapURIToAddonID", "shutdown", "init",
|
||||
"stateToString", "errorToString"];
|
||||
|
||||
const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
|
||||
"AddonScreenshot", "AddonType", "startup", "shutdown",
|
||||
|
|
Загрузка…
Ссылка в новой задаче