summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/browser/browser_webapi_install.js')
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_webapi_install.js311
1 files changed, 311 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
new file mode 100644
index 000000000..bc31c2331
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -0,0 +1,311 @@
+const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
+const XPI_URL = `${SECURE_TESTROOT}addons/browser_webapi_install.xpi`;
+const XPI_SHA = "sha256:d4bab17ff9ba5f635e97c84021f4c527c502250d62ab7f6e6c9e8ee28822f772";
+
+const ID = "webapi_install@tests.mozilla.org";
+// eh, would be good to just stat the real file instead of this...
+const XPI_LEN = 4782;
+
+function waitForClear() {
+ const MSG = "WebAPICleanup";
+ return new Promise(resolve => {
+ let listener = {
+ receiveMessage: function(msg) {
+ if (msg.name == MSG) {
+ Services.mm.removeMessageListener(MSG, listener);
+ resolve();
+ }
+ }
+ };
+
+ Services.mm.addMessageListener(MSG, listener, true);
+ });
+}
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["extensions.webapi.testing", true],
+ ["extensions.install.requireBuiltInCerts", false]],
+ });
+ info("added preferences");
+});
+
+// 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, args, steps, description) {
+ let success = yield ContentTask.spawn(browser, {args, steps}, function* (opts) {
+ let { args, steps } = opts;
+ let install = yield content.navigator.mozAddonManager.createInstall(args);
+ 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 = [];
+ let prevEvent = null;
+ 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();
+ // Skip any repeated onDownloadProgress events.
+ while (received &&
+ received.event == prevEvent &&
+ prevEvent == "onDownloadProgress") {
+ received = receivedEvents.shift();
+ }
+ // Wait for more events if we skipped all there were.
+ if (!received) {
+ eventWaiter = () => {
+ eventWaiter = null;
+ check();
+ }
+ return;
+ }
+ prevEvent = received.event;
+ 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();
+ }
+ 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");
+ };
+}
+
+function makeRegularTest(options, what) {
+ return 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, options, steps, what);
+
+ 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(makeRegularTest({url: XPI_URL}, "a basic install works"));
+add_task(makeRegularTest({url: XPI_URL, hash: null}, "install with hash=null works"));
+add_task(makeRegularTest({url: XPI_URL, hash: ""}, "install with empty string for hash works"));
+add_task(makeRegularTest({url: XPI_URL, hash: XPI_SHA}, "install with hash works"));
+
+add_task(makeInstallTest(function* (browser) {
+ let steps = [
+ {action: "cancel"},
+ {
+ event: "onDownloadCancelled",
+ props: {
+ state: "STATE_CANCELLED",
+ error: null,
+ },
+ }
+ ];
+
+ yield testInstall(browser, {url: 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, {url: XPI_URL + "bogus"}, steps, "install of a bad url fails");
+
+ 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_INCORRECT_HASH",
+ },
+ }
+ ];
+
+ yield testInstall(browser, {url: XPI_URL, hash: "sha256:bogus"}, steps, "install with bad hash fails");
+
+ 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(function* test_permissions() {
+ function testBadUrl(url, pattern, successMessage) {
+ return BrowserTestUtils.withNewTab(TESTPAGE, function* (browser) {
+ let result = yield ContentTask.spawn(browser, {url, pattern}, function (opts) {
+ return new Promise(resolve => {
+ content.navigator.mozAddonManager.createInstall({url: opts.url})
+ .then(() => {
+ resolve({success: false, message: "createInstall should not have succeeded"});
+ }, err => {
+ if (err.message.match(new RegExp(opts.pattern))) {
+ resolve({success: true});
+ }
+ resolve({success: false, message: `Wrong error message: ${err.message}`});
+ });
+ });
+ });
+ is(result.success, true, result.message || successMessage);
+ });
+ }
+
+ yield testBadUrl("i am not a url", "NS_ERROR_MALFORMED_URI",
+ "Installing from an unparseable URL fails");
+
+ yield testBadUrl("https://addons.not-really-mozilla.org/impostor.xpi",
+ "not permitted",
+ "Installing from non-approved URL fails");
+});