mirror of
https://github.com/zen-browser/desktop.git
synced 2026-02-19 01:48:22 +00:00
feat: Ctrl+Tab cycling to keep within essentials/regular tabs only, p=#11242
* feat: ctrl+tab cycles within essential/workspace tabs only * add test for ctrl+tab cycling by attribute * move settings UI related code to /src/zen, revert patches for main.inc.xhtml and main.js, use simple tab filter when current tab is hidden * chore: Cleanup and add extra pref, b=no-bug, c=tests, tabs --------- Co-authored-by: mr. m <mr.m@tuta.com>
This commit is contained in:
@@ -59,6 +59,12 @@ zen-tabs-unloader-enabled =
|
||||
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-tabs-cycle-by-attribute =
|
||||
.label = Ctrl+Tab cycles within Essential or Workspace tabs only
|
||||
zen-tabs-cycle-ignore-pending-tabs =
|
||||
.label = Ignore Pending tabs when cycling with Ctrl+Tab
|
||||
zen-tabs-cycle-by-attribute-warning = Ctrl+Tab will cycle by recently used order, as it is enabled
|
||||
|
||||
zen-look-and-feel-compact-toolbar-themed =
|
||||
.label = Use themed background for compact toolbar
|
||||
|
||||
@@ -150,7 +156,7 @@ zen-theme-marketplace-input-default-placeholder =
|
||||
.placeholder = Type something...
|
||||
pane-zen-marketplace-title = Zen Mods
|
||||
zen-themes-auto-update =
|
||||
.label = Automatically update installed mods on startup
|
||||
.label = Automatically update installed mods on startup
|
||||
|
||||
zen-settings-workspaces-force-container-tabs-to-workspace =
|
||||
.label = Switch to workspace where container is set as default when opening container tabs
|
||||
|
||||
@@ -37,3 +37,9 @@
|
||||
|
||||
- name: zen.tabs.close-on-back-with-no-history
|
||||
value: true
|
||||
|
||||
- name: zen.tabs.ctrl-tab.ignore-essential-tabs
|
||||
value: false
|
||||
|
||||
- name: zen.tabs.ctrl-tab.ignore-pending-tabs
|
||||
value: false
|
||||
|
||||
@@ -707,13 +707,39 @@ var gZenWorkspacesSettings = {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let toggleZenCycleByAttrWarning = {
|
||||
observe() {
|
||||
const warning = document.getElementById('zenTabsCycleByAttributeWarning');
|
||||
warning.hidden = !(
|
||||
Services.prefs.getBoolPref('zen.tabs.ctrl-tab.ignore-essential-tabs', false) &&
|
||||
Services.prefs.getBoolPref('browser.ctrlTab.sortByRecentlyUsed', false)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
toggleZenCycleByAttrWarning.observe(); // call it once on initial load
|
||||
|
||||
Services.prefs.addObserver('zen.glance.enabled', tabsUnloaderPrefListener); // We can use the same listener for both prefs
|
||||
Services.prefs.addObserver('zen.workspaces.separate-essentials', tabsUnloaderPrefListener);
|
||||
Services.prefs.addObserver('zen.glance.activation-method', tabsUnloaderPrefListener);
|
||||
Services.prefs.addObserver(
|
||||
'zen.tabs.ctrl-tab.ignore-essential-tabs',
|
||||
toggleZenCycleByAttrWarning
|
||||
);
|
||||
Services.prefs.addObserver('browser.ctrlTab.sortByRecentlyUsed', toggleZenCycleByAttrWarning);
|
||||
window.addEventListener('unload', () => {
|
||||
Services.prefs.removeObserver('zen.glance.enabled', tabsUnloaderPrefListener);
|
||||
Services.prefs.removeObserver('zen.glance.activation-method', tabsUnloaderPrefListener);
|
||||
Services.prefs.removeObserver('zen.workspaces.separate-essentials', tabsUnloaderPrefListener);
|
||||
Services.prefs.removeObserver(
|
||||
'zen.tabs.ctrl-tab.ignore-essential-tabs',
|
||||
toggleZenCycleByAttrWarning
|
||||
);
|
||||
Services.prefs.removeObserver(
|
||||
'browser.ctrlTab.sortByRecentlyUsed',
|
||||
toggleZenCycleByAttrWarning
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -1135,6 +1161,21 @@ Preferences.addAll([
|
||||
type: 'bool',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: 'zen.tabs.ctrl-tab.ignore-essential-tabs',
|
||||
type: 'bool',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: 'zen.tabs.ctrl-tab.ignore-pending-tabs',
|
||||
type: 'bool',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: 'zen.tabs.close-on-back-with-no-history',
|
||||
type: 'bool',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
|
||||
Preferences.addSetting({
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
<checkbox id="zenTabsCloseOnBackWithNoHistory"
|
||||
data-l10n-id="zen-tabs-close-on-back-with-no-history"
|
||||
preference="zen.tabs.close-on-back-with-no-history"/>
|
||||
<checkbox data-l10n-id="zen-tabs-cycle-ignore-pending-tabs"
|
||||
preference="zen.tabs.ctrl-tab.ignore-pending-tabs"/>
|
||||
<checkbox data-l10n-id="zen-tabs-cycle-by-attribute"
|
||||
preference="zen.tabs.ctrl-tab.ignore-essential-tabs"/>
|
||||
<description id="zenTabsCycleByAttributeWarning"
|
||||
class="description-deemphasized"
|
||||
data-l10n-id="zen-tabs-cycle-by-attribute-warning"
|
||||
hidden="true"/>
|
||||
</groupbox>
|
||||
|
||||
<hbox id="zenTabsUnloadCategory"
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js
|
||||
index cfe2da6e199667bd668f117cc8972212c7f57da2..bdd7dcc26139202e6e31afde47dc4d877f3db3c5 100644
|
||||
index cfe2da6e199667bd668f117cc8972212c7f57da2..470033466eae0e853855e21b86a0722627f9ed4b 100644
|
||||
--- a/toolkit/content/widgets/tabbox.js
|
||||
+++ b/toolkit/content/widgets/tabbox.js
|
||||
@@ -213,7 +213,7 @@
|
||||
@@ -11,6 +11,23 @@
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
+ const { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
+ );
|
||||
+ const lazyZenPrefs = {};
|
||||
+ XPCOMUtils.defineLazyPreferenceGetter(
|
||||
+ lazyZenPrefs,
|
||||
+ "cycleByAttribute",
|
||||
+ "zen.tabs.ctrl-tab.ignore-essential-tabs",
|
||||
+ false
|
||||
+ );
|
||||
+ XPCOMUtils.defineLazyPreferenceGetter(
|
||||
+ lazyZenPrefs,
|
||||
+ "ignorePendingTabs",
|
||||
+ "zen.tabs.ctrl-tab.ignore-pending-tabs",
|
||||
+ false
|
||||
+ );
|
||||
+
|
||||
let imports = {};
|
||||
ChromeUtils.defineESModuleGetters(imports, {
|
||||
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
|
||||
@@ -213,7 +230,7 @@
|
||||
) {
|
||||
this._inAsyncOperation = false;
|
||||
if (oldPanel != this._selectedPanel) {
|
||||
@@ -11,7 +35,7 @@ index cfe2da6e199667bd668f117cc8972212c7f57da2..bdd7dcc26139202e6e31afde47dc4d87
|
||||
this._selectedPanel?.classList.add("deck-selected");
|
||||
}
|
||||
this.setAttribute("selectedIndex", val);
|
||||
@@ -697,7 +697,7 @@
|
||||
@@ -697,7 +714,7 @@
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
@@ -20,7 +44,7 @@ index cfe2da6e199667bd668f117cc8972212c7f57da2..bdd7dcc26139202e6e31afde47dc4d87
|
||||
if (otherTab != tab && otherTab.selected) {
|
||||
otherTab._selected = false;
|
||||
}
|
||||
@@ -733,6 +733,7 @@
|
||||
@@ -733,6 +750,7 @@
|
||||
* @param {MozTab|null} [val]
|
||||
*/
|
||||
set selectedItem(val) {
|
||||
@@ -28,7 +52,7 @@ index cfe2da6e199667bd668f117cc8972212c7f57da2..bdd7dcc26139202e6e31afde47dc4d87
|
||||
if (val && !val.selected) {
|
||||
// The selectedIndex setter ignores invalid values
|
||||
// such as -1 if |val| isn't one of our child nodes.
|
||||
@@ -910,7 +911,7 @@
|
||||
@@ -910,7 +928,7 @@
|
||||
if (tab == startTab) {
|
||||
return null;
|
||||
}
|
||||
@@ -37,7 +61,7 @@ index cfe2da6e199667bd668f117cc8972212c7f57da2..bdd7dcc26139202e6e31afde47dc4d87
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
@@ -972,10 +973,11 @@
|
||||
@@ -972,13 +990,30 @@
|
||||
* @param {boolean} [aWrap]
|
||||
*/
|
||||
advanceSelectedTab(aDir, aWrap) {
|
||||
@@ -50,3 +74,31 @@ index cfe2da6e199667bd668f117cc8972212c7f57da2..bdd7dcc26139202e6e31afde47dc4d87
|
||||
}
|
||||
let newTab = null;
|
||||
|
||||
+ const tabFilter = tab => {
|
||||
+ if (!tab.visible) {
|
||||
+ return false
|
||||
+ }
|
||||
+ if (lazyZenPrefs.ignorePendingTabs && tab.hasAttribute("pending")) {
|
||||
+ return false
|
||||
+ }
|
||||
+ if (!lazyZenPrefs.cycleByAttribute) {
|
||||
+ return true
|
||||
+ }
|
||||
+ if (startTab.hasAttribute("zen-essential")) {
|
||||
+ return tab.hasAttribute("zen-essential")
|
||||
+ }
|
||||
+ return !tab.hasAttribute("zen-essential")
|
||||
+ }
|
||||
+
|
||||
// Handle keyboard navigation for a hidden tab that can be selected, like the Firefox View tab,
|
||||
// which has a random placement in this.allTabs.
|
||||
if (startTab.hidden) {
|
||||
@@ -991,7 +1026,7 @@
|
||||
newTab = this.findNextTab(startTab, {
|
||||
direction: aDir,
|
||||
wrap: aWrap,
|
||||
- filter: tab => tab.visible,
|
||||
+ filter: tabFilter,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ support-files = [
|
||||
|
||||
["browser_tabs_empty_checks.js"]
|
||||
["browser_tabs_fetch_checks.js"]
|
||||
["browser_tabs_cycle_by_attribute.js"]
|
||||
["browser_drag_drop_vertical.js"]
|
||||
tags = [
|
||||
"drag-drop",
|
||||
"vertical-tabs"
|
||||
]
|
||||
]
|
||||
|
||||
86
src/zen/tests/tabs/browser_tabs_cycle_by_attribute.js
Normal file
86
src/zen/tests/tabs/browser_tabs_cycle_by_attribute.js
Normal file
@@ -0,0 +1,86 @@
|
||||
'use strict';
|
||||
|
||||
const URL1 = 'data:text/plain,tab1';
|
||||
const URL2 = 'data:text/plain,tab2';
|
||||
const URL3 = 'data:text/plain,tab3';
|
||||
const URL4 = 'data:text/plain,tab4';
|
||||
const URL5 = 'data:text/plain,tab5';
|
||||
const URL6 = 'data:text/plain,tab6';
|
||||
|
||||
/**
|
||||
* ensures that tab select action is completed
|
||||
*/
|
||||
async function selectTab(tab) {
|
||||
const onSelect = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, 'TabSelect');
|
||||
gBrowser.selectedTab = tab;
|
||||
await onSelect;
|
||||
}
|
||||
|
||||
add_setup(async () => {
|
||||
// remove default new tab
|
||||
const tabToRemove = gBrowser.selectedTab;
|
||||
BrowserTestUtils.removeTab(tabToRemove);
|
||||
|
||||
const tabs = await Promise.all([
|
||||
addTabTo(gBrowser, URL1),
|
||||
addTabTo(gBrowser, URL2),
|
||||
addTabTo(gBrowser, URL3),
|
||||
addTabTo(gBrowser, URL4),
|
||||
addTabTo(gBrowser, URL5),
|
||||
addTabTo(gBrowser, URL6),
|
||||
]);
|
||||
|
||||
gZenPinnedTabManager.addToEssentials(tabs.slice(0, 3));
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => tabs.slice(0, 3).every((tab) => tab.hasAttribute('zen-essential')),
|
||||
'all essentials ready'
|
||||
);
|
||||
|
||||
const essentialTabs = gBrowser.tabs.filter((tab) => tab.hasAttribute('zen-essential'));
|
||||
Assert.equal(essentialTabs.length, 3, '3 essential tabs created');
|
||||
|
||||
const workspaceTabs = gBrowser.tabs.filter(
|
||||
(tab) => !tab.hasAttribute('zen-essential') && !tab.hasAttribute('zen-empty-tab')
|
||||
);
|
||||
Assert.equal(workspaceTabs.length, 3, '3 workspace tabs created, excluding empty tab');
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
// replace the default new tab in the test window
|
||||
addTabTo(gBrowser, 'about:blank');
|
||||
tabs.forEach((element) => {
|
||||
BrowserTestUtils.removeTab(element);
|
||||
});
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function cycleTabsByAttribute() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [['zen.tabs.ctrl-tab.ignore-essential-tabs', true]],
|
||||
});
|
||||
|
||||
const essentialTabs = gBrowser.tabs.filter((tab) => tab.hasAttribute('zen-essential'));
|
||||
await selectTab(essentialTabs[0]);
|
||||
|
||||
gBrowser.tabContainer.advanceSelectedTab(1, true);
|
||||
gBrowser.tabContainer.advanceSelectedTab(1, true);
|
||||
gBrowser.tabContainer.advanceSelectedTab(1, true);
|
||||
ok(
|
||||
gBrowser.selectedTab === essentialTabs[0],
|
||||
'tab cycling applies within essential tabs only, as the starting tab is a essential tab'
|
||||
);
|
||||
|
||||
const workspaceTabs = gBrowser.tabs.filter(
|
||||
(tab) => !tab.hasAttribute('zen-essential') && !tab.hasAttribute('zen-empty-tab')
|
||||
);
|
||||
await selectTab(workspaceTabs[0]);
|
||||
|
||||
gBrowser.tabContainer.advanceSelectedTab(1, true);
|
||||
gBrowser.tabContainer.advanceSelectedTab(1, true);
|
||||
gBrowser.tabContainer.advanceSelectedTab(1, true);
|
||||
|
||||
ok(
|
||||
gBrowser.selectedTab === workspaceTabs[0],
|
||||
'tab cycling applies within workspace tabs only, as the starting tab is a workspace tab'
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user