forked from mirrors/gecko-dev
Bug 1874406 - Refactor, optimize and cleanup event page idle management r=robwu
Differential Revision: https://phabricator.services.mozilla.com/D210220
This commit is contained in:
parent
49af7ffb9e
commit
ee34cc38db
5 changed files with 88 additions and 94 deletions
|
|
@ -1206,7 +1206,7 @@ ParentAPIManager = {
|
|||
!context.extension.persistentBackground
|
||||
) {
|
||||
context.extension.emit("background-script-reset-idle", {
|
||||
reason: "parentApiCall",
|
||||
reason: "parentapicall",
|
||||
path: data.path,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -497,8 +497,8 @@ class BackgroundContextOwner {
|
|||
EventManager.clearPrimedListeners(this.extension, false);
|
||||
}
|
||||
|
||||
// Ensure there is no backgroundTimer running
|
||||
this.backgroundBuilder.clearIdleTimer();
|
||||
// Ensure any idle background timer is not running.
|
||||
this.backgroundBuilder.idleManager.clearTimer();
|
||||
|
||||
const bgInstance = this.bgInstance;
|
||||
if (bgInstance) {
|
||||
|
|
@ -664,6 +664,7 @@ class BackgroundBuilder {
|
|||
constructor(extension) {
|
||||
this.extension = extension;
|
||||
this.backgroundContextOwner = new BackgroundContextOwner(this, extension);
|
||||
this.idleManager = new IdleManager(extension);
|
||||
}
|
||||
|
||||
async build() {
|
||||
|
|
@ -718,26 +719,6 @@ class BackgroundBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
observe(subject, topic) {
|
||||
if (topic == "timer-callback") {
|
||||
let { extension } = this;
|
||||
this.clearIdleTimer();
|
||||
extension?.terminateBackground();
|
||||
}
|
||||
}
|
||||
|
||||
clearIdleTimer() {
|
||||
this.backgroundTimer?.cancel();
|
||||
this.backgroundTimer = null;
|
||||
}
|
||||
|
||||
resetIdleTimer() {
|
||||
this.clearIdleTimer();
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.init(this, backgroundIdleTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
this.backgroundTimer = timer;
|
||||
}
|
||||
|
||||
primeBackground(isInStartup = true) {
|
||||
let { extension } = this;
|
||||
|
||||
|
|
@ -779,79 +760,45 @@ class BackgroundBuilder {
|
|||
return bgStartupPromise;
|
||||
};
|
||||
|
||||
let resetBackgroundIdle = (eventName, resetIdleDetails) => {
|
||||
this.clearIdleTimer();
|
||||
if (!this.extension || extension.persistentBackground) {
|
||||
// Extension was already shut down or is persistent and
|
||||
// does not idle timout.
|
||||
return;
|
||||
}
|
||||
// TODO remove at an appropriate point in the future prior
|
||||
// to general availability. There may be some racy conditions
|
||||
// with idle timeout between an event starting and the event firing
|
||||
// but we still want testing with an idle timeout.
|
||||
if (
|
||||
!Services.prefs.getBoolPref("extensions.background.idle.enabled", true)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let resetBackgroundIdle = (event, { reason }) => {
|
||||
if (
|
||||
extension.backgroundState == BACKGROUND_STATE.SUSPENDING &&
|
||||
// After we begin suspending the background, parent API calls from
|
||||
// runtime.onSuspend listeners shouldn't cancel the suspension.
|
||||
resetIdleDetails?.reason !== "parentApiCall"
|
||||
reason !== "parentapicall"
|
||||
) {
|
||||
extension.backgroundState = BACKGROUND_STATE.RUNNING;
|
||||
// call runtime.onSuspendCanceled
|
||||
extension.emit("background-script-suspend-canceled");
|
||||
}
|
||||
|
||||
this.resetIdleTimer();
|
||||
this.idleManager.resetTimer();
|
||||
|
||||
if (
|
||||
eventName === "background-script-reset-idle" &&
|
||||
// TODO(Bug 1790087): record similar telemetry for background service worker.
|
||||
!this.isWorker
|
||||
) {
|
||||
// Record the reason for resetting the event page idle timeout
|
||||
// in a idle result histogram, with the category set based
|
||||
// on the reason for resetting (defaults to 'reset_other'
|
||||
// if resetIdleDetails.reason is missing or not mapped into the
|
||||
// telemetry histogram categories).
|
||||
//
|
||||
// Keep this in sync with the categories listed in Histograms.json
|
||||
// for "WEBEXT_EVENTPAGE_IDLE_RESULT_COUNT".
|
||||
let category = "reset_other";
|
||||
switch (resetIdleDetails?.reason) {
|
||||
case "event":
|
||||
category = "reset_event";
|
||||
return; // not break; because too frequent, see bug 1868960.
|
||||
case "hasActiveNativeAppPorts":
|
||||
category = "reset_nativeapp";
|
||||
break;
|
||||
case "hasActiveStreamFilter":
|
||||
category = "reset_streamfilter";
|
||||
break;
|
||||
case "pendingListeners":
|
||||
category = "reset_listeners";
|
||||
break;
|
||||
case "parentApiCall":
|
||||
category = "reset_parentapicall";
|
||||
return; // not break; because too frequent, see bug 1868960.
|
||||
}
|
||||
|
||||
ExtensionTelemetry.eventPageIdleResult.histogramAdd({
|
||||
extension,
|
||||
category,
|
||||
});
|
||||
if (this.isWorker) {
|
||||
// TODO(Bug 1790087): record similar telemetry for service workers.
|
||||
return;
|
||||
}
|
||||
if (reason === "event" || reason === "parentapicall") {
|
||||
// Bug 1868960: not recording these because too frequent.
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep in sync with categories in WEBEXT_EVENTPAGE_IDLE_RESULT_COUNT.
|
||||
let KNOWN = ["nativeapp", "streamfilter", "listeners"];
|
||||
ExtensionTelemetry.eventPageIdleResult.histogramAdd({
|
||||
extension,
|
||||
category: `reset_${KNOWN.includes(reason) ? reason : "other"}`,
|
||||
});
|
||||
};
|
||||
|
||||
// Listen for events from the EventManager
|
||||
extension.on("background-script-reset-idle", resetBackgroundIdle);
|
||||
// After the background is started, initiate the first timer
|
||||
extension.once("background-script-started", resetBackgroundIdle);
|
||||
if (!extension.persistentBackground) {
|
||||
// Listen for events from the EventManager
|
||||
extension.on("background-script-reset-idle", resetBackgroundIdle);
|
||||
|
||||
// After the background is started, initiate the first timer
|
||||
extension.once("background-script-started", () => {
|
||||
this.idleManager.resetTimer();
|
||||
});
|
||||
}
|
||||
|
||||
// TODO bug 1844488: terminateBackground should account for externally
|
||||
// triggered background restarts. It does currently performs various
|
||||
|
|
@ -889,9 +836,7 @@ class BackgroundBuilder {
|
|||
!disableResetIdleForTest &&
|
||||
extension.backgroundContext?.hasActiveNativeAppPorts
|
||||
) {
|
||||
extension.emit("background-script-reset-idle", {
|
||||
reason: "hasActiveNativeAppPorts",
|
||||
});
|
||||
extension.emit("background-script-reset-idle", { reason: "nativeapp" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -900,7 +845,7 @@ class BackgroundBuilder {
|
|||
extension.backgroundContext?.pendingRunListenerPromisesCount
|
||||
) {
|
||||
extension.emit("background-script-reset-idle", {
|
||||
reason: "pendingListeners",
|
||||
reason: "listeners",
|
||||
pendingListeners:
|
||||
extension.backgroundContext.pendingRunListenerPromisesCount,
|
||||
});
|
||||
|
|
@ -941,7 +886,7 @@ class BackgroundBuilder {
|
|||
});
|
||||
if (!disableResetIdleForTest && hasActiveStreamFilter) {
|
||||
extension.emit("background-script-reset-idle", {
|
||||
reason: "hasActiveStreamFilter",
|
||||
reason: "streamfilter",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -956,7 +901,7 @@ class BackgroundBuilder {
|
|||
}
|
||||
|
||||
extension.backgroundState = BACKGROUND_STATE.SUSPENDING;
|
||||
this.clearIdleTimer();
|
||||
this.idleManager.clearTimer();
|
||||
// call runtime.onSuspend
|
||||
await extension.emit("background-script-suspend");
|
||||
// If in the meantime another event fired, state will be RUNNING,
|
||||
|
|
@ -1027,6 +972,53 @@ class BackgroundBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Times the suspension of the background page, acts like a 3-state machine:
|
||||
* - suspended (or uninitialized)
|
||||
* - waiting for a timeout (now() < sleepTime)
|
||||
* - TODO: waiting on a promise (keepAwake.size > 0)
|
||||
*/
|
||||
var IdleManager = class IdleManager {
|
||||
sleepTime = 0;
|
||||
/** @type {nsITimer} */
|
||||
timer = null;
|
||||
/** @type {Map<promise, string>} */
|
||||
keepAlive = new Map();
|
||||
|
||||
constructor(extension) {
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
clearTimer() {
|
||||
this.timer?.cancel();
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
resetTimer() {
|
||||
this.sleepTime = Cu.now() + backgroundIdleTimeout;
|
||||
if (!this.timer) {
|
||||
this.createTimer();
|
||||
}
|
||||
}
|
||||
|
||||
createTimer() {
|
||||
let timeLeft = this.sleepTime - Cu.now();
|
||||
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this.timer.init(() => this.timeout(), timeLeft, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
timeout() {
|
||||
this.clearTimer();
|
||||
if (!this.keepAlive.size) {
|
||||
if (Cu.now() < this.sleepTime) {
|
||||
this.createTimer();
|
||||
} else {
|
||||
this.extension.terminateBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.backgroundPage = class extends ExtensionAPI {
|
||||
async onManifestEntry() {
|
||||
let { extension } = this;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ add_setup(async () => {
|
|||
// e.g. see Bug 1803801) have a higher chance that the test extension may have hit the
|
||||
// idle timeout and being suspended by the time the test is going to trigger API method
|
||||
// calls through test API events (which do not expect the lifetime of the event page).
|
||||
Services.prefs.setBoolPref("extensions.background.idle.enabled", false);
|
||||
Services.prefs.setIntPref("extensions.background.idle.timeout", 300_000);
|
||||
|
||||
// NOTE: reduce the static rules limits to reduce the amount of time needed to run
|
||||
// this xpcshell test.
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ add_task(
|
|||
"background-script-reset-idle"
|
||||
);
|
||||
|
||||
equal(resetData.reason, "parentApiCall", "Got the expected idle reset.");
|
||||
equal(resetData.reason, "parentapicall", "Got the expected idle reset.");
|
||||
|
||||
await promiseExtensionEvent(extension, "shutdown-background-script");
|
||||
|
||||
|
|
@ -640,7 +640,7 @@ add_task(
|
|||
Assert.deepEqual(
|
||||
resetIdleData,
|
||||
{
|
||||
reason: "pendingListeners",
|
||||
reason: "listeners",
|
||||
pendingListeners: 2,
|
||||
},
|
||||
"Got the expected idle reset reason and pendingListeners count"
|
||||
|
|
@ -754,7 +754,7 @@ add_task(
|
|||
Assert.deepEqual(
|
||||
resetIdleData,
|
||||
{
|
||||
reason: "pendingListeners",
|
||||
reason: "listeners",
|
||||
pendingListeners: 1,
|
||||
},
|
||||
"Got the expected idle reset reason and pendingListeners count"
|
||||
|
|
|
|||
|
|
@ -325,7 +325,9 @@ async function test_create_new_streamfilter_while_suspending({
|
|||
await extension.awaitMessage("suspend-listener");
|
||||
|
||||
info("Simulated idle timeout canceled");
|
||||
extension.extension.emit("background-script-reset-idle");
|
||||
extension.extension.emit("background-script-reset-idle", {
|
||||
reason: "other/simulate-idle-reset",
|
||||
});
|
||||
await extension.awaitMessage("suspend-canceled-listener");
|
||||
|
||||
await extension.unload();
|
||||
|
|
|
|||
Loading…
Reference in a new issue