From 142e3b09f8fe0414fec4081d38171fad60ae3731 Mon Sep 17 00:00:00 2001 From: Martynas Bagdonas Date: Wed, 20 Apr 2022 14:14:50 +0700 Subject: [PATCH] Unload tabs (#2500) - Keep all tabs unloaded on Zotero opening. - Keep loaded only the last five selected tabs. - Keep loaded only in the last 24h selected tabs. Fixes #2383 --- chrome/content/zotero/contextPane.js | 6 +- chrome/content/zotero/tabs.js | 93 ++++++++++++++++++++++----- chrome/content/zotero/xpcom/reader.js | 1 + 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/chrome/content/zotero/contextPane.js b/chrome/content/zotero/contextPane.js index 0e2672846c..98aaa4a710 100644 --- a/chrome/content/zotero/contextPane.js +++ b/chrome/content/zotero/contextPane.js @@ -181,13 +181,13 @@ var ZoteroContextPane = new function () { // It seems that changing `hidden` or `collapsed` values might // be related with significant slow down when there are too many // DOM nodes (i.e. 10k notes) - if (Zotero_Tabs.selectedIndex == 0) { + if (Zotero_Tabs.selectedType == 'library') { _contextPaneSplitter.setAttribute('hidden', true); _contextPane.setAttribute('collapsed', true); _tabToolbar.hidden = true; _tabCover.hidden = true; } - else { + else if (Zotero_Tabs.selectedType == 'reader') { var reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); if (reader) { _tabCover.hidden = false; @@ -217,7 +217,7 @@ var ZoteroContextPane = new function () { }); _tabToolbar.hidden = false; } - + _selectItemContext(ids[0]); _update(); } diff --git a/chrome/content/zotero/tabs.js b/chrome/content/zotero/tabs.js index 1c0eb7cea9..a661f94b44 100644 --- a/chrome/content/zotero/tabs.js +++ b/chrome/content/zotero/tabs.js @@ -30,11 +30,18 @@ var React = require('react'); var ReactDOM = require('react-dom'); import TabBar from 'components/tabBar'; +const MAX_LOADED_TABS = 5; +const UNLOAD_UNUSED_AFTER = 86400; // 24h + var Zotero_Tabs = new function () { Object.defineProperty(this, 'selectedID', { get: () => this._selectedID }); + Object.defineProperty(this, 'selectedType', { + get: () => this._getTab(this._selectedID).tab.type + }); + Object.defineProperty(this, 'selectedIndex', { get: () => this._getTab(this._selectedID).tabIndex }); @@ -53,6 +60,10 @@ var Zotero_Tabs = new function () { this._prevSelectedID = null; this._history = []; + this._unloadInterval = setInterval(() => { + this.unloadUnusedTabs(); + }, 60000); // Trigger every minute + this._getTab = function (id) { var tabIndex = this._tabs.findIndex(tab => tab.id == id); return { tab: this._tabs[tabIndex], tabIndex }; @@ -90,8 +101,12 @@ var Zotero_Tabs = new function () { this.getState = function () { return this._tabs.map((tab) => { + let type = tab.type; + if (type === 'reader-unloaded') { + type = 'reader'; + } var o = { - type: tab.type, + type, title: tab.title, }; if (tab.data) { @@ -103,21 +118,21 @@ var Zotero_Tabs = new function () { return o; }); }; - - this.restoreState = function(tabs) { - for (let tab of tabs) { + + this.restoreState = function (tabs) { + for (let i = 0; i < tabs.length; i++) { + let tab = tabs[i]; if (tab.type === 'library') { this.rename('zotero-pane', tab.title); } else if (tab.type === 'reader') { if (Zotero.Items.exists(tab.data.itemID)) { - Zotero.Reader.open(tab.data.itemID, - null, - { - title: tab.title, - openInBackground: !tab.selected - } - ); + this.add({ + type: 'reader-unloaded', + title: tab.title, + index: i, + data: tab.data + }); } } } @@ -292,10 +307,22 @@ var Zotero_Tabs = new function () { * @param {Boolean} reopening */ this.select = function (id, reopening) { - var { tab } = this._getTab(id); + var { tab, tabIndex } = this._getTab(id); if (!tab || tab.id === this._selectedID) { return; } + if (this._selectedID) { + let { tab: selectedTab } = this._getTab(this._selectedID); + selectedTab.timeUnselected = Zotero.Date.getUnixTimestamp(); + } + if (tab.type === 'reader-unloaded') { + this.close(tab.id); + Zotero.Reader.open(tab.data.itemID, null, { + title: tab.title, + tabIndex + }); + return; + } if (this._selectedID === 'zotero-pane') { var { tab: selectedTab } = this._getTab(this._selectedID); selectedTab.lastFocusedElement = document.activeElement; @@ -312,12 +339,18 @@ var Zotero_Tabs = new function () { } tab.lastFocusedElement = null; } - let tabNode = document.querySelector(`.tab[data-id="${tab.id}"]`); - let tabsContainerNode = document.querySelector('#tab-bar-container .tabs'); - document.querySelector(`.tab[data-id="${tab.id}"]`).scrollIntoView({ behavior: 'smooth' }); + // Allow React to create a tab node + setTimeout(() => { + document.querySelector(`.tab[data-id="${tab.id}"]`).scrollIntoView({ behavior: 'smooth' }); + }); // Border is not included when scrolling element node into view, therefore we do it manually. // TODO: `scroll-padding` since Firefox 68 can probably be used instead setTimeout(() => { + let tabNode = document.querySelector(`.tab[data-id="${tab.id}"]`); + if (!tabNode) { + return; + } + let tabsContainerNode = document.querySelector('#tab-bar-container .tabs'); if (tabNode.offsetLeft + tabNode.offsetWidth - tabsContainerNode.offsetWidth + 1 >= tabsContainerNode.scrollLeft) { document.querySelector('#tab-bar-container .tabs').scrollLeft += 1; } @@ -325,6 +358,36 @@ var Zotero_Tabs = new function () { document.querySelector('#tab-bar-container .tabs').scrollLeft -= 1; } }, 500); + tab.timeSelected = Zotero.Date.getUnixTimestamp(); + this.unloadUnusedTabs(); + }; + + this.unload = function (id) { + var { tab, tabIndex } = this._getTab(id); + if (!tab || tab.id === this._selectedID || tab.type !== 'reader') { + return; + } + this.close(tab.id); + this.add({ + type: 'reader-unloaded', + title: tab.title, + index: tabIndex, + data: tab.data + }); + }; + + this.unloadUnusedTabs = function () { + for (let tab of this._tabs) { + if (Zotero.Date.getUnixTimestamp() - tab.timeUnselected > UNLOAD_UNUSED_AFTER) { + this.unload(tab.id); + } + } + let tabs = this._tabs.slice().filter(x => x.type === 'reader'); + tabs.sort((a, b) => b.timeUnselected - a.timeUnselected); + tabs = tabs.slice(MAX_LOADED_TABS); + for (let tab of tabs) { + this.unload(tab.id); + } }; /** diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js index 380b079737..77434ff0a5 100644 --- a/chrome/content/zotero/xpcom/reader.js +++ b/chrome/content/zotero/xpcom/reader.js @@ -969,6 +969,7 @@ class ReaderTab extends ReaderInstance { // i.e. note-editor. There should be a better way to solve this this._window.addEventListener('pointerup', (event) => { if (this._window.Zotero_Tabs.selectedID === this.tabID + && this._iframeWindow && event.target && event.target.closest && !event.target.closest('#outerContainer')) {