diff --git a/locales/en-US/browser/browser/preferences/zen-preferences.ftl b/locales/en-US/browser/browser/preferences/zen-preferences.ftl
index ea49e2517..d053124e2 100644
--- a/locales/en-US/browser/browser/preferences/zen-preferences.ftl
+++ b/locales/en-US/browser/browser/preferences/zen-preferences.ftl
@@ -56,6 +56,9 @@ pane-settings-workspaces-title = Workspaces
zen-tabs-unloader-enabled =
.label = Enable Tab Unloader
+zen-tabs-close-on-back-with-no-history =
+ .label = Close tab and switch to its owner tab (or most recently used tab) when going back with no history
+
zen-look-and-feel-compact-toolbar-themed =
.label = Use themed background for compact toolbar
diff --git a/prefs/zen/zen.yaml b/prefs/zen/zen.yaml
index 469622931..c44a0e626 100644
--- a/prefs/zen/zen.yaml
+++ b/prefs/zen/zen.yaml
@@ -34,3 +34,6 @@
- name: zen.tabs.open-pinned-in-new-tab
value: true
+
+- name: zen.tabs.close-on-back-with-no-history
+ value: true
diff --git a/src/browser/base/content/browser-commands-js.patch b/src/browser/base/content/browser-commands-js.patch
index a475e0749..3c0aabfee 100644
--- a/src/browser/base/content/browser-commands-js.patch
+++ b/src/browser/base/content/browser-commands-js.patch
@@ -1,8 +1,19 @@
diff --git a/browser/base/content/browser-commands.js b/browser/base/content/browser-commands.js
-index 939ca497b882b3f4200141ba1b6764fb5c846f45..36c830a503b004c0a332340f686c86cad68e9381 100644
+index 939ca497b882b3f4200141ba1b6764fb5c846f45..779cba5ff3df856a246321a36caa3725c054a314 100644
--- a/browser/base/content/browser-commands.js
+++ b/browser/base/content/browser-commands.js
-@@ -315,6 +315,10 @@ var BrowserCommands = {
+@@ -14,6 +14,10 @@ var BrowserCommands = {
+ const where = BrowserUtils.whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
++ if (!gBrowser.webNavigation.canGoBack && gZenCommonActions.shouldCloseTabOnBack()) {
++ gBrowser.removeTab(gBrowser.selectedTab);
++ return;
++ }
+ try {
+ gBrowser.goBack();
+ } catch (ex) {}
+@@ -315,6 +319,10 @@ var BrowserCommands = {
}
}
@@ -13,7 +24,7 @@ index 939ca497b882b3f4200141ba1b6764fb5c846f45..36c830a503b004c0a332340f686c86ca
// A notification intended to be useful for modular peformance tracking
// starting as close as is reasonably possible to the time when the user
// expressed the intent to open a new tab. Since there are a lot of
-@@ -399,6 +403,11 @@ var BrowserCommands = {
+@@ -399,6 +407,11 @@ var BrowserCommands = {
return;
}
@@ -25,7 +36,7 @@ index 939ca497b882b3f4200141ba1b6764fb5c846f45..36c830a503b004c0a332340f686c86ca
// Keyboard shortcuts that would close a tab that is pinned select the first
// unpinned tab instead.
if (
-@@ -406,8 +415,8 @@ var BrowserCommands = {
+@@ -406,8 +419,8 @@ var BrowserCommands = {
(event.ctrlKey || event.metaKey || event.altKey) &&
gBrowser.selectedTab.pinned
) {
diff --git a/src/browser/base/content/browser-gestureSupport-js.patch b/src/browser/base/content/browser-gestureSupport-js.patch
new file mode 100644
index 000000000..ef205d5df
--- /dev/null
+++ b/src/browser/base/content/browser-gestureSupport-js.patch
@@ -0,0 +1,13 @@
+diff --git a/browser/base/content/browser-gestureSupport.js b/browser/base/content/browser-gestureSupport.js
+index a28d54bf72c0e6495b9586f220d1859aac794936..66154668b9f85ffbaacea1e8351370659260227b 100644
+--- a/browser/base/content/browser-gestureSupport.js
++++ b/browser/base/content/browser-gestureSupport.js
+@@ -832,7 +832,7 @@ var gHistorySwipeAnimation = {
+ * @return true if there is a previous page in history, false otherwise.
+ */
+ canGoBack: function HSA_canGoBack() {
+- return gBrowser.webNavigation.canGoBack;
++ return gBrowser.webNavigation.canGoBack || gZenCommonActions.shouldCloseTabOnBack();
+ },
+
+ /**
diff --git a/src/browser/base/content/browser-js.patch b/src/browser/base/content/browser-js.patch
index 8d2658275..4165d22e5 100644
--- a/src/browser/base/content/browser-js.patch
+++ b/src/browser/base/content/browser-js.patch
@@ -1,5 +1,5 @@
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
-index b4b79e7fb3228ba91bd8afa08659be0d88883725..9e1d59b6a736107e55bda73686a4a627e4c0ba7c 100644
+index b4b79e7fb3228ba91bd8afa08659be0d88883725..b4801e2a3076139622d58f81943e61cd61ee1828 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -31,6 +31,7 @@ ChromeUtils.defineESModuleGetters(this, {
@@ -10,7 +10,21 @@ index b4b79e7fb3228ba91bd8afa08659be0d88883725..9e1d59b6a736107e55bda73686a4a627
DevToolsSocketStatus:
"resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
-@@ -2298,6 +2299,8 @@ var XULBrowserWindow = {
+@@ -822,7 +823,12 @@ function UpdateBackForwardCommands(aWebNavigation) {
+
+ var backDisabled = backCommand.hasAttribute("disabled");
+ var forwardDisabled = forwardCommand.hasAttribute("disabled");
+- if (backDisabled == aWebNavigation.canGoBack) {
++ var canGoBack = aWebNavigation.canGoBack;
++ if (!canGoBack) {
++ canGoBack = gZenCommonActions.shouldCloseTabOnBack();
++ }
++
++ if (backDisabled == canGoBack) {
+ if (backDisabled) {
+ backCommand.removeAttribute("disabled");
+ } else {
+@@ -2298,6 +2304,8 @@ var XULBrowserWindow = {
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
@@ -19,7 +33,7 @@ index b4b79e7fb3228ba91bd8afa08659be0d88883725..9e1d59b6a736107e55bda73686a4a627
PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
if (!gMultiProcessBrowser) {
-@@ -3809,7 +3812,7 @@ function warnAboutClosingWindow() {
+@@ -3809,7 +3817,7 @@ function warnAboutClosingWindow() {
if (!isPBWindow && !toolbar.visible) {
return gBrowser.warnAboutClosingTabs(
@@ -28,7 +42,7 @@ index b4b79e7fb3228ba91bd8afa08659be0d88883725..9e1d59b6a736107e55bda73686a4a627
gBrowser.closingTabsEnum.ALL
);
}
-@@ -3849,7 +3852,7 @@ function warnAboutClosingWindow() {
+@@ -3849,7 +3857,7 @@ function warnAboutClosingWindow() {
return (
isPBWindow ||
gBrowser.warnAboutClosingTabs(
@@ -37,7 +51,7 @@ index b4b79e7fb3228ba91bd8afa08659be0d88883725..9e1d59b6a736107e55bda73686a4a627
gBrowser.closingTabsEnum.ALL
)
);
-@@ -3874,7 +3877,7 @@ function warnAboutClosingWindow() {
+@@ -3874,7 +3882,7 @@ function warnAboutClosingWindow() {
AppConstants.platform != "macosx" ||
isPBWindow ||
gBrowser.warnAboutClosingTabs(
@@ -46,7 +60,7 @@ index b4b79e7fb3228ba91bd8afa08659be0d88883725..9e1d59b6a736107e55bda73686a4a627
gBrowser.closingTabsEnum.ALL
)
);
-@@ -4796,6 +4799,9 @@ var ConfirmationHint = {
+@@ -4796,6 +4804,9 @@ var ConfirmationHint = {
MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
MozXULElement.insertFTLIfNeeded("browser/confirmationHints.ftl");
document.l10n.setAttributes(this._message, messageId, options.l10nArgs);
diff --git a/src/browser/components/preferences/zenTabsManagement.inc.xhtml b/src/browser/components/preferences/zenTabsManagement.inc.xhtml
index b8693566f..2943b863b 100644
--- a/src/browser/components/preferences/zenTabsManagement.inc.xhtml
+++ b/src/browser/components/preferences/zenTabsManagement.inc.xhtml
@@ -26,6 +26,9 @@
+
f.apply(this, args), delay);
};
},
+
+ /**
+ * Determines if a tab should be closed when navigating back with no history.
+ * Only tabs with an owner that are not pinned and not empty are eligible.
+ * Respects the user preference zen.tabs.close-on-back-with-no-history.
+ *
+ * @return {boolean} True if the tab should be closed on back
+ */
+ shouldCloseTabOnBack() {
+ if (!Services.prefs.getBoolPref('zen.tabs.close-on-back-with-no-history', true)) {
+ return false;
+ }
+ const tab = gBrowser.selectedTab;
+ return Boolean(tab.owner && !tab.pinned && !tab.hasAttribute('zen-empty-tab'));
+ },
};
diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs
index eebfadf06..779f3566b 100644
--- a/src/zen/tabs/ZenPinnedTabManager.mjs
+++ b/src/zen/tabs/ZenPinnedTabManager.mjs
@@ -778,10 +778,6 @@
return;
}
const existingPin = this._pinsCache.find((p) => p.uuid === pin.uuid);
- if (existingPin && existingPin === pin) {
- // We want to avoid unnecessary writes
- return;
- }
if (existingPin) {
Object.assign(existingPin, pin);
} else {
diff --git a/src/zen/tabs/ZenPinnedTabsStorage.mjs b/src/zen/tabs/ZenPinnedTabsStorage.mjs
index 425dbf2d1..bc11213f7 100644
--- a/src/zen/tabs/ZenPinnedTabsStorage.mjs
+++ b/src/zen/tabs/ZenPinnedTabsStorage.mjs
@@ -2,6 +2,8 @@
// 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/.
var ZenPinnedTabsStorage = {
+ _saveCache: [],
+
async init() {
await this._ensureTable();
},
@@ -74,6 +76,22 @@ var ZenPinnedTabsStorage = {
},
async savePin(pin, notifyObservers = true) {
+ // If we find the exact same pin in the cache, skip saving
+ const existingIndex = this._saveCache.findIndex((cachedPin) => cachedPin.uuid === pin.uuid);
+ if (existingIndex !== -1) {
+ const existingPin = this._saveCache[existingIndex];
+ const isSame = Object.keys(pin).every((key) => pin[key] === existingPin[key]);
+ if (isSame) {
+ return; // No changes, skip saving
+ } else {
+ // Update the cached pin
+ this._saveCache[existingIndex] = pin;
+ }
+ } else {
+ // Add to cache
+ this._saveCache.push(pin);
+ }
+
const changedUUIDs = new Set();
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.savePin', async (db) => {
@@ -389,6 +407,11 @@ var ZenPinnedTabsStorage = {
},
async removePin(uuid, notifyObservers = true) {
+ const cachedIndex = this._saveCache.findIndex((cachedPin) => cachedPin.uuid === uuid);
+ if (cachedIndex !== -1) {
+ this._saveCache.splice(cachedIndex, 1);
+ }
+
const changedUUIDs = [uuid];
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.removePin', async (db) => {