mirror of
https://github.com/zen-browser/desktop.git
synced 2026-03-29 03:41:51 +00:00
feat: Add "Separate from pinned tab" when resetting pinned tab with CMD, p=#12710
This commit is contained in:
@@ -46,5 +46,6 @@ tabbrowser-reset-pin-button =
|
||||
zen-tab-sublabel =
|
||||
{ $tabSubtitle ->
|
||||
[zen-default-pinned] Back to pinned url
|
||||
[zen-default-pinned-cmd] Separate from pinned tab
|
||||
*[other] { $tabSubtitle }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js
|
||||
index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb894fa1d4 100644
|
||||
index 836bee14d2b63604688ebe477a5d915a5e99b305..5f60aa3bedd4f80b887ea3e050fd86a21a6b280a 100644
|
||||
--- a/browser/components/tabbrowser/content/tab.js
|
||||
+++ b/browser/components/tabbrowser/content/tab.js
|
||||
@@ -21,6 +21,7 @@
|
||||
@@ -110,7 +110,26 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
|
||||
}
|
||||
|
||||
get splitview() {
|
||||
@@ -489,6 +515,8 @@
|
||||
@@ -444,6 +470,10 @@
|
||||
: this;
|
||||
gBrowser.warmupTab(tabToWarm);
|
||||
|
||||
+ if (event.target.classList.contains("tab-reset-pin-button")) {
|
||||
+ gZenPinnedTabManager.onResetPinButtonMouseOver(this, event);
|
||||
+ }
|
||||
+
|
||||
// If the previous target wasn't part of this tab then this is a mouseenter event.
|
||||
if (!this.contains(event.relatedTarget)) {
|
||||
this._mouseenter();
|
||||
@@ -455,6 +485,7 @@
|
||||
if (!this.contains(event.relatedTarget)) {
|
||||
this._mouseleave();
|
||||
}
|
||||
+ gZenPinnedTabManager.onResetPinButtonMouseOut(this);
|
||||
}
|
||||
|
||||
on_dragstart(event) {
|
||||
@@ -489,6 +520,8 @@
|
||||
this.style.MozUserFocus = "ignore";
|
||||
} else if (
|
||||
event.target.classList.contains("tab-close-button") ||
|
||||
@@ -119,7 +138,7 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
|
||||
event.target.classList.contains("tab-icon-overlay") ||
|
||||
event.target.classList.contains("tab-audio-button")
|
||||
) {
|
||||
@@ -543,6 +571,10 @@
|
||||
@@ -543,16 +576,21 @@
|
||||
this.style.MozUserFocus = "";
|
||||
}
|
||||
|
||||
@@ -130,7 +149,40 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
|
||||
on_click(event) {
|
||||
if (event.button != 0) {
|
||||
return;
|
||||
@@ -603,6 +635,14 @@
|
||||
}
|
||||
|
||||
- if (event.getModifierState("Accel") || event.shiftKey) {
|
||||
+ if (event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
+ !event.getModifierState("Accel") &&
|
||||
gBrowser.multiSelectedTabsCount > 0 &&
|
||||
!event.target.classList.contains("tab-close-button") &&
|
||||
!event.target.classList.contains("tab-icon-overlay") &&
|
||||
@@ -564,8 +602,9 @@
|
||||
}
|
||||
|
||||
if (
|
||||
- event.target.classList.contains("tab-icon-overlay") ||
|
||||
- event.target.classList.contains("tab-audio-button")
|
||||
+ !event.getModifierState("Accel") &&
|
||||
+ (event.target.classList.contains("tab-icon-overlay") ||
|
||||
+ event.target.classList.contains("tab-audio-button"))
|
||||
) {
|
||||
if (this.activeMediaBlocked) {
|
||||
if (this.multiselected) {
|
||||
@@ -583,7 +622,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
- if (event.target.classList.contains("tab-close-button")) {
|
||||
+ if (!event.getModifierState("Accel") && event.target.classList.contains("tab-close-button")) {
|
||||
if (this.multiselected) {
|
||||
gBrowser.removeMultiSelectedTabs(
|
||||
lazy.TabMetrics.userTriggeredContext(
|
||||
@@ -603,6 +642,14 @@
|
||||
// (see tabbrowser-tabs 'click' handler).
|
||||
gBrowser.tabContainer._blockDblClick = true;
|
||||
}
|
||||
@@ -138,14 +190,14 @@ index 836bee14d2b63604688ebe477a5d915a5e99b305..7e105a1ae07657b0a0e664a8e3d9d2eb
|
||||
+ if (event.target.classList.contains("tab-reset-pin-button")) {
|
||||
+ gZenPinnedTabManager._onTabResetPinButton(event, this, 'reset');
|
||||
+ gBrowser.tabContainer._blockDblClick = true;
|
||||
+ } else if (event.target.classList.contains("tab-reset-button")) {
|
||||
+ } else if (!event.getModifierState("Accel") && event.target.classList.contains("tab-reset-button")) {
|
||||
+ gZenPinnedTabManager.onCloseTabShortcut(event, this);
|
||||
+ gBrowser.tabContainer._blockDblClick = true;
|
||||
+ }
|
||||
}
|
||||
|
||||
on_dblclick(event) {
|
||||
@@ -626,6 +666,8 @@
|
||||
@@ -626,6 +673,8 @@
|
||||
animate: true,
|
||||
triggeringEvent: event,
|
||||
});
|
||||
|
||||
@@ -115,7 +115,14 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
|
||||
_onTabResetPinButton(event, tab) {
|
||||
event.stopPropagation();
|
||||
this._resetTabToStoredState(tab);
|
||||
if (event.getModifierState("Accel")) {
|
||||
let newTab = gBrowser.duplicateTab(tab, true);
|
||||
newTab.addEventListener("SSTabRestored", () => {
|
||||
this._resetTabToStoredState(tab);
|
||||
}, { once: true });
|
||||
} else {
|
||||
this._resetTabToStoredState(tab);
|
||||
}
|
||||
gBrowser.selectedTab = tab;
|
||||
}
|
||||
|
||||
@@ -170,6 +177,37 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
}
|
||||
|
||||
_onAccelKeyChange(e) {
|
||||
let tab = this._tabWithResetPinButtonHovered;
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
let accelHeld = e.getModifierState("Accel") || (e.metaKey && e.type == "keydown");
|
||||
this._setResetPinSublabel(tab, accelHeld);
|
||||
// Up <-> down events until the mouse leaves the button.
|
||||
// When hovered with accelHeld, we should listen to the next keyup event
|
||||
let nextEvent = accelHeld ? "keyup" : "keydown";
|
||||
let handler = (nextE) => this._onAccelKeyChange(nextE);
|
||||
window.addEventListener(nextEvent, handler, { once: true });
|
||||
}
|
||||
|
||||
_setResetPinSublabel(tab, accelHeld) {
|
||||
let label = tab.querySelector(".zen-tab-sublabel");
|
||||
document.l10n.setArgs(label, {
|
||||
tabSubtitle: accelHeld ? "zen-default-pinned-cmd" : "zen-default-pinned",
|
||||
});
|
||||
}
|
||||
|
||||
onResetPinButtonMouseOver(tab, event) {
|
||||
this._tabWithResetPinButtonHovered = tab;
|
||||
this._onAccelKeyChange(event);
|
||||
}
|
||||
|
||||
onResetPinButtonMouseOut(tab) {
|
||||
this._setResetPinSublabel(tab, false);
|
||||
delete this._tabWithResetPinButtonHovered;
|
||||
}
|
||||
|
||||
resetPinnedTab(tab) {
|
||||
if (!tab) {
|
||||
tab = TabContextMenu.contextTab;
|
||||
@@ -812,7 +850,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
tab.removeAttribute("zen-show-sublabel");
|
||||
|
||||
const label = tab.querySelector(".zen-tab-sublabel");
|
||||
window.document.l10n.setArgs(label, {
|
||||
document.l10n.setArgs(label, {
|
||||
tabSubtitle: "zen-default-pinned",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ prefs = ["zen.workspaces.separate-essentials=false"]
|
||||
|
||||
["browser_pinned_nounload_reset.js"]
|
||||
|
||||
["browser_pinned_reset_button.js"]
|
||||
|
||||
["browser_pinned_reset_noswitch.js"]
|
||||
|
||||
["browser_pinned_switch.js"]
|
||||
|
||||
130
src/zen/tests/pinned/browser_pinned_reset_button.js
Normal file
130
src/zen/tests/pinned/browser_pinned_reset_button.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
async function pinAndNavigateTab(url, navigateTo) {
|
||||
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
|
||||
gBrowser.pinTab(tab);
|
||||
await gBrowser.TabStateFlusher.flush(tab.linkedBrowser);
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, navigateTo);
|
||||
await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, navigateTo);
|
||||
return tab;
|
||||
}
|
||||
|
||||
add_task(async function test_ResetPinButton_SelectsTab() {
|
||||
const tab = await pinAndNavigateTab("https://example.com/1", "https://example.com/2");
|
||||
|
||||
// Open another tab and select it
|
||||
const otherTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/other");
|
||||
Assert.notEqual(gBrowser.selectedTab, tab, "The pinned tab should not be selected initially");
|
||||
|
||||
// Simulate clicking the reset pin button (without Accel key)
|
||||
gZenPinnedTabManager._onTabResetPinButton(
|
||||
{ stopPropagation() {}, getModifierState() { return false; } },
|
||||
tab
|
||||
);
|
||||
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
Assert.strictEqual(gBrowser.selectedTab, tab, "The pinned tab should be selected after reset");
|
||||
ok(!tab.hasAttribute("zen-pinned-changed"), "zen-pinned-changed should be removed after reset");
|
||||
|
||||
gBrowser.removeTab(otherTab);
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_ResetPinButton_CmdClick_DuplicatesAndResets() {
|
||||
const originalUrl = "https://example.com/1";
|
||||
const navigatedUrl = "https://example.com/2";
|
||||
const tab = await pinAndNavigateTab(originalUrl, navigatedUrl);
|
||||
const tabCountBefore = gBrowser.tabs.length;
|
||||
|
||||
// Simulate CMD+click on the reset pin button
|
||||
gZenPinnedTabManager._onTabResetPinButton(
|
||||
{ stopPropagation() {}, getModifierState() { return true; } },
|
||||
tab
|
||||
);
|
||||
|
||||
// Wait for the duplicate tab to be restored
|
||||
const restoredEvent = await BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "SSTabRestored");
|
||||
const newTab = restoredEvent.target;
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
|
||||
Assert.equal(gBrowser.tabs.length, tabCountBefore + 1, "A new tab should be created from the duplicate");
|
||||
Assert.equal(
|
||||
newTab.linkedBrowser.currentURI.spec,
|
||||
navigatedUrl,
|
||||
"The duplicated tab should have the navigated URL"
|
||||
);
|
||||
ok(!newTab.pinned, "The duplicated tab should not be pinned");
|
||||
|
||||
Assert.strictEqual(gBrowser.selectedTab, tab, "The pinned tab should be selected after CMD+click reset");
|
||||
ok(!tab.hasAttribute("zen-pinned-changed"), "zen-pinned-changed should be removed after reset");
|
||||
Assert.equal(
|
||||
tab.linkedBrowser.currentURI.spec,
|
||||
originalUrl,
|
||||
"The pinned tab should be reset to the original URL"
|
||||
);
|
||||
|
||||
gBrowser.removeTab(newTab);
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_Hover_SublabelChangesWithAccelKey() {
|
||||
const tab = await pinAndNavigateTab("https://example.com/1", "https://example.com/2");
|
||||
|
||||
// Track calls to document.l10n.setArgs to verify sublabel updates
|
||||
const sublabelArgs = [];
|
||||
const label = tab.querySelector(".zen-tab-sublabel");
|
||||
const origSetArgs = document.l10n.setArgs;
|
||||
document.l10n.setArgs = (el, args) => {
|
||||
if (el === label) {
|
||||
sublabelArgs.push(args.tabSubtitle);
|
||||
}
|
||||
origSetArgs.call(document.l10n, el, args);
|
||||
};
|
||||
|
||||
try {
|
||||
// Simulate hovering with no modifier key held
|
||||
gZenPinnedTabManager.onResetPinButtonMouseOver(tab, {
|
||||
getModifierState() { return false; },
|
||||
metaKey: false,
|
||||
type: "mouseover",
|
||||
});
|
||||
|
||||
Assert.equal(sublabelArgs.at(-1), "zen-default-pinned", "Sublabel should show default text on hover without Accel");
|
||||
|
||||
// Simulate pressing CMD while hovering
|
||||
gZenPinnedTabManager._onAccelKeyChange({
|
||||
getModifierState() { return true; },
|
||||
metaKey: true,
|
||||
type: "keydown",
|
||||
});
|
||||
|
||||
Assert.equal(sublabelArgs.at(-1), "zen-default-pinned-cmd", "Sublabel should show CMD text when Accel key is pressed");
|
||||
|
||||
// Simulate releasing CMD while still hovering
|
||||
gZenPinnedTabManager._onAccelKeyChange({
|
||||
getModifierState() { return false; },
|
||||
metaKey: false,
|
||||
type: "keyup",
|
||||
});
|
||||
|
||||
Assert.equal(sublabelArgs.at(-1), "zen-default-pinned", "Sublabel should revert to default text when Accel key is released");
|
||||
|
||||
// Simulate mouse out
|
||||
gZenPinnedTabManager.onResetPinButtonMouseOut(tab);
|
||||
|
||||
Assert.equal(sublabelArgs.at(-1), "zen-default-pinned", "Sublabel should show default text after mouse out");
|
||||
ok(!gZenPinnedTabManager._tabWithResetPinButtonHovered, "Hovered tab reference should be cleared after mouse out");
|
||||
} finally {
|
||||
document.l10n.setArgs = origSetArgs;
|
||||
}
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
Reference in New Issue
Block a user