diff options
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_update.js')
-rw-r--r-- | toolkit/mozapps/extensions/test/xpcshell/test_update.js | 836 |
1 files changed, 836 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_update.js b/toolkit/mozapps/extensions/test/xpcshell/test_update.js new file mode 100644 index 0000000000..6fd6af44c7 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js @@ -0,0 +1,836 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that add-on update checks work + +// The test extension uses an insecure update url. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); +// This test uses add-on versions that follow the toolkit version but we +// started to encourage the use of a simpler format in Bug 1793925. We disable +// the pref below to avoid install errors. +Services.prefs.setBoolPref( + "extensions.webextensions.warnings-as-errors", + false +); + +const updateFile = "test_update.json"; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +const ADDONS = { + test_update: { + id: "addon1@tests.mozilla.org", + version: "2.0", + name: "Test 1", + }, + test_update8: { + id: "addon8@tests.mozilla.org", + version: "2.0", + name: "Test 8", + }, + test_update12: { + id: "addon12@tests.mozilla.org", + version: "2.0", + name: "Test 12", + }, + test_install2_1: { + id: "addon2@tests.mozilla.org", + version: "2.0", + name: "Real Test 2", + }, + test_install2_2: { + id: "addon2@tests.mozilla.org", + version: "3.0", + name: "Real Test 3", + }, +}; + +var testserver = createHttpServer({ hosts: ["example.com"] }); +testserver.registerDirectory("/data/", do_get_file("data")); + +const XPIS = {}; + +add_task(async function setup() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1"); + + Services.locale.requestedLocales = ["fr-FR"]; + + for (let [name, info] of Object.entries(ADDONS)) { + XPIS[name] = createTempWebExtensionFile({ + manifest: { + name: info.name, + version: info.version, + browser_specific_settings: { gecko: { id: info.id } }, + }, + }); + testserver.registerFile(`/addons/${name}.xpi`, XPIS[name]); + } + + AddonTestUtils.updateReason = AddonManager.UPDATE_WHEN_USER_REQUESTED; + + await promiseStartupManager(); +}); + +// Verify that an update is available and can be installed. +add_task(async function test_apply_update() { + await promiseInstallWebExtension({ + manifest: { + name: "Test Addon 1", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon1@tests.mozilla.org", + update_url: `http://example.com/data/${updateFile}`, + }, + }, + }, + }); + + let a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + notEqual(a1, null); + equal(a1.version, "1.0"); + equal(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DEFAULT); + equal(a1.releaseNotesURI, null); + notEqual(a1.syncGUID, null); + + let originalSyncGUID = a1.syncGUID; + + await expectEvents( + { + ignorePlugins: true, + addonEvents: { + "addon1@tests.mozilla.org": [ + { + event: "onPropertyChanged", + properties: ["applyBackgroundUpdates"], + }, + ], + }, + }, + async () => { + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + } + ); + + a1.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + let install; + await expectEvents( + { + ignorePlugins: true, + installEvents: [{ event: "onNewInstall" }], + }, + async () => { + ({ + updateAvailable: install, + } = await AddonTestUtils.promiseFindAddonUpdates(a1)); + } + ); + + let installs = await AddonManager.getAllInstalls(); + equal(installs.length, 1); + equal(installs[0], install); + + equal(install.name, a1.name); + equal(install.version, "2.0"); + equal(install.state, AddonManager.STATE_AVAILABLE); + equal(install.existingAddon, a1); + equal(install.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + // Verify that another update check returns the same AddonInstall + let { + updateAvailable: install2, + } = await AddonTestUtils.promiseFindAddonUpdates(a1); + + installs = await AddonManager.getAllInstalls(); + equal(installs.length, 1); + equal(installs[0], install); + equal(install2, install); + + await expectEvents( + { + ignorePlugins: true, + installEvents: [ + { event: "onDownloadStarted" }, + { event: "onDownloadEnded", returnValue: false }, + ], + }, + () => { + install.install(); + } + ); + + equal(install.state, AddonManager.STATE_DOWNLOADED); + + // Continue installing the update. + // Verify that another update check returns no new update + let { updateAvailable } = await AddonTestUtils.promiseFindAddonUpdates( + install.existingAddon + ); + + ok( + !updateAvailable, + "Should find no available updates when one is already downloading" + ); + + installs = await AddonManager.getAllInstalls(); + equal(installs.length, 1); + equal(installs[0], install); + + await expectEvents( + { + ignorePlugins: true, + addonEvents: { + "addon1@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => { + install.install(); + } + ); + + await AddonTestUtils.loadAddonsList(true); + + // Grab the current time so we can check the mtime of the add-on below + // without worrying too much about how long other tests take. + let startupTime = Date.now(); + + ok(isExtensionInBootstrappedList(profileDir, "addon1@tests.mozilla.org")); + + a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + notEqual(a1, null); + equal(a1.version, "2.0"); + ok(isExtensionInBootstrappedList(profileDir, a1.id)); + equal(a1.applyBackgroundUpdates, AddonManager.AUTOUPDATE_DISABLE); + equal(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + notEqual(a1.syncGUID, null); + equal(originalSyncGUID, a1.syncGUID); + + // Make sure that the extension lastModifiedTime was updated. + let testFile = getAddonFile(a1); + let difference = testFile.lastModifiedTime - startupTime; + ok(Math.abs(difference) < MAX_TIME_DIFFERENCE); + + await a1.uninstall(); +}); + +// Check that an update check finds compatibility updates and applies them +add_task(async function test_compat_update() { + await promiseInstallWebExtension({ + manifest: { + name: "Test Addon 2", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon2@tests.mozilla.org", + update_url: "http://example.com/data/" + updateFile, + strict_max_version: "0", + }, + }, + }, + }); + + let a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + notEqual(a2, null); + ok(a2.isActive); + ok(a2.isCompatible); + ok(!a2.appDisabled); + ok(a2.isCompatibleWith("0", "0")); + + let result = await AddonTestUtils.promiseFindAddonUpdates(a2); + ok(result.compatibilityUpdate, "Should have seen a compatibility update"); + ok(!result.updateAvailable, "Should not have seen a version update"); + + ok(a2.isCompatible); + ok(!a2.appDisabled); + ok(a2.isActive); + + await promiseRestartManager(); + + a2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org"); + notEqual(a2, null); + ok(a2.isActive); + ok(a2.isCompatible); + ok(!a2.appDisabled); + await a2.uninstall(); +}); + +// Checks that we see no compatibility information when there is none. +add_task(async function test_no_compat() { + gAppInfo.platformVersion = "5"; + await promiseRestartManager("5"); + await promiseInstallWebExtension({ + manifest: { + name: "Test Addon 3", + browser_specific_settings: { + gecko: { + id: "addon3@tests.mozilla.org", + update_url: `http://example.com/data/${updateFile}`, + strict_min_version: "5", + }, + }, + }, + }); + + gAppInfo.platformVersion = "1"; + await promiseRestartManager("1"); + + let a3 = await AddonManager.getAddonByID("addon3@tests.mozilla.org"); + notEqual(a3, null); + ok(!a3.isActive); + ok(!a3.isCompatible); + ok(a3.appDisabled); + ok(a3.isCompatibleWith("5", "5")); + ok(!a3.isCompatibleWith("2", "2")); + + let result = await AddonTestUtils.promiseFindAddonUpdates(a3); + ok( + !result.compatibilityUpdate, + "Should not have seen a compatibility update" + ); + ok(!result.updateAvailable, "Should not have seen a version update"); +}); + +// Checks that compatibility info for future apps are detected but don't make +// the item compatibile. +add_task(async function test_future_compat() { + let a3 = await AddonManager.getAddonByID("addon3@tests.mozilla.org"); + notEqual(a3, null); + ok(!a3.isActive); + ok(!a3.isCompatible); + ok(a3.appDisabled); + ok(a3.isCompatibleWith("5", "5")); + ok(!a3.isCompatibleWith("2", "2")); + + let result = await AddonTestUtils.promiseFindAddonUpdates( + a3, + undefined, + "3.0", + "3.0" + ); + ok(result.compatibilityUpdate, "Should have seen a compatibility update"); + ok(!result.updateAvailable, "Should not have seen a version update"); + + ok(!a3.isActive); + ok(!a3.isCompatible); + ok(a3.appDisabled); + + await promiseRestartManager(); + + a3 = await AddonManager.getAddonByID("addon3@tests.mozilla.org"); + notEqual(a3, null); + ok(!a3.isActive); + ok(!a3.isCompatible); + ok(a3.appDisabled); + + await a3.uninstall(); +}); + +// Test that background update checks work +add_task(async function test_background_update() { + await promiseInstallWebExtension({ + manifest: { + name: "Test Addon 1", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon1@tests.mozilla.org", + update_url: `http://example.com/data/${updateFile}`, + strict_min_version: "1", + strict_max_version: "1", + }, + }, + }, + }); + + function checkInstall(install) { + notEqual(install.existingAddon, null); + equal(install.existingAddon.id, "addon1@tests.mozilla.org"); + } + + await expectEvents( + { + ignorePlugins: true, + addonEvents: { + "addon1@tests.mozilla.org": [ + { event: "onInstalling" }, + { event: "onInstalled" }, + ], + }, + installEvents: [ + { event: "onNewInstall" }, + { event: "onDownloadStarted" }, + { event: "onDownloadEnded", callback: checkInstall }, + { event: "onInstallStarted" }, + { event: "onInstallEnded" }, + ], + }, + () => { + AddonManagerPrivate.backgroundUpdateCheck(); + } + ); + + let a1 = await AddonManager.getAddonByID("addon1@tests.mozilla.org"); + notEqual(a1, null); + equal(a1.version, "2.0"); + equal(a1.releaseNotesURI.spec, "http://example.com/updateInfo.xhtml"); + + await a1.uninstall(); +}); + +const STATE_BLOCKED = Ci.nsIBlocklistService.STATE_BLOCKED; + +const PARAMS = + "?" + + [ + "req_version=%REQ_VERSION%", + "item_id=%ITEM_ID%", + "item_version=%ITEM_VERSION%", + "item_maxappversion=%ITEM_MAXAPPVERSION%", + "item_status=%ITEM_STATUS%", + "app_id=%APP_ID%", + "app_version=%APP_VERSION%", + "current_app_version=%CURRENT_APP_VERSION%", + "app_os=%APP_OS%", + "app_abi=%APP_ABI%", + "app_locale=%APP_LOCALE%", + "update_type=%UPDATE_TYPE%", + ].join("&"); + +const PARAM_ADDONS = { + "addon1@tests.mozilla.org": { + manifest: { + name: "Test Addon 1", + version: "5.0", + browser_specific_settings: { + gecko: { + id: "addon1@tests.mozilla.org", + update_url: `http://example.com/data/param_test.json${PARAMS}`, + strict_min_version: "1", + strict_max_version: "2", + }, + }, + }, + params: { + item_version: "5.0", + item_maxappversion: "2", + item_status: "userEnabled", + app_version: "1", + update_type: "97", + }, + updateType: [AddonManager.UPDATE_WHEN_USER_REQUESTED], + }, + + "addon2@tests.mozilla.org": { + manifest: { + name: "Test Addon 2", + version: "67.0.5b1", + browser_specific_settings: { + gecko: { + id: "addon2@tests.mozilla.org", + update_url: "http://example.com/data/param_test.json" + PARAMS, + strict_min_version: "0", + strict_max_version: "3", + }, + }, + }, + initialState: { + userDisabled: true, + }, + params: { + item_version: "67.0.5b1", + item_maxappversion: "3", + item_status: "userDisabled", + app_version: "1", + update_type: "49", + }, + updateType: [AddonManager.UPDATE_WHEN_ADDON_INSTALLED], + compatOnly: true, + }, + + "addon3@tests.mozilla.org": { + manifest: { + name: "Test Addon 3", + version: "1.3+", + browser_specific_settings: { + gecko: { + id: "addon3@tests.mozilla.org", + update_url: `http://example.com/data/param_test.json${PARAMS}`, + }, + }, + }, + params: { + item_version: "1.3+", + item_status: "userEnabled", + app_version: "1", + update_type: "112", + }, + updateType: [AddonManager.UPDATE_WHEN_PERIODIC_UPDATE], + }, + + "addon4@tests.mozilla.org": { + manifest: { + name: "Test Addon 4", + version: "0.5ab6", + browser_specific_settings: { + gecko: { + id: "addon4@tests.mozilla.org", + update_url: `http://example.com/data/param_test.json${PARAMS}`, + strict_min_version: "1", + strict_max_version: "5", + }, + }, + }, + params: { + item_version: "0.5ab6", + item_maxappversion: "5", + item_status: "userEnabled", + app_version: "2", + update_type: "98", + }, + updateType: [AddonManager.UPDATE_WHEN_NEW_APP_DETECTED, "2"], + }, + + "addon5@tests.mozilla.org": { + manifest: { + name: "Test Addon 5", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon5@tests.mozilla.org", + update_url: `http://example.com/data/param_test.json${PARAMS}`, + strict_min_version: "1", + strict_max_version: "1", + }, + }, + }, + params: { + item_version: "1.0", + item_maxappversion: "1", + item_status: "userEnabled", + app_version: "1", + update_type: "35", + }, + updateType: [AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED], + compatOnly: true, + }, + + "addon6@tests.mozilla.org": { + manifest: { + name: "Test Addon 6", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon6@tests.mozilla.org", + update_url: `http://example.com/data/param_test.json${PARAMS}`, + strict_min_version: "1", + strict_max_version: "1", + }, + }, + }, + params: { + item_version: "1.0", + item_maxappversion: "1", + item_status: "userEnabled", + app_version: "1", + update_type: "99", + }, + updateType: [AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED], + }, + + "blocklist2@tests.mozilla.org": { + manifest: { + name: "Test Addon 1", + version: "5.0", + browser_specific_settings: { + gecko: { + id: "blocklist2@tests.mozilla.org", + update_url: `http://example.com/data/param_test.json${PARAMS}`, + strict_min_version: "1", + strict_max_version: "2", + }, + }, + }, + params: { + item_version: "5.0", + item_maxappversion: "2", + item_status: "userEnabled,blocklisted", + app_version: "1", + update_type: "97", + }, + updateType: [AddonManager.UPDATE_WHEN_USER_REQUESTED], + blocklistState: STATE_BLOCKED, + }, +}; + +const PARAM_IDS = Object.keys(PARAM_ADDONS); + +// Verify the parameter escaping in update urls. +add_task(async function test_params() { + let blocked = []; + for (let [id, options] of Object.entries(PARAM_ADDONS)) { + if (options.blocklistState == STATE_BLOCKED) { + blocked.push(`${id}:${options.manifest.version}`); + } + } + let extensionsMLBF = [{ stash: { blocked, unblocked: [] }, stash_time: 0 }]; + await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF }); + + for (let [id, options] of Object.entries(PARAM_ADDONS)) { + await promiseInstallWebExtension({ manifest: options.manifest }); + + if (options.initialState) { + let addon = await AddonManager.getAddonByID(id); + await setInitialState(addon, options.initialState); + } + } + + let resultsPromise = new Promise(resolve => { + let results = new Map(); + + testserver.registerPathHandler("/data/param_test.json", function( + request, + response + ) { + let params = new URLSearchParams(request.queryString); + let itemId = params.get("item_id"); + ok( + !results.has(itemId), + `Should not see a duplicate request for item ${itemId}` + ); + + results.set(itemId, params); + + if (results.size === PARAM_IDS.length) { + resolve(results); + } + + request.setStatusLine(null, 500, "Server Error"); + }); + }); + + let addons = await getAddons(PARAM_IDS); + for (let [id, options] of Object.entries(PARAM_ADDONS)) { + // Having an onUpdateAvailable callback in the listener automagically adds + // UPDATE_TYPE_NEWVERSION to the update type flags in the request. + let listener = options.compatOnly ? {} : { onUpdateAvailable() {} }; + + addons.get(id).findUpdates(listener, ...options.updateType); + } + + let baseParams = { + req_version: "2", + app_id: "xpcshell@tests.mozilla.org", + current_app_version: "1", + app_os: "XPCShell", + app_abi: "noarch-spidermonkey", + app_locale: "fr-FR", + }; + + let results = await resultsPromise; + for (let [id, options] of Object.entries(PARAM_ADDONS)) { + info(`Checking update params for ${id}`); + + let expected = Object.assign({}, baseParams, options.params); + let params = results.get(id); + + for (let [prop, value] of Object.entries(expected)) { + equal(params.get(prop), value, `Expected value for ${prop}`); + } + } + + for (let [, addon] of await getAddons(PARAM_IDS)) { + await addon.uninstall(); + } +}); + +// Tests that if a manifest claims compatibility then the add-on will be +// seen as compatible regardless of what the update payload says. +add_task(async function test_manifest_compat() { + await promiseInstallWebExtension({ + manifest: { + name: "Test Addon 1", + version: "5.0", + browser_specific_settings: { + gecko: { + id: "addon4@tests.mozilla.org", + update_url: `http://example.com/data/${updateFile}`, + strict_min_version: "0", + strict_max_version: "1", + }, + }, + }, + }); + + let a4 = await AddonManager.getAddonByID("addon4@tests.mozilla.org"); + ok(a4.isActive, "addon4 is active"); + ok(a4.isCompatible, "addon4 is compatible"); + + // Test that a normal update check won't decrease a targetApplication's + // maxVersion but an update check for a new application will. + await AddonTestUtils.promiseFindAddonUpdates( + a4, + AddonManager.UPDATE_WHEN_PERIODIC_UPDATE + ); + ok(a4.isActive, "addon4 is active"); + ok(a4.isCompatible, "addon4 is compatible"); + + await AddonTestUtils.promiseFindAddonUpdates( + a4, + AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED + ); + ok(!a4.isActive, "addon4 is not active"); + ok(!a4.isCompatible, "addon4 is not compatible"); + + await a4.uninstall(); +}); + +// Test that the background update check doesn't update an add-on that isn't +// allowed to update automatically. +add_task(async function test_no_auto_update() { + // Have an add-on there that will be updated so we see some events from it + await promiseInstallWebExtension({ + manifest: { + name: "Test Addon 1", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon1@tests.mozilla.org", + update_url: `http://example.com/data/${updateFile}`, + }, + }, + }, + }); + + await promiseInstallWebExtension({ + manifest: { + name: "Test Addon 8", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon8@tests.mozilla.org", + update_url: `http://example.com/data/${updateFile}`, + }, + }, + }, + }); + + let a8 = await AddonManager.getAddonByID("addon8@tests.mozilla.org"); + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // The background update check will find updates for both add-ons but only + // proceed to install one of them. + let listener; + await new Promise(resolve => { + listener = { + onNewInstall(aInstall) { + let id = aInstall.existingAddon.id; + ok( + id == "addon1@tests.mozilla.org" || id == "addon8@tests.mozilla.org", + "Saw unexpected onNewInstall for " + id + ); + }, + + onDownloadStarted(aInstall) { + equal(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadEnded(aInstall) { + equal(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onDownloadFailed(aInstall) { + ok(false, "Should not have seen onDownloadFailed event"); + }, + + onDownloadCancelled(aInstall) { + ok(false, "Should not have seen onDownloadCancelled event"); + }, + + onInstallStarted(aInstall) { + equal(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + }, + + onInstallEnded(aInstall) { + equal(aInstall.existingAddon.id, "addon1@tests.mozilla.org"); + + resolve(); + }, + + onInstallFailed(aInstall) { + ok(false, "Should not have seen onInstallFailed event"); + }, + + onInstallCancelled(aInstall) { + ok(false, "Should not have seen onInstallCancelled event"); + }, + }; + AddonManager.addInstallListener(listener); + AddonManagerPrivate.backgroundUpdateCheck(); + }); + AddonManager.removeInstallListener(listener); + + let a1; + [a1, a8] = await AddonManager.getAddonsByIDs([ + "addon1@tests.mozilla.org", + "addon8@tests.mozilla.org", + ]); + notEqual(a1, null); + equal(a1.version, "2.0"); + await a1.uninstall(); + + notEqual(a8, null); + equal(a8.version, "1.0"); + await a8.uninstall(); +}); + +// Test that the update check returns nothing for addons in locked install +// locations. +add_task(async function run_test_locked_install() { + const lockedDir = gProfD.clone(); + lockedDir.append("locked_extensions"); + registerDirectory("XREAppFeat", lockedDir); + + await promiseShutdownManager(); + + let xpi = await createTempWebExtensionFile({ + manifest: { + name: "Test Addon 13", + version: "1.0", + browser_specific_settings: { + gecko: { + id: "addon13@tests.mozilla.org", + update_url: "http://example.com/data/test_update.json", + }, + }, + }, + }); + xpi.copyTo(lockedDir, "addon13@tests.mozilla.org.xpi"); + + let validAddons = { system: ["addon13@tests.mozilla.org"] }; + await overrideBuiltIns(validAddons); + + await promiseStartupManager(); + + let a13 = await AddonManager.getAddonByID("addon13@tests.mozilla.org"); + notEqual(a13, null); + + let result = await AddonTestUtils.promiseFindAddonUpdates(a13); + ok( + !result.compatibilityUpdate, + "Should not have seen a compatibility update" + ); + ok(!result.updateAvailable, "Should not have seen a version update"); + + let installs = await AddonManager.getAllInstalls(); + equal(installs.length, 0); +}); |