Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
ContextualIdentityService:
"moz-src:///toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs",
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
});
const DEBOUNCE_MS = 30000;
const PREF_EPHEMERAL_IDS = "browser.policies.ephemeralContainerIds";
export const EphemeralContainerWatcher = {
_ephemeralIds: new Set(),
_deferredTasks: new Map(),
_initialized: false,
_shutdownBlockerAdded: false,
init(ephemeralUserContextIds) {
this._ephemeralIds = ephemeralUserContextIds;
this._cancelStaleTimers();
// On first init, clear data for containers that were ephemeral in the
// previous session but are no longer in the current policy. Handles the
// case where we crashed before the shutdown blocker could run clearAll.
if (!this._initialized) {
this._clearStaleIds();
}
this._persistIds();
if (this._initialized) {
return;
}
this._initialized = true;
lazy.EveryWindow.registerCallback(
"EphemeralContainerWatcher",
win => this._onWindowInit(win),
(win, closing) => this._onWindowUninit(win, closing)
);
if (!this._shutdownBlockerAdded) {
this._shutdownBlockerAdded = true;
lazy.AsyncShutdown.profileBeforeChange.addBlocker(
"EphemeralContainerWatcher: clear ephemeral container data",
() => this.clearAll()
);
}
},
destroy() {
if (!this._initialized) {
return;
}
this._initialized = false;
this._ephemeralIds.clear();
this._cancelStaleTimers();
this._persistIds();
lazy.EveryWindow.unregisterCallback("EphemeralContainerWatcher");
},
_cancelStaleTimers() {
for (let [userContextId, task] of this._deferredTasks) {
if (!this._ephemeralIds.has(userContextId)) {
task.disarm();
this._deferredTasks.delete(userContextId);
}
}
},
_onWindowInit(win) {
win.gBrowser.tabContainer.addEventListener("TabClose", this);
},
_onWindowUninit(win, closing) {
win.gBrowser.tabContainer.removeEventListener("TabClose", this);
if (closing) {
for (let userContextId of this._ephemeralIds) {
this._scheduleCheck(userContextId);
}
}
},
handleEvent(event) {
let tab = event.target;
let userContextId = tab.getAttribute("usercontextid");
if (!userContextId) {
return;
}
userContextId = parseInt(userContextId, 10);
if (this._ephemeralIds.has(userContextId)) {
this._scheduleCheck(userContextId);
}
},
_scheduleCheck(userContextId) {
let task = this._deferredTasks.getOrInsertComputed(userContextId, () => {
return new lazy.DeferredTask(() => {
if (
lazy.ContextualIdentityService.countContainerTabs(userContextId) == 0
) {
return this._clearData(userContextId);
}
return undefined;
}, DEBOUNCE_MS);
});
task.disarm();
task.arm();
},
_clearData(userContextId) {
return new Promise(resolve => {
Services.clearData.deleteDataFromOriginAttributesPattern(
{ userContextId },
resolve
);
});
},
clearAll() {
let promises = [];
for (let userContextId of this._ephemeralIds) {
promises.push(this._clearData(userContextId));
}
this.destroy();
return Promise.all(promises);
},
_clearStaleIds() {
let staleIds;
try {
staleIds = JSON.parse(
Services.prefs.getStringPref(PREF_EPHEMERAL_IDS, "[]")
);
} catch (e) {
return;
}
for (let id of staleIds) {
if (!this._ephemeralIds.has(id)) {
this._clearData(id);
}
}
},
_persistIds() {
Services.prefs.setStringPref(
PREF_EPHEMERAL_IDS,
JSON.stringify([...this._ephemeralIds])
);
},
async flushPendingChecks() {
let tasks = [...this._deferredTasks.values()];
this._deferredTasks.clear();
await Promise.all(tasks.map(t => t.finalize()));
},
};