Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: browser/components/newtab/test/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
/* import-globals-from ../../../../extensions/newtab/test/xpcshell/head.js */
/* import-globals-from head_nimbus_trainhop.js */
const { sinon } = ChromeUtils.importESModule(
);
const { AsyncShutdown } = ChromeUtils.importESModule(
"resource://gre/modules/AsyncShutdown.sys.mjs"
);
const { TestUtils } = ChromeUtils.importESModule(
);
/**
* Triggers the `appShutdownConfirmed` AsyncShutdown barrier. This spins the
* event loop until every blocker registered on the barrier has been resolved,
* so it never returns while a blocker is stalled.
*/
function triggerAppShutdownConfirmed() {
Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
try {
AsyncShutdown.appShutdownConfirmed._trigger();
} finally {
// Reset the barrier so the rest of the test file (and the harness shutdown)
// is not left with a closed phase.
AsyncShutdown.appShutdownConfirmed._reset();
Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
}
}
/**
* Ensures that a stalled download of an XPI does not cause an AsyncShutdown
* blocker to be added.
*/
add_task(
{
pref_set: [
[TRAINHOP_SCHEDULED_UPDATE_STATE_TIMEOUT_PREF, 100],
[TRAINHOP_SCHEDULED_UPDATE_STATE_DELAY_PREF, 100],
],
},
async function test_no_asyncshutdown_blocker_on_stalled_trainhop() {
const sandbox = sinon.createSandbox();
// Simulate a train-hop install that never settles (e.g. a download whose
// channel stalled and that never got cancelled).
const neverResolves = Promise.withResolvers();
sandbox
.stub(AboutNewTabResourceMapping, "_installTrainhopAddon")
.callsFake(() => neverResolves.promise);
const updateAddonVersion = `${BUILTIN_ADDON_VERSION}.111`;
const { nimbusFeatureCleanup } = await setupNimbusTrainhopAddon({
updateAddonVersion,
});
AboutNewTabResourceMapping.scheduleUpdateTrainhopAddonState();
const deferredTask =
AboutNewTabResourceMapping._updateAddonStateDeferredTask;
Assert.ok(deferredTask, "Got the _updateAddonStateDeferredTask instance");
// Let the DeferredTask fire and get stuck inside _installTrainhopAddon.
await TestUtils.waitForCondition(
() => deferredTask.isArmed === false && deferredTask.isRunning,
"Wait for the train-hop deferred task to be running"
);
Assert.ok(
AboutNewTabResourceMapping._installTrainhopAddon.called,
"Expect the stalled _installTrainhopAddon to have been reached"
);
// We now simulate a shutdown and ensure we're not blocking it.
triggerAppShutdownConfirmed();
Assert.ok(
deferredTask.isRunning,
"Expect the stalled train-hop task to still be running, and shutdown to not have waited for it"
);
Assert.ok(
!deferredTask.isFinalized,
"Expect no AsyncShutdown blocker to have finalized the train-hop deferred task"
);
// Unblock and tear down.
neverResolves.resolve();
await deferredTask._runningPromise;
sandbox.restore();
await nimbusFeatureCleanup();
await deferredTask.finalize();
AboutNewTabResourceMapping._updateAddonStateDeferredTask = null;
}
);
/**
* Tests that once the application is shutting down, the deferred task must bail
* out without being able to start a new train-hop download.
*/
add_task(
{
pref_set: [
[TRAINHOP_SCHEDULED_UPDATE_STATE_TIMEOUT_PREF, 100],
[TRAINHOP_SCHEDULED_UPDATE_STATE_DELAY_PREF, 100],
],
},
async function test_no_download_started_while_shutting_down() {
const sandbox = sinon.createSandbox();
sandbox
.stub(AboutNewTabResourceMapping, "_isAppShuttingDown")
.callsFake(() => true);
const installSpy = sandbox.spy(
AboutNewTabResourceMapping,
"_installTrainhopAddon"
);
const updateAddonVersion = `${BUILTIN_ADDON_VERSION}.222`;
const { nimbusFeatureCleanup } = await setupNimbusTrainhopAddon({
updateAddonVersion,
});
AboutNewTabResourceMapping.scheduleUpdateTrainhopAddonState();
const deferredTask =
AboutNewTabResourceMapping._updateAddonStateDeferredTask;
await TestUtils.waitForCondition(
() => !deferredTask.isArmed && !deferredTask.isRunning,
"Wait for the train-hop deferred task to have run"
);
Assert.ok(
!installSpy.called,
"Expect no train-hop install to be attempted while shutting down"
);
Assert.deepEqual(
await AddonManager.getAllInstalls(),
[],
"Expect no AddonInstall to have been created while shutting down"
);
// The same must hold when updateTrainhopAddonState is called directly (e.g.
// from the FirstStartup hook) and reaches _installTrainhopAddon.
await AboutNewTabResourceMapping.updateTrainhopAddonState();
Assert.ok(
installSpy.called,
"Sanity check: _installTrainhopAddon was reached"
);
Assert.deepEqual(
await AddonManager.getAllInstalls(),
[],
"Expect _installTrainhopAddon to not start a download while shutting down"
);
sandbox.restore();
await nimbusFeatureCleanup();
await deferredTask.finalize();
AboutNewTabResourceMapping._updateAddonStateDeferredTask = null;
}
);
/**
* If shutdown starts after the XPI has been downloaded, we must leave the
* install staged for the next startup instead of forcing a restartless
* install.
*/
add_task(async function test_no_restartless_install_while_shutting_down() {
const sandbox = sinon.createSandbox();
// Reset the resource mapping state so the forceRestartlessInstall path (which
// requires `initialized` to be false) is taken, as on FirstStartup.
mockAboutNewTabUninit();
// Only report "shutting down" once the XPI has finished downloading, so that
// the download itself is allowed to start.
let shuttingDown = false;
sandbox
.stub(AboutNewTabResourceMapping, "_isAppShuttingDown")
.callsFake(() => shuttingDown);
const downloadEndedListener = {
onDownloadEnded() {
shuttingDown = true;
},
};
AddonManager.addInstallListener(downloadEndedListener);
const updateAddonVersion = `${BUILTIN_ADDON_VERSION}.333`;
const { nimbusFeatureCleanup } = await setupNimbusTrainhopAddon({
updateAddonVersion,
});
await AboutNewTabResourceMapping.updateTrainhopAddonState(
true /* forceRestartlessInstall */
);
Assert.ok(shuttingDown, "Sanity check: the XPI download did complete");
// The train-hop add-on must NOT have been installed restartlessly.
await asyncAssertNewTabAddon({
locationName: BUILTIN_LOCATION_NAME,
version: BUILTIN_ADDON_VERSION,
});
const { pendingInstall } = await asyncAssertNimbusTrainhopAddonStaged({
updateAddonVersion,
});
AddonManager.removeInstallListener(downloadEndedListener);
await cancelPendingInstall(pendingInstall);
sandbox.restore();
await nimbusFeatureCleanup();
await AboutNewTabResourceMapping._updateAddonStateDeferredTask?.finalize();
AboutNewTabResourceMapping._updateAddonStateDeferredTask = null;
});