Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4903575
feat: add new shortcuts
nikvnt Jul 9, 2025
b1253bf
patch: remove mozilla's ctrl+tab navigation
nikvnt Jul 9, 2025
c6769bf
feat: add menu & radio options & shortcuts logic
nikvnt Jul 9, 2025
42fd45d
change: trail some trash code and add more modifier options
nikvnt Jul 13, 2025
f0c69b7
Merge remote-tracking branch 'upstream/dev' into dev
nikvnt Jul 13, 2025
2e7ef05
change: leaving inputs for navigation unset + setting a default input…
nikvnt Jul 13, 2025
b030f01
change: move functionalities from workspaces to commonUtils & remove …
nikvnt Jul 22, 2025
ae63f9d
Merge remote-tracking branch upstream/dev into dev
nikvnt Jul 22, 2025
1ebd642
Merge remote-tracking branch 'upstream/dev' into dev
nikvnt Aug 14, 2025
f582029
test: add tests for unloaded navigation pref
nikvnt Sep 9, 2025
4550322
feat: use firefox's native tab navigation + other adjusts for PR
nikvnt Sep 9, 2025
9bc7609
chore(dev): sync with upstream repo
nikvnt Sep 9, 2025
f6f6b56
feat: make it so that unloaded cycling shortcut is unset by default
nikvnt Sep 10, 2025
cf89eba
feat: make tab navigation shortcuts take precedence over sites
nikvnt Sep 11, 2025
4462483
Merge branch 'dev' into dev
mr-cheffy Sep 13, 2025
8237ee6
chore: Updated to dev, b=no-bug, c=common, kbs, tests, tabs, workspaces
mr-cheffy Sep 13, 2025
9d4d9e3
chore: change key names so that they match l10n translations
nikvnt Sep 16, 2025
3183624
Merge remote-tracking branch 'refs/remotes/origin/dev' into dev
nikvnt Sep 16, 2025
0b0ef8f
chore: improve identation
nikvnt Sep 17, 2025
f1bb8f8
feat: make it so that cycling tabs wrap around + add a test case for …
nikvnt Sep 17, 2025
47e3779
fix: remove _precedentListeners array
nikvnt Sep 17, 2025
6f608db
feat: make it so that every key has isPrecedent as an attribute + set…
nikvnt Sep 18, 2025
ed3f1ff
Merge branch 'dev' into dev
mr-cheffy Sep 18, 2025
da26a8a
Merge branch 'dev' into dev
nikvnt Sep 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/browser/components/preferences/zen-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ var zenMissingKeyboardShortcutL10n = {
key_zenTabNext: 'zen-key-tab-next-shortcut',
key_zenTabPrevious: 'zen-key-tab-prev-shortcut',
key_toggleUnloadedCycling: 'zen-toggle-unloaded-cycling-shortcut',

'zen-glance-expand': 'zen-glance-expand',

key_selectTab1: 'zen-key-select-tab-1',
Expand Down Expand Up @@ -1169,7 +1169,7 @@ Preferences.addAll([
type: 'bool',
default: true,
},
{
{
id: 'zen.tabs.unloaded-navigation-mode',
type: 'string',
default: 'always',
Expand Down
7 changes: 5 additions & 2 deletions src/zen/common/ZenCommonUtils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ window.gZenOperatingSystemCommonUtils = {

/* eslint-disable no-unused-vars */
class nsZenMultiWindowFeature {
constructor() { }
constructor() {}

static get browsers() {
return Services.wm.getEnumerator('navigator:browser');
Expand Down Expand Up @@ -130,7 +130,10 @@ var gZenCommonActions = {

toggleUnloadedCycling() {
try {
const currentMode = Services.prefs.getStringPref('zen.tabs.unloaded-navigation-mode', 'always');
const currentMode = Services.prefs.getStringPref(
'zen.tabs.unloaded-navigation-mode',
'always'
);
const nextMode = currentMode === 'always' ? 'never' : 'always';
Services.prefs.setStringPref('zen.tabs.unloaded-navigation-mode', nextMode);
} catch (e) {
Expand Down
10 changes: 4 additions & 6 deletions src/zen/kbs/ZenKeyboardShortcuts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,9 @@ class nsZenKeyboardShortcutsVersioner {
nsKeyShortcutModifiers.fromObject({}),
'cmd_zenToggleUnloadedCycling',
'zen-toggle-unloaded-cycling-shortcut'

)
);

// 2) Add the new pin/unpin tab toggle shortcut with Ctrl+Shift+D
data.push(
new KeyShortcut(
Expand Down Expand Up @@ -1249,7 +1251,6 @@ var gZenKeyboardShortcutsManager = {

_registerPrecedentShortcut(shortcut, browser) {
const listener = (event) => {

let keyMatch = false;
if (shortcut.getKeyName()) {
keyMatch = event.key.toLowerCase() === shortcut.getKeyName().toLowerCase();
Expand Down Expand Up @@ -1312,10 +1313,7 @@ var gZenKeyboardShortcutsManager = {
continue;
}

if (
key.getID() === 'zen-tab-next-shortcut' ||
key.getID() === 'zen-tab-prev-shortcut'
) {
if (key.getID() === 'zen-tab-next-shortcut' || key.getID() === 'zen-tab-prev-shortcut') {
this._registerPrecedentShortcut(key, browser);
} else {
let child = key.toXHTMLElement(browser);
Expand Down
95 changes: 60 additions & 35 deletions src/zen/tests/tabs/browser_tab_unloaded_navigation.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";
'use strict';

/**
* Tests tab navigation between loaded/unloaded tabs based on user preference
Expand All @@ -18,32 +18,31 @@ async function createLoadedTab(url) {
inBackground: true,
});


await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
return tab;
}

function createUnloadedTab(url) {
const tab = BrowserTestUtils.addTab(gBrowser, url, {
inBackground: true,
skipAnimation: true
skipAnimation: true,
});

tab.linkedBrowser.setAttribute("pending", "true");
tab.setAttribute("pending", "true");
tab.linkedBrowser.setAttribute('pending', 'true');
tab.setAttribute('pending', 'true');

info(`New unloaded tab created at index ${gBrowser.tabs.indexOf(tab)} with URL ${url}`);
return tab;
}

function resetPreferences() {
Services.prefs.clearUserPref("zen.tabs.unloaded-navigation-mode");
Services.prefs.clearUserPref('zen.tabs.unloaded-navigation-mode');
}

async function waitForTabSelection(expectedTab) {
await BrowserTestUtils.waitForCondition(
() => gBrowser.selectedTab === expectedTab,
"Waiting for tab to be selected"
'Waiting for tab to be selected'
);
}

Expand All @@ -52,15 +51,15 @@ add_setup(async () => {
});

add_task(async function test_basic_unloaded_navigation() {
info("Basic test to verify the test infrastructure works");
info('Basic test to verify the test infrastructure works');

const tab1 = await createLoadedTab(URL1);
const tab2 = createUnloadedTab(URL2);

ok(tab1, "Loaded tab should be created");
ok(tab2, "Unloaded tab should be created");
is(tab2.hasAttribute("pending"), true, "Tab should have pending attribute");
is(tab1.hasAttribute("pending"), false, "Loaded tab should not have pending attribute");
ok(tab1, 'Loaded tab should be created');
ok(tab2, 'Unloaded tab should be created');
is(tab2.hasAttribute('pending'), true, 'Tab should have pending attribute');
is(tab1.hasAttribute('pending'), false, 'Loaded tab should not have pending attribute');

BrowserTestUtils.removeTab(tab1);
BrowserTestUtils.removeTab(tab2);
Expand All @@ -70,13 +69,15 @@ add_task(async function test_unloaded_navigation_always_mode() {
info("Testing navigation with 'always' mode (includes unloaded tabs)");

await SpecialPowers.pushPrefEnv({
set: [["zen.tabs.unloaded-navigation-mode", "always"]],
set: [['zen.tabs.unloaded-navigation-mode', 'always']],
});

const navigateAndAssert = async (direction, expectedTab, message) => {
gBrowser.tabContainer.advanceSelectedTab(direction, false);
await TestUtils.waitForTick();
info(`After navigating by ${direction}, selected tab is at index ${gBrowser.tabs.indexOf(gBrowser.selectedTab)}`);
info(
`After navigating by ${direction}, selected tab is at index ${gBrowser.tabs.indexOf(gBrowser.selectedTab)}`
);
is(gBrowser.selectedTab, expectedTab, message);
};

Expand All @@ -87,11 +88,13 @@ add_task(async function test_unloaded_navigation_always_mode() {
gBrowser.selectedTab = loadedTab;
await waitForTabSelection(loadedTab);

await navigateAndAssert(1, gBrowser.tabs[initialIndex + 1],
"Should navigate forward to the next tab (unloaded)");
await navigateAndAssert(
1,
gBrowser.tabs[initialIndex + 1],
'Should navigate forward to the next tab (unloaded)'
);

await navigateAndAssert(-1, loadedTab,
"Should navigate backward to the previous tab (loaded)");
await navigateAndAssert(-1, loadedTab, 'Should navigate backward to the previous tab (loaded)');

BrowserTestUtils.removeTab(loadedTab);
BrowserTestUtils.removeTab(unloadedTab);
Expand All @@ -103,7 +106,7 @@ add_task(async function test_unloaded_navigation_never_mode() {
info("Testing navigation with 'never' mode (skips unloaded tabs) using URL comparison");

await SpecialPowers.pushPrefEnv({
set: [["zen.tabs.unloaded-navigation-mode", "never"]],
set: [['zen.tabs.unloaded-navigation-mode', 'never']],
});

await TestUtils.waitForTick();
Expand All @@ -117,11 +120,10 @@ add_task(async function test_unloaded_navigation_never_mode() {

let allTestTabs = [];
try {

// Create all the tabs required for this specific test scenario
for (const tabConfig of setup) {
let newTab;
if (tabConfig.type === "loaded") {
if (tabConfig.type === 'loaded') {
newTab = await createLoadedTab(tabConfig.url);
} else {
newTab = createUnloadedTab(tabConfig.url);
Expand All @@ -138,9 +140,12 @@ add_task(async function test_unloaded_navigation_never_mode() {
//trimming unnecessary about:blanks
for (let i = gBrowser.tabs.length - 1; i >= 0; i--) {
const tab = gBrowser.tabs[i];
if (tab.linkedBrowser.currentURI.spec === "about:blank" &&
tab.visible && !tab.hasAttribute('pending') &&
!allTestTabs.includes(tab)) {
if (
tab.linkedBrowser.currentURI.spec === 'about:blank' &&
tab.visible &&
!tab.hasAttribute('pending') &&
!allTestTabs.includes(tab)
) {
gBrowser.removeTab(tab);
}
}
Expand All @@ -160,10 +165,11 @@ add_task(async function test_unloaded_navigation_never_mode() {
const finalURL = gBrowser.selectedTab.linkedBrowser.currentURI.spec;
const finalTabIndex = gBrowser.tabs.indexOf(gBrowser.selectedTab);

info(`--> Navigation result: Started on tab ${startTabIndex} [${startURL}], ended on tab ${finalTabIndex} [${finalURL}]`);
info(
`--> Navigation result: Started on tab ${startTabIndex} [${startURL}], ended on tab ${finalTabIndex} [${finalURL}]`
);

is(finalURL, expectedURL, description);

} finally {
// just remove all tabs created for a specific test run before moving on to the next. Trying to 'KISS'
for (const tab of allTestTabs) {
Expand All @@ -176,41 +182,60 @@ add_task(async function test_unloaded_navigation_never_mode() {

// test scenarios
await runNavTest({
setup: [{ type: "loaded", url: URL1 }, { type: "unloaded", url: URL2 }, { type: "loaded", url: URL3 }],
setup: [
{ type: 'loaded', url: URL1 },
{ type: 'unloaded', url: URL2 },
{ type: 'loaded', url: URL3 },
],
startIndex: 0,
direction: 1,
expectedURL: URL3,
description: "Should skip one unloaded tab and land on the correct URL.",
description: 'Should skip one unloaded tab and land on the correct URL.',
});

await runNavTest({
setup: [{ type: "loaded", url: URL1 }, { type: "unloaded", url: URL2 }, { type: "loaded", url: URL3 }],
setup: [
{ type: 'loaded', url: URL1 },
{ type: 'unloaded', url: URL2 },
{ type: 'loaded', url: URL3 },
],
startIndex: 2,
direction: -1,
expectedURL: URL1,
description: "Should skip one unloaded tab backward and land on the correct URL.",
description: 'Should skip one unloaded tab backward and land on the correct URL.',
});

await runNavTest({
setup: [
{ type: "loaded", url: URL1 }, { type: "unloaded", url: URL2 }, { type: "unloaded", url: URL2 }, { type: "loaded", url: URL3 },
{ type: 'loaded', url: URL1 },
{ type: 'unloaded', url: URL2 },
{ type: 'unloaded', url: URL2 },
{ type: 'loaded', url: URL3 },
],
startIndex: 0,
direction: 1,
expectedURL: URL3,
description: "Should skip multiple unloaded tabs and land on the correct URL.",
description: 'Should skip multiple unloaded tabs and land on the correct URL.',
});

await runNavTest({
setup: [{ type: "loaded", url: URL1 }, { type: "unloaded", url: URL2 }, { type: "unloaded", url: URL2 }],
setup: [
{ type: 'loaded', url: URL1 },
{ type: 'unloaded', url: URL2 },
{ type: 'unloaded', url: URL2 },
],
startIndex: 0,
direction: 1,
expectedURL: URL1,
description: "Should not move if there is no next loaded tab.",
description: 'Should not move if there is no next loaded tab.',
});

await runNavTest({
setup: [{ type: "loaded", url: URL1 }, { type: "unloaded", url: URL2 }, { type: "loaded", url: URL3 }],
setup: [
{ type: 'loaded', url: URL1 },
{ type: 'unloaded', url: URL2 },
{ type: 'loaded', url: URL3 },
],
startIndex: 2,
direction: -1,
expectedURL: URL1,
Expand Down
2 changes: 1 addition & 1 deletion src/zen/workspaces/ZenWorkspaces.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3068,4 +3068,4 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature {
document.getElementById('cmd_closeWindow').doCommand();
}
}
})();
})();