mirror of
https://github.com/zen-browser/components.git
synced 2025-07-08 14:59:59 +02:00
Refactor ZenTabUnloader to add support for unloading inactive tabs
This commit refactors the ZenTabUnloader class to add support for unloading inactive tabs. It introduces a new class called ZenTabsIntervalUnloader, which periodically checks for inactive tabs and discards them if they meet certain criteria. The criteria include being inactive for a specified timeout period and not meeting any exclusion URLs. The commit also adds a new class called ZenTabsObserver, which listens to various tab events and notifies the ZenTabUnloader when a tab's activity changes. These changes improve the efficiency of tab management and help reduce resource usage by unloading tabs that are not actively being used.
This commit is contained in:
parent
b63df576e3
commit
a615de5b6f
1 changed files with 251 additions and 0 deletions
251
src/ZenTabUnloader.mjs
Normal file
251
src/ZenTabUnloader.mjs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
|
||||||
|
{
|
||||||
|
const ZEN_TAB_UNLOADER_PREF = "zen.tab-unloader.enabled";
|
||||||
|
const ZEN_TAB_UNLOADER_TIMEOUT_PREF = "zen.tab-unloader.timeout";
|
||||||
|
|
||||||
|
const lazy = {};
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyPreferenceGetter(
|
||||||
|
lazy,
|
||||||
|
"zenTabUnloaderEnabled",
|
||||||
|
"zen.tab-unloader.enabled",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyPreferenceGetter(
|
||||||
|
lazy,
|
||||||
|
"zenTabUnloaderTimeout",
|
||||||
|
"zen.tab-unloader.timeout-minutes",
|
||||||
|
5
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyPreferenceGetter(
|
||||||
|
lazy,
|
||||||
|
"zenTabUnloaderExcludedUrls",
|
||||||
|
"zen.tab-unloader.excluded-urls",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
const ZEN_TAB_UNLOADER_DEFAULT_EXCLUDED_URLS = [
|
||||||
|
"^about:",
|
||||||
|
"^chrome:",
|
||||||
|
"^devtools:",
|
||||||
|
"^file:",
|
||||||
|
"^resource:",
|
||||||
|
"^view-source:",
|
||||||
|
"^view-image:",
|
||||||
|
];
|
||||||
|
|
||||||
|
class ZenTabsObserver {
|
||||||
|
static ALL_EVENTS = [
|
||||||
|
"TabAttrModified",
|
||||||
|
"TabPinned",
|
||||||
|
"TabUnpinned",
|
||||||
|
"TabBrowserInserted",
|
||||||
|
"TabBrowserDiscarded",
|
||||||
|
"TabShow",
|
||||||
|
"TabHide",
|
||||||
|
"TabOpen",
|
||||||
|
"TabClose",
|
||||||
|
"TabSelect",
|
||||||
|
"TabMultiSelect",
|
||||||
|
]
|
||||||
|
|
||||||
|
#listeners = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#listenAllEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
#listenAllEvents() {
|
||||||
|
const eventListener = this.#eventListener.bind(this);
|
||||||
|
for (const event of ZenTabsObserver.ALL_EVENTS) {
|
||||||
|
window.addEventListener(event, eventListener);
|
||||||
|
}
|
||||||
|
window.addEventListener("unload", () => {
|
||||||
|
for (const event of ZenTabsObserver.ALL_EVENTS) {
|
||||||
|
window.removeEventListener(event, eventListener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#eventListener(event) {
|
||||||
|
for (const listener of this.#listeners) {
|
||||||
|
listener(event.type, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addTabsListener(listener) {
|
||||||
|
this.#listeners.push(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZenTabsIntervalUnloader {
|
||||||
|
static INTERVAL = 1000 * 60; // 1 minute
|
||||||
|
|
||||||
|
interval = null;
|
||||||
|
unloader = null;
|
||||||
|
|
||||||
|
#excludedUrls = [];
|
||||||
|
#compiledExcludedUrls = [];
|
||||||
|
|
||||||
|
constructor(unloader) {
|
||||||
|
this.unloader = unloader;
|
||||||
|
this.interval = setInterval(this.intervalListener.bind(this), ZenTabsIntervalUnloader.INTERVAL);
|
||||||
|
this.#excludedUrls = this.lazyExcludeUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lazyExcludeUrls() {
|
||||||
|
return [
|
||||||
|
...ZEN_TAB_UNLOADER_DEFAULT_EXCLUDED_URLS,
|
||||||
|
...lazy.zenTabUnloaderExcludedUrls.split(",").map(url => url.trim())
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
arraysEqual(a, b) {
|
||||||
|
if (a === b) return true;
|
||||||
|
if (a == null || b == null) return false;
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
|
||||||
|
// If you don't care about the order of the elements inside
|
||||||
|
// the array, you should sort both arrays here.
|
||||||
|
// Please note that calling sort on an array will modify that array.
|
||||||
|
// you might want to clone your array first.
|
||||||
|
|
||||||
|
for (var i = 0; i < a.length; ++i) {
|
||||||
|
if (a[i] !== b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get excludedUrls() {
|
||||||
|
// Check if excludedrls is the same as the pref value
|
||||||
|
const excludedUrls = this.lazyExcludeUrls;
|
||||||
|
if (!this.arraysEqual(this.#excludedUrls, excludedUrls) || !this.#compiledExcludedUrls.length) {
|
||||||
|
this.#excludedUrls = excludedUrls;
|
||||||
|
this.#compiledExcludedUrls = excludedUrls.map(url => new RegExp(url));
|
||||||
|
}
|
||||||
|
return this.#compiledExcludedUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
intervalListener() {
|
||||||
|
const currentTimestamp = Date.now();
|
||||||
|
const excludedUrls = this.excludedUrls;
|
||||||
|
for (const tab of this.unloader.tabs) {
|
||||||
|
if (this.unloader.canUnloadTab(tab, currentTimestamp, excludedUrls)) {
|
||||||
|
console.debug("ZenTabUnloader: Discarding tab", tab);
|
||||||
|
tab.ownerGlobal.gBrowser.discardBrowser(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ZenTabUnloader {
|
||||||
|
static ACTIVITY_MODIFIERS = [
|
||||||
|
"muted",
|
||||||
|
"soundplaying",
|
||||||
|
"label",
|
||||||
|
"attention",
|
||||||
|
]
|
||||||
|
|
||||||
|
allTabs = [];
|
||||||
|
constructor() {
|
||||||
|
if (!lazy.zenTabUnloaderEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.observer = new ZenTabsObserver();
|
||||||
|
this.intervalUnloader = new ZenTabsIntervalUnloader(this);
|
||||||
|
this.allTabs = gBrowser.tabs;
|
||||||
|
this.observer.addTabsListener(this.onTabEvent.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onTabEvent(action, event) {
|
||||||
|
const tab = event.target;
|
||||||
|
switch (action) {
|
||||||
|
case "TabPinned":
|
||||||
|
case "TabUnpinned":
|
||||||
|
case "TabBrowserInserted":
|
||||||
|
case "TabBrowserDiscarded":
|
||||||
|
case "TabShow":
|
||||||
|
case "TabHide":
|
||||||
|
break;
|
||||||
|
case "TabAttrModified":
|
||||||
|
this.handleTabAttrModified(tab, event);
|
||||||
|
break;
|
||||||
|
case "TabOpen":
|
||||||
|
this.handleTabOpen(tab);
|
||||||
|
break;
|
||||||
|
case "TabClose":
|
||||||
|
this.handleTabClose(tab);
|
||||||
|
break;
|
||||||
|
case "TabSelect":
|
||||||
|
case "TabMultiSelect":
|
||||||
|
this.updateTabActivity(tab);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("ZenTabUnloader: Unhandled tab event", action);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLocationChange(browser) {
|
||||||
|
const tab = browser.ownerGlobal.gBrowser.getTabForBrowser(browser);
|
||||||
|
this.updateTabActivity(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabClose(tab) {
|
||||||
|
this.allTabs = this.allTabs.filter(t => t !== tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabOpen(tab) {
|
||||||
|
if (!lazy.zenTabUnloaderEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.allTabs.includes(tab)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.allTabs.push(tab);
|
||||||
|
this.updateTabActivity(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabAttrModified(tab, event) {
|
||||||
|
for (const modifier of ZenTabUnloader.ACTIVITY_MODIFIERS) {
|
||||||
|
if (event.detail.changed.includes(modifier)) {
|
||||||
|
this.updateTabActivity(tab);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTabActivity(tab) {
|
||||||
|
const currentTimestamp = Date.now();
|
||||||
|
tab.lastActivity = currentTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tabs() {
|
||||||
|
return this.allTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
canUnloadTab(tab, currentTimestamp, excludedUrls) {
|
||||||
|
if (tab.pinned || tab.selected || tab.multiselected
|
||||||
|
|| tab.hasAttribute("busy") || tab.hasAttribute("pending")
|
||||||
|
|| !tab.linkedPanel || tab.splitView || tab.attention
|
||||||
|
|| excludedUrls.some(url => url.test(tab.linkedBrowser.currentURI.spec))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const lastActivity = tab.lastActivity;
|
||||||
|
if (!lastActivity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const diff = currentTimestamp - lastActivity;
|
||||||
|
// Check if the tab has been inactive for more than the timeout
|
||||||
|
if (diff < lazy.zenTabUnloaderTimeout * 60 * 1000) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.gZenTabUnloader = new ZenTabUnloader();
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue