mirror of
https://github.com/zen-browser/components.git
synced 2025-07-08 00:59:57 +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