/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // This verifies that delaying a system add-on update works. PromiseTestUtils.allowMatchingRejectionsGlobally( /Message manager disconnected/ ); const profileDir = gProfD.clone(); profileDir.append("extensions"); const IGNORE_ID = "system_delay_ignore@tests.mozilla.org"; const COMPLETE_ID = "system_delay_complete@tests.mozilla.org"; const DEFER_ID = "system_delay_defer@tests.mozilla.org"; const DEFER2_ID = "system_delay_defer2@tests.mozilla.org"; const DEFER_ALSO_ID = "system_delay_defer_also@tests.mozilla.org"; const NORMAL_ID = "system1@tests.mozilla.org"; const distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true); registerDirectory("XREAppFeat", distroDir); registerCleanupFunction(() => { distroDir.remove(true); }); AddonTestUtils.usePrivilegedSignatures = id => "system"; createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42"); function promiseInstallPostponed(addonID1, addonID2) { return new Promise((resolve, reject) => { let seen = []; let listener = { onInstallFailed: () => { AddonManager.removeInstallListener(listener); reject("extension installation should not have failed"); }, onInstallEnded: install => { AddonManager.removeInstallListener(listener); reject( `extension installation should not have ended for ${install.addon.id}` ); }, onInstallPostponed: install => { seen.push(install.addon.id); if (seen.includes(addonID1) && seen.includes(addonID2)) { AddonManager.removeInstallListener(listener); resolve(); } }, }; AddonManager.addInstallListener(listener); }); } function promiseInstallResumed(addonID1, addonID2) { return new Promise((resolve, reject) => { let seenPostponed = []; let seenEnded = []; let listener = { onInstallFailed: () => { AddonManager.removeInstallListener(listener); reject("extension installation should not have failed"); }, onInstallEnded: install => { seenEnded.push(install.addon.id); if ( seenEnded.includes(addonID1) && seenEnded.includes(addonID2) && seenPostponed.includes(addonID1) && seenPostponed.includes(addonID2) ) { AddonManager.removeInstallListener(listener); resolve(); } }, onInstallPostponed: install => { seenPostponed.push(install.addon.id); }, }; AddonManager.addInstallListener(listener); }); } function promiseInstallDeferred(addonID1, addonID2) { return new Promise((resolve, reject) => { let seenEnded = []; let listener = { onInstallFailed: () => { AddonManager.removeInstallListener(listener); reject("extension installation should not have failed"); }, onInstallEnded: install => { seenEnded.push(install.addon.id); if (seenEnded.includes(addonID1) && seenEnded.includes(addonID2)) { AddonManager.removeInstallListener(listener); resolve(); } }, }; AddonManager.addInstallListener(listener); }); } async function checkAddon(addonID, { version }) { let addon = await promiseAddonByID(addonID); Assert.notEqual(addon, null); Assert.equal(addon.version, version); Assert.ok(addon.isCompatible); Assert.ok(!addon.appDisabled); Assert.ok(addon.isActive); Assert.equal(addon.type, "extension"); } // Tests below have webextension background scripts inline. /* globals browser */ // add-on registers upgrade listener, and ignores update. add_task(async function test_addon_upgrade_on_restart() { // discard system addon updates Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, ""); let xpi = await getSystemAddonXPI(1, "1.0"); xpi.copyTo(distroDir, `${NORMAL_ID}.xpi`); // Version 1.0 of an extension that ignores updates. function background() { browser.runtime.onUpdateAvailable.addListener(() => { browser.test.sendMessage("got-update"); }); } xpi = await createTempWebExtensionFile({ background, manifest: { version: "1.0", browser_specific_settings: { gecko: { id: IGNORE_ID } }, }, }); xpi.copyTo(distroDir, `${IGNORE_ID}.xpi`); // Version 2.0 of the same extension. let xpi2 = await createTempWebExtensionFile({ manifest: { version: "2.0", browser_specific_settings: { gecko: { id: IGNORE_ID } }, }, }); await overrideBuiltIns({ system: [IGNORE_ID, NORMAL_ID] }); let extension = ExtensionTestUtils.expectExtension(IGNORE_ID); await Promise.all([promiseStartupManager(), extension.awaitStartup()]); let updateList = [ { id: IGNORE_ID, version: "2.0", path: "system_delay_ignore_2.xpi", xpi: xpi2, }, { id: NORMAL_ID, version: "2.0", path: "system1_2.xpi", xpi: await getSystemAddonXPI(1, "2.0"), }, ]; await Promise.all([ promiseInstallPostponed(IGNORE_ID, NORMAL_ID), installSystemAddons(buildSystemAddonUpdates(updateList)), extension.awaitMessage("got-update"), ]); // addon upgrade has been delayed. await checkAddon(IGNORE_ID, { version: "1.0" }); // other addons in the set are delayed as well. await checkAddon(NORMAL_ID, { version: "1.0" }); // restarting allows upgrades to proceed await Promise.all([promiseRestartManager(), extension.awaitStartup()]); await checkAddon(IGNORE_ID, { version: "2.0" }); await checkAddon(NORMAL_ID, { version: "2.0" }); await promiseShutdownManager(); }); // add-on registers upgrade listener, and allows update. add_task(async function test_addon_upgrade_on_reload() { // discard system addon updates Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, ""); let xpi = await getSystemAddonXPI(1, "1.0"); xpi.copyTo(distroDir, `${NORMAL_ID}.xpi`); // Version 1.0 of an extension that listens for and immediately // applies updates. function background() { browser.runtime.onUpdateAvailable.addListener(function listener() { browser.runtime.onUpdateAvailable.removeListener(listener); browser.test.sendMessage("got-update"); browser.runtime.reload(); }); } xpi = await createTempWebExtensionFile({ background, manifest: { version: "1.0", browser_specific_settings: { gecko: { id: COMPLETE_ID } }, }, }); xpi.copyTo(distroDir, `${COMPLETE_ID}.xpi`); // Version 2.0 of the same extension. let xpi2 = await createTempWebExtensionFile({ manifest: { version: "2.0", browser_specific_settings: { gecko: { id: COMPLETE_ID } }, }, }); await overrideBuiltIns({ system: [COMPLETE_ID, NORMAL_ID] }); let extension = ExtensionTestUtils.expectExtension(COMPLETE_ID); await Promise.all([promiseStartupManager(), extension.awaitStartup()]); let updateList = [ { id: COMPLETE_ID, version: "2.0", path: "system_delay_complete_2.xpi", xpi: xpi2, }, { id: NORMAL_ID, version: "2.0", path: "system1_2.xpi", xpi: await getSystemAddonXPI(1, "2.0"), }, ]; // initial state await checkAddon(COMPLETE_ID, { version: "1.0" }); await checkAddon(NORMAL_ID, { version: "1.0" }); // We should see that the onUpdateListener executed, then see the // update resume. await Promise.all([ extension.awaitMessage("got-update"), promiseInstallResumed(COMPLETE_ID, NORMAL_ID), installSystemAddons(buildSystemAddonUpdates(updateList)), ]); await extension.awaitStartup(); // addon upgrade has been allowed await checkAddon(COMPLETE_ID, { version: "2.0" }); // other upgrades in the set are allowed as well await checkAddon(NORMAL_ID, { version: "2.0" }); // restarting changes nothing await Promise.all([promiseRestartManager(), extension.awaitStartup()]); await checkAddon(COMPLETE_ID, { version: "2.0" }); await checkAddon(NORMAL_ID, { version: "2.0" }); await promiseShutdownManager(); }); function delayBackground() { browser.test.onMessage.addListener(msg => { if (msg !== "reload") { browser.test.fail(`Got unexpected test message: ${msg}`); } browser.runtime.reload(); }); browser.runtime.onUpdateAvailable.addListener(async function listener() { browser.runtime.onUpdateAvailable.removeListener(listener); browser.test.sendMessage("got-update"); }); } // Upgrade listener initially defers then proceeds after a pause. add_task(async function test_addon_upgrade_after_pause() { // discard system addon updates Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, ""); let xpi = await getSystemAddonXPI(1, "1.0"); xpi.copyTo(distroDir, `${NORMAL_ID}.xpi`); // Version 1.0 of an extension that delays upgrades. xpi = await createTempWebExtensionFile({ background: delayBackground, manifest: { version: "1.0", browser_specific_settings: { gecko: { id: DEFER_ID } }, }, }); xpi.copyTo(distroDir, `${DEFER_ID}.xpi`); // Version 2.0 of the same xtension. let xpi2 = await createTempWebExtensionFile({ manifest: { version: "2.0", browser_specific_settings: { gecko: { id: DEFER_ID } }, }, }); await overrideBuiltIns({ system: [DEFER_ID, NORMAL_ID] }); let extension = ExtensionTestUtils.expectExtension(DEFER_ID); await Promise.all([promiseStartupManager(), extension.awaitStartup()]); let updateList = [ { id: DEFER_ID, version: "2.0", path: "system_delay_defer_2.xpi", xpi: xpi2, }, { id: NORMAL_ID, version: "2.0", path: "system1_2.xpi", xpi: await getSystemAddonXPI(1, "2.0"), }, ]; await Promise.all([ promiseInstallPostponed(DEFER_ID, NORMAL_ID), installSystemAddons(buildSystemAddonUpdates(updateList)), extension.awaitMessage("got-update"), ]); // upgrade is initially postponed await checkAddon(DEFER_ID, { version: "1.0" }); // other addons in the set are postponed as well. await checkAddon(NORMAL_ID, { version: "1.0" }); let deferred = promiseInstallDeferred(DEFER_ID, NORMAL_ID); // Tell the extension to proceed with the update. extension.setRestarting(); extension.sendMessage("reload"); await Promise.all([deferred, extension.awaitStartup()]); // addon upgrade has been allowed await checkAddon(DEFER_ID, { version: "2.0" }); // other addons in the set are allowed as well. await checkAddon(NORMAL_ID, { version: "2.0" }); // restarting changes nothing await promiseRestartManager(); await extension.awaitStartup(); await checkAddon(DEFER_ID, { version: "2.0" }); await checkAddon(NORMAL_ID, { version: "2.0" }); await promiseShutdownManager(); }); // Multiple add-ons register update listeners, initially defers then // each unblock in turn. add_task(async function test_multiple_addon_upgrade_postpone() { // discard system addon updates. Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, ""); let updateList = []; let xpi = await createTempWebExtensionFile({ background: delayBackground, manifest: { version: "1.0", browser_specific_settings: { gecko: { id: DEFER2_ID } }, }, }); xpi.copyTo(distroDir, `${DEFER2_ID}.xpi`); xpi = await createTempWebExtensionFile({ manifest: { version: "2.0", browser_specific_settings: { gecko: { id: DEFER2_ID } }, }, }); updateList.push({ id: DEFER2_ID, version: "2.0", path: "system_delay_defer_2.xpi", xpi, }); xpi = await createTempWebExtensionFile({ background: delayBackground, manifest: { version: "1.0", browser_specific_settings: { gecko: { id: DEFER_ALSO_ID } }, }, }); xpi.copyTo(distroDir, `${DEFER_ALSO_ID}.xpi`); xpi = await createTempWebExtensionFile({ manifest: { version: "2.0", browser_specific_settings: { gecko: { id: DEFER_ALSO_ID } }, }, }); updateList.push({ id: DEFER_ALSO_ID, version: "2.0", path: "system_delay_defer_also_2.xpi", xpi, }); await overrideBuiltIns({ system: [DEFER2_ID, DEFER_ALSO_ID] }); let extension1 = ExtensionTestUtils.expectExtension(DEFER2_ID); let extension2 = ExtensionTestUtils.expectExtension(DEFER_ALSO_ID); await Promise.all([ promiseStartupManager(), extension1.awaitStartup(), extension2.awaitStartup(), ]); await Promise.all([ promiseInstallPostponed(DEFER2_ID, DEFER_ALSO_ID), installSystemAddons(buildSystemAddonUpdates(updateList)), extension1.awaitMessage("got-update"), extension2.awaitMessage("got-update"), ]); // upgrade is initially postponed await checkAddon(DEFER2_ID, { version: "1.0" }); // other addons in the set are postponed as well. await checkAddon(DEFER_ALSO_ID, { version: "1.0" }); let deferred = promiseInstallDeferred(DEFER2_ID, DEFER_ALSO_ID); // Let one extension request that the update proceed. extension1.setRestarting(); extension1.sendMessage("reload"); // Upgrade blockers still present. await checkAddon(DEFER2_ID, { version: "1.0" }); await checkAddon(DEFER_ALSO_ID, { version: "1.0" }); // Let the second extension allow the update to proceed. extension2.setRestarting(); extension2.sendMessage("reload"); await Promise.all([ deferred, extension1.awaitStartup(), extension2.awaitStartup(), ]); // addon upgrade has been allowed await checkAddon(DEFER2_ID, { version: "2.0" }); await checkAddon(DEFER_ALSO_ID, { version: "2.0" }); // restarting changes nothing await Promise.all([ promiseRestartManager(), extension1.awaitStartup(), extension2.awaitStartup(), ]); await checkAddon(DEFER2_ID, { version: "2.0" }); await checkAddon(DEFER_ALSO_ID, { version: "2.0" }); await promiseShutdownManager(); });