From 94020c21bf48d45fd571db71ec3de8e6c56f8349 Mon Sep 17 00:00:00 2001
From: Alex Kontos
Date: Mon, 23 Sep 2024 14:58:39 +0100
Subject: [PATCH] Update sidebar to 1.0.3.
* Fix "Autohide" behavior of the Waterfox Sidebar for full screen mode
It was caused by the difference of the spec of -moz-bool-pref between ESR115 and ESR128. Now codes have been renewed for ESR128.
* Fix an error opening option page
It failed to run the initializer function on some cases. Now the initializer is retried with delay.
* Fix "Bookmark Selected Tabs" command
It was caused by the difference of the Places modules between ESR115 and ESR128. Now codes have been renewed for ESR128.
* Fix Tab preview feature with browser.tabs.hoverPreview.enabled=true
It was largely reconstructured on Firefox, thus I've updated codes for the current implementation.
* Fix behavior of F1 key not to toggle the Waterfox Sidebar
It happened when the "key" XUL element is re-inserted dynamically. Now the first inserted element is reused after reinstallation of the sidebar module.
* Fix the behavior of "Reset to default" operation in the toolbar customization
It was caused by an unhandled error in Firefox's toolbar customization module. I've implemented a mechanism to reset toolbar customizations until insertion of the tabs sidebar button succeed.
---
.../sidebar/_locales/en/messages.json | 3 +
.../sidebar/_locales/ja/messages.json | 3 +
.../sidebar/background/api-tabs-listener.js | 89 +--
.../sidebar/background/background-cache.js | 6 +-
.../sidebar/background/background.js | 15 +-
.../sidebar/background/browser-action-menu.js | 28 +-
.../sidebar/background/handle-misc.js | 32 +-
.../sidebar/background/handle-moved-tabs.js | 92 +--
.../sidebar/background/handle-tab-bunches.js | 11 +-
.../components/sidebar/background/prefs.js | 15 -
.../sidebar/background/successor-tab.js | 53 +-
.../sidebar/background/tab-context-menu.js | 31 +
.../sidebar/background/tabs-group.js | 40 +-
.../sidebar/background/tabs-move.js | 22 +-
.../sidebar/background/tabs-open.js | 3 +-
.../components/sidebar/background/tree.js | 306 ++++++----
.../browser/components/sidebar/common/Tab.js | 65 +-
.../components/sidebar/common/Window.js | 11 +-
.../components/sidebar/common/api-tabs.js | 49 --
.../components/sidebar/common/bookmark.js | 90 +--
.../sidebar/common/cache-storage.js | 127 ++--
.../components/sidebar/common/common.js | 23 +-
.../components/sidebar/common/constants.js | 3 +
.../components/sidebar/common/permissions.js | 19 +-
.../components/sidebar/common/retrieve-url.js | 7 +-
.../sidebar/common/tabs-internal-operation.js | 85 ++-
.../sidebar/common/tree-behavior.js | 17 +-
.../components/sidebar/common/tst-api.js | 6 +
.../components/sidebar/experiments/prefs.js | 26 +-
.../sidebar/experiments/waterfoxBridge.js | 236 +++++---
.../sidebar/experiments/waterfoxBridge.json | 11 -
.../browser/components/sidebar/manifest.json | 559 +++++++++---------
.../components/sidebar/options/init.js | 31 +-
.../components/sidebar/options/options.css | 2 +-
.../components/sidebar/options/options.html | 16 +
.../components/sidebar/resources/group-tab.js | 74 ++-
.../components/sidebar/resources/ui-color.css | 36 +-
.../sidebar/sidebar/components/TabElement.js | 58 +-
.../sidebar/components/TabLabelElement.js | 5 +
.../sidebar/sidebar/drag-and-drop.js | 21 +-
.../components/sidebar/sidebar/index-ws.js | 12 +
.../sidebar/sidebar/mouse-event-listener.js | 71 ++-
.../components/sidebar/sidebar/scroll.js | 169 ++++--
.../sidebar/sidebar/sidebar-tabs.js | 4 +-
.../components/sidebar/sidebar/sidebar.js | 16 +-
.../sidebar/sidebar/styles/photon/base.css | 7 +-
.../sidebar/styles/photon/highcontrast.css | 4 +-
.../sidebar/sidebar/styles/photon/photon.css | 8 +-
.../sidebar/sidebar/styles/proton/proton.css | 32 +-
.../sidebar/sidebar/tab-context-menu.js | 41 +-
.../components/sidebar/sidebar/tab-preview.js | 2 -
.../sidebar/sidebar/tst-api-frontend.js | 7 +-
52 files changed, 1653 insertions(+), 1046 deletions(-)
diff --git a/waterfox/browser/components/sidebar/_locales/en/messages.json b/waterfox/browser/components/sidebar/_locales/en/messages.json
index c902600bec85d..c9071c5bc71fe 100644
--- a/waterfox/browser/components/sidebar/_locales/en/messages.json
+++ b/waterfox/browser/components/sidebar/_locales/en/messages.json
@@ -614,6 +614,7 @@
"config_groupTabTemporaryStateForNewTabsFromBookmarks_label": { "message": "For tabs opened from bookmarks:" },
"config_groupTabTemporaryStateForNewTabsFromOthers_label": { "message": "For tabs opened at a time from non-bookmark triggers:" },
"config_groupTabTemporaryStateForOrphanedTabs_label": { "message": "Opened to replace a closed parent tab:" },
+ "config_groupTabTemporaryStateForAPI_label": { "message": "Opened to group tabs from other addons via API:" },
"config_inheritContextualIdentityToChildTabMode_label": { "message": "Container:" },
"config_inheritContextualIdentityToChildTabMode_default": { "message": "(no control)" },
@@ -730,6 +731,7 @@
"config_insertDroppedTabsAt_first": { "message": "The top of the tree (next to the parent)" },
"config_insertDroppedTabsAt_end": { "message": "The end of the tree" },
+ "config_autoCreateFolderForBookmarksFromTree_label": { "message": "Auto-group bookmark items under a folder when they are guessed to be created from multiple tree item tabs (ex. drag and drop of multiselected tabs)" },
"config_autoExpandOnLongHoverDelay_before": { "message": "Expand collapsed tree with hovering over " },
"config_autoExpandOnLongHoverDelay_after": { "message": "msec while dragging" },
"config_autoExpandOnLongHoverRestoreIniitalState_label": { "message": "Collapse such trees automatically after a drag action is finished" },
@@ -892,6 +894,7 @@
"tabContextMenu_shareTabURL_more_label": { "message": "More…" },
+ "tabContextMenu_closeDuplicatedTabs_label": { "message": "Close D&uplicate Tabs" },
"tabContextMenu_closeMultipleTabs_label": { "message": "Close &Multiple Tabs" },
"tabContextMenu_closeTabsToTop_label": { "message": "Close Tabs to the &Top" },
"tabContextMenu_closeTabsToBottom_label": { "message": "Close Tabs to the &Bottom" },
diff --git a/waterfox/browser/components/sidebar/_locales/ja/messages.json b/waterfox/browser/components/sidebar/_locales/ja/messages.json
index af98063c97a27..b923dca2c73aa 100644
--- a/waterfox/browser/components/sidebar/_locales/ja/messages.json
+++ b/waterfox/browser/components/sidebar/_locales/ja/messages.json
@@ -611,6 +611,7 @@
"config_groupTabTemporaryStateForNewTabsFromBookmarks_label": { "message": "ブックマークから開かれたタブをグループ化した場合:" },
"config_groupTabTemporaryStateForNewTabsFromOthers_label": { "message": "ブックマーク以外から連続して開かれたタブをグループ化した場合:" },
"config_groupTabTemporaryStateForOrphanedTabs_label": { "message": "閉じられた親タブを置き換える形で開かれた場合:" },
+ "config_groupTabTemporaryStateForAPI_label": { "message": "他の拡張機能からAPI経由でタブをグループ化した場合:" },
"config_inheritContextualIdentityToChildTabMode_label": { "message": "コンテナー:" },
"config_inheritContextualIdentityToChildTabMode_default": { "message": "(制御しない)" },
@@ -727,6 +728,7 @@
"config_insertDroppedTabsAt_first": { "message": "ツリーの先頭(親タブの隣)" },
"config_insertDroppedTabsAt_end": { "message": "ツリーの末尾" },
+ "config_autoCreateFolderForBookmarksFromTree_label": { "message": "ツリー構造を持ったタブを複数選択してのドラッグ&ドロップなどの操作で複数のブックマークが作成されたと推測できる場合に、作成されたブックマークを自動的に1つのフォルダーにまとめる" },
"config_autoExpandOnLongHoverDelay_before": { "message": "ドラッグ操作中、折りたたまれたツリーの上で" },
"config_autoExpandOnLongHoverDelay_after": { "message": "ミリ秒以上経過したらツリーを展開する" },
"config_autoExpandOnLongHoverRestoreIniitalState_label": { "message": "ドラッグ操作の終了後、ツリーを折りたたんだ状態に自動的に戻す" },
@@ -888,6 +890,7 @@
"tabContextMenu_shareTabURL_label": { "message": "共有(&H)" },
"tabContextMenu_shareTabURL_more_label": { "message": "その他..." },
+ "tabContextMenu_closeDuplicatedTabs_label": { "message": "重複タブを閉じる(&U)" },
"tabContextMenu_closeMultipleTabs_label": { "message": "複数のタブを閉じる(&M)" },
"tabContextMenu_closeTabsToTop_label": { "message": "上のタブをすべて閉じる(&T)" },
"tabContextMenu_closeTabsToBottom_label": { "message": "下のタブをすべて閉じる(&B)" },
diff --git a/waterfox/browser/components/sidebar/background/api-tabs-listener.js b/waterfox/browser/components/sidebar/background/api-tabs-listener.js
index 8910e4241d8e0..b7a539d522392 100644
--- a/waterfox/browser/components/sidebar/background/api-tabs-listener.js
+++ b/waterfox/browser/components/sidebar/background/api-tabs-listener.js
@@ -138,15 +138,12 @@ async function onActivated(activeInfo) {
try {
const win = Window.init(activeInfo.windowId);
- const byInternalOperation = win.internalFocusCount > 0;
- if (byInternalOperation)
- win.internalFocusCount--;
- const byMouseOperation = win.internalByMouseFocusCount > 0;
- if (byMouseOperation)
- win.internalByMouseFocusCount--;
- const silently = win.internalSilentlyFocusCount > 0;
- if (silently)
- win.internalSilentlyFocusCount--;
+ const byInternalOperation = win.internallyFocusingTabs.has(activeInfo.tabId);
+ win.internallyFocusingTabs.delete(activeInfo.tabId);
+ const byMouseOperation = win.internallyFocusingByMouseTabs.has(activeInfo.tabId);
+ win.internallyFocusingByMouseTabs.delete(activeInfo.tabId);
+ const silently = win.internallyFocusingSilentlyTabs.has(activeInfo.tabId);
+ win.internallyFocusingSilentlyTabs.delete(activeInfo.tabId);
const byTabDuplication = parseInt(win.duplicatingTabsCount) > 0;
if (!Tab.isTracked(activeInfo.tabId))
@@ -272,6 +269,7 @@ async function onUpdated(tabId, changeInfo, tab) {
}
}
/*
+ Workaround for Firefox 130 and olders.
Updated openerTabId is not notified via tabs.onUpdated due to
https://bugzilla.mozilla.org/show_bug.cgi?id=1409262 , so it can be
notified with delay as a part of the complete tabs.Tab object,
@@ -284,8 +282,11 @@ async function onUpdated(tabId, changeInfo, tab) {
continue;
if ('key' in updatedTab)
oldState[key] = updatedTab[key];
- if (key == 'openerTabId')
+ if (key == 'openerTabId') {
+ if (changeInfo.openerTabId == updatedTab.openerTabId) // already processed
+ continue;
log(`openerTabId of ${tabId} is changed by someone (notified via changeInfo)!: ${updatedTab.openerTabId} (original) => ${changeInfo[key]} (changed by someone)`, configs.debug && new Error().stack);
+ }
updatedTab[key] = changeInfo[key];
}
if (changeInfo.url ||
@@ -788,10 +789,8 @@ async function onNewTabTracked(tab, info) {
}
tab.$TST.memorizeNeighbors('newly tracked');
- if (tab.$TST.unsafePreviousTab)
- tab.$TST.unsafePreviousTab.$TST.memorizeNeighbors('unsafePreviousTab');
- if (tab.$TST.unsafeNextTab)
- tab.$TST.unsafeNextTab.$TST.memorizeNeighbors('unsafeNextTab');
+ tab.$TST.unsafePreviousTab?.$TST?.memorizeNeighbors('unsafePreviousTab');
+ tab.$TST.unsafeNextTab?.$TST?.memorizeNeighbors('unsafeNextTab');
Tree.onAttached.removeListener(onTreeModified);
metric.add('Tree.onAttached proceeded');
@@ -839,11 +838,18 @@ async function onRemoved(tabId, removeInfo) {
log('tabs.onRemoved: ', tabId, removeInfo);
const win = Window.init(removeInfo.windowId);
const byInternalOperation = win.internalClosingTabs.has(tabId);
- if (byInternalOperation)
- win.internalClosingTabs.delete(tabId);
const preventEntireTreeBehavior = win.keepDescendantsTabs.has(tabId);
- if (preventEntireTreeBehavior)
- win.keepDescendantsTabs.delete(tabId);
+
+ win.internalMovingTabs.delete(tabId);
+ win.alreadyMovedTabs.delete(tabId);
+ win.internalClosingTabs.delete(tabId);
+ win.keepDescendantsTabs.delete(tabId);
+ win.highlightingTabs.delete(tabId);
+ win.tabsToBeHighlightedAlone.delete(tabId);
+
+ win.internallyFocusingTabs.delete(tabId);
+ win.internallyFocusingByMouseTabs.delete(tabId);
+ win.internallyFocusingSilentlyTabs.delete(tabId);
if (Tab.needToWaitTracked(removeInfo.windowId))
await Tab.waitUntilTrackedAll(removeInfo.windowId);
@@ -925,9 +931,7 @@ async function onRemoved(tabId, removeInfo) {
oldTab.$TST.destroy();
for (const tab of nearestTabs) {
- if (!tab || !tab.$TST)
- continue;
- tab.$TST.memorizeNeighbors('neighbor of closed tab');
+ tab?.$TST?.memorizeNeighbors('neighbor of closed tab');
}
onCompleted();
@@ -956,7 +960,10 @@ async function onMoved(tabId, moveInfo) {
// and other fixup operations around tabs moved by foreign triggers, on such
// cases. Don't mind, the tab will be rearranged again by delayed
// TabsMove.syncTabsPositionToApiTabs() anyway!
- const maybeInternalOperation = win.internalMovingTabs.has(tabId);
+ const internalExpectedIndex = win.internalMovingTabs.get(tabId);
+ const maybeInternalOperation = internalExpectedIndex < 0 || internalExpectedIndex == moveInfo.toIndex;
+ if (maybeInternalOperation)
+ log(`tabs.onMoved: ${tabId} is detected as moved internally`);
if (!Tab.isTracked(tabId))
await Tab.waitUntilTracked(tabId);
@@ -979,7 +986,7 @@ async function onMoved(tabId, moveInfo) {
do following processes after the tab is completely pinned. */
const movedTab = Tab.get(tabId);
if (!movedTab) {
- if (maybeInternalOperation)
+ if (win.internalMovingTabs.has(tabId))
win.internalMovingTabs.delete(tabId);
completelyMoved();
warnTabDestroyedWhileWaiting(tabId, movedTab);
@@ -989,22 +996,21 @@ async function onMoved(tabId, moveInfo) {
let oldPreviousTab = movedTab.hidden ? movedTab.$TST.unsafePreviousTab : movedTab.$TST.previousTab;
let oldNextTab = movedTab.hidden ? movedTab.$TST.unsafeNextTab : movedTab.$TST.nextTab;
if (movedTab.index != moveInfo.toIndex ||
- (oldPreviousTab && oldPreviousTab.index == movedTab.index - 1) ||
- (oldNextTab && oldNextTab.index == movedTab.index + 1)) {
+ (oldPreviousTab?.index == movedTab.index - 1) ||
+ (oldNextTab?.index == movedTab.index + 1)) {
// already moved
oldPreviousTab = Tab.getTabAt(moveInfo.windowId, moveInfo.toIndex < moveInfo.fromIndex ? moveInfo.fromIndex : moveInfo.fromIndex - 1);
oldNextTab = Tab.getTabAt(moveInfo.windowId, moveInfo.toIndex < moveInfo.fromIndex ? moveInfo.fromIndex + 1 : moveInfo.fromIndex);
- if (oldPreviousTab && oldPreviousTab.id == movedTab.id)
+ if (oldPreviousTab?.id == movedTab.id)
oldPreviousTab = Tab.getTabAt(moveInfo.windowId, moveInfo.toIndex < moveInfo.fromIndex ? moveInfo.fromIndex - 1 : moveInfo.fromIndex - 2);
- if (oldNextTab && oldNextTab.id == movedTab.id)
+ if (oldNextTab?.id == movedTab.id)
oldNextTab = Tab.getTabAt(moveInfo.windowId, moveInfo.toIndex < moveInfo.fromIndex ? moveInfo.fromIndex : moveInfo.fromIndex - 1);
}
- let alreadyMoved = false;
- if (win.alreadyMovedTabs.has(tabId)) {
+ const expectedIndex = win.alreadyMovedTabs.get(tabId);
+ const alreadyMoved = expectedIndex < 0 || expectedIndex == moveInfo.toIndex;
+ if (win.alreadyMovedTabs.has(tabId))
win.alreadyMovedTabs.delete(tabId);
- alreadyMoved = true;
- }
const extendedMoveInfo = {
...moveInfo,
@@ -1012,7 +1018,10 @@ async function onMoved(tabId, moveInfo) {
alreadyMoved,
oldPreviousTab,
oldNextTab,
- isSubstantiallyMoved: movedTab.$TST.isSubstantiallyMoved
+ // Multiselected tabs can be moved together in bulk, by drag and drop
+ // in the horizontal tab bar, or addons like
+ // https://addons.mozilla.org/firefox/addon/move-tab-hotkeys/
+ movedInBulk: !maybeInternalOperation && (movedTab.$TST.multiselected || movedTab.$TST.movedInBulk),
};
log('tabs.onMoved: ', movedTab, extendedMoveInfo);
@@ -1060,20 +1069,16 @@ async function onMoved(tabId, moveInfo) {
nextTabId: nextTab && nextTab.id
});
}
- if (maybeInternalOperation)
+ if (win.internalMovingTabs.has(tabId))
win.internalMovingTabs.delete(tabId);
completelyMoved();
movedTab.$TST.memorizeNeighbors('moved');
- if (movedTab.$TST.unsafePreviousTab)
- movedTab.$TST.unsafePreviousTab.$TST.memorizeNeighbors('unsafePreviousTab');
- if (movedTab.$TST.unsafeNextTab)
- movedTab.$TST.unsafeNextTab.$TST.memorizeNeighbors('unsafeNextTab');
-
- if (oldPreviousTab)
- oldPreviousTab.$TST.memorizeNeighbors('oldPreviousTab');
- if (oldNextTab)
- oldNextTab.$TST.memorizeNeighbors('oldNextTab');
+ movedTab.$TST.unsafePreviousTab?.$TST?.memorizeNeighbors('unsafePreviousTab');
+ movedTab.$TST.unsafeNextTab?.$TST?.memorizeNeighbors('unsafeNextTab');
+
+ oldPreviousTab?.$TST?.memorizeNeighbors('oldPreviousTab');
+ oldNextTab?.$TST?.memorizeNeighbors('oldNextTab');
}
catch(e) {
console.log(e);
diff --git a/waterfox/browser/components/sidebar/background/background-cache.js b/waterfox/browser/components/sidebar/background/background-cache.js
index bbbf21c92c765..1561ce5eff62b 100644
--- a/waterfox/browser/components/sidebar/background/background-cache.js
+++ b/waterfox/browser/components/sidebar/background/background-cache.js
@@ -339,7 +339,8 @@ async function updateWindowCache(owner, key, value) {
});
return;
}
- catch(_error) {
+ catch(error) {
+ console.log(`BackgroundCache.updateWindowCache for ${owner.windowId}/${key} failed: `, error.message, error.stack, error);
}
}
@@ -372,7 +373,8 @@ async function getWindowCache(owner, key) {
});
return value;
}
- catch(_error) {
+ catch(error) {
+ console.log(`BackgroundCache.getWindowCache for ${owner.windowId}/${key} failed: `, error.message, error.stack, error);
}
}
diff --git a/waterfox/browser/components/sidebar/background/background.js b/waterfox/browser/components/sidebar/background/background.js
index 52bca30f1033d..419cd6d4ab7f9 100644
--- a/waterfox/browser/components/sidebar/background/background.js
+++ b/waterfox/browser/components/sidebar/background/background.js
@@ -310,12 +310,23 @@ async function rebuildAll(windows) {
try {
log(`build tabs for ${win.id} from scratch`);
Window.init(win.id);
+ const promises = [];
for (let tab of win.tabs) {
tab = Tab.get(tab.id);
tab.$TST.clear(); // clear dirty restored states
- TabsUpdate.updateTab(tab, tab, { forceApply: true });
+ promises.push(
+ tab.$TST.getPermanentStates()
+ .then(states => {
+ tab.$TST.states = new Set(states);
+ })
+ .catch(console.error)
+ .then(() => {
+ TabsUpdate.updateTab(tab, tab, { forceApply: true });
+ })
+ );
tryStartHandleAccelKeyOnTab(tab);
}
+ await Promise.all(promises);
}
catch(e) {
log(`failed to build tabs for ${win.id}`, e);
@@ -774,7 +785,7 @@ Tab.onTabInternallyMoved.addListener((tab, info = {}) => {
});
Tab.onMoved.addListener((tab, moveInfo) => {
- if (!moveInfo.isSubstantiallyMoved)
+ if (moveInfo.movedInBulk)
return;
reserveToUpdateInsertionPosition([
tab,
diff --git a/waterfox/browser/components/sidebar/background/browser-action-menu.js b/waterfox/browser/components/sidebar/background/browser-action-menu.js
index 72cf03cbdfbe4..af224b7f61135 100644
--- a/waterfox/browser/components/sidebar/background/browser-action-menu.js
+++ b/waterfox/browser/components/sidebar/background/browser-action-menu.js
@@ -1298,8 +1298,32 @@ const mItems = [
type: 'radio'
}
]
- }
- ]
+ },
+ {
+ title: indent() + browser.i18n.getMessage('config_groupTabTemporaryStateForAPI_label'),
+ expert: true,
+ children: [
+ {
+ title: browser.i18n.getMessage('config_groupTabTemporaryState_option_default'),
+ key: 'groupTabTemporaryStateForAPI',
+ value: Constants.kGROUP_TAB_TEMPORARY_STATE_NOTHING,
+ type: 'radio'
+ },
+ {
+ title: `${browser.i18n.getMessage('config_groupTabTemporaryState_option_checked_before')}${browser.i18n.getMessage('groupTab_temporary_label')}${browser.i18n.getMessage('config_groupTabTemporaryState_option_checked_after')}`,
+ key: 'groupTabTemporaryStateForAPI',
+ value: Constants.kGROUP_TAB_TEMPORARY_STATE_PASSIVE,
+ type: 'radio'
+ },
+ {
+ title: `${browser.i18n.getMessage('config_groupTabTemporaryState_option_checked_before')}${browser.i18n.getMessage('groupTab_temporaryAggressive_label')}${browser.i18n.getMessage('config_groupTabTemporaryState_option_checked_after')}`,
+ key: 'groupTabTemporaryStateForAPI',
+ value: Constants.kGROUP_TAB_TEMPORARY_STATE_AGGRESSIVE,
+ type: 'radio'
+ },
+ ],
+ },
+ ],
},
{
title: browser.i18n.getMessage('config_treeBehavior_caption'),
diff --git a/waterfox/browser/components/sidebar/background/handle-misc.js b/waterfox/browser/components/sidebar/background/handle-misc.js
index 77e8f8eff7ab9..b0de426fde4cb 100644
--- a/waterfox/browser/components/sidebar/background/handle-misc.js
+++ b/waterfox/browser/components/sidebar/background/handle-misc.js
@@ -264,7 +264,7 @@ async function onShortcutCommand(command) {
return;
case 'simulateUpOnTree':
- if (SidebarConnection.isSidebarOpen(activeTab.windowId)) {
+ if (SidebarConnection.isOpen(activeTab.windowId)) {
if (configs.faviconizePinnedTabs &&
(activeTab.pinned ||
activeTab == Tab.getFirstNormalTab(activeTab.windowId))) {
@@ -291,7 +291,7 @@ async function onShortcutCommand(command) {
}
return;
case 'simulateDownOnTree':
- if (SidebarConnection.isSidebarOpen(activeTab.windowId)) {
+ if (SidebarConnection.isOpen(activeTab.windowId)) {
if (configs.faviconizePinnedTabs &&
activeTab.pinned) {
const nextActiveId = await browser.runtime.sendMessage({
@@ -317,7 +317,7 @@ async function onShortcutCommand(command) {
}
return;
case 'simulateLeftOnTree':
- if (SidebarConnection.isSidebarOpen(activeTab.windowId)) {
+ if (SidebarConnection.isOpen(activeTab.windowId)) {
if (configs.faviconizePinnedTabs &&
activeTab.pinned) {
const nextActiveId = await browser.runtime.sendMessage({
@@ -342,7 +342,7 @@ async function onShortcutCommand(command) {
}
return;
case 'simulateRightOnTree':
- if (SidebarConnection.isSidebarOpen(activeTab.windowId)) {
+ if (SidebarConnection.isOpen(activeTab.windowId)) {
if (configs.faviconizePinnedTabs &&
activeTab.pinned) {
const nextActiveId = await browser.runtime.sendMessage({
@@ -453,12 +453,14 @@ async function onShortcutCommand(command) {
function focusPrevious(activeTab) {
const nextActive = activeTab.$TST.nearestVisiblePrecedingTab ||
+ (!SidebarConnection.isOpen(activeTab.windowId) && activeTab.$TST.previousTab) ||
Tab.getLastVisibleTab(activeTab.windowId);
TabsInternalOperation.activateTab(nextActive);
}
function focusPreviousSilently(activeTab) {
const nextActive = activeTab.$TST.nearestVisiblePrecedingTab ||
+ (!SidebarConnection.isOpen(activeTab.windowId) && activeTab.$TST.previousTab) ||
Tab.getLastVisibleTab(activeTab.windowId);
TabsInternalOperation.activateTab(nextActive, {
silently: true,
@@ -466,13 +468,15 @@ function focusPreviousSilently(activeTab) {
}
function focusNext(activeTab) {
- const nextActive = activeTab.$TST.nextVisibleTab ||
+ const nextActive = activeTab.$TST.nearestVisibleFollowingTab ||
+ (!SidebarConnection.isOpen(activeTab.windowId) && activeTab.$TST.nextTab) ||
Tab.getFirstVisibleTab(activeTab.windowId);
TabsInternalOperation.activateTab(nextActive);
}
function focusNextSilently(activeTab) {
const nextActive = activeTab.$TST.nearestVisibleFollowingTab ||
+ (!SidebarConnection.isOpen(activeTab.windowId) && activeTab.$TST.nextTab) ||
Tab.getFirstVisibleTab(activeTab.windowId);
TabsInternalOperation.activateTab(nextActive, {
silently: true,
@@ -953,9 +957,27 @@ function onMessageExternal(message, sender) {
case TSTAPI.kGROUP_TABS:
return (async () => {
const tabs = await TSTAPI.getTargetTabs(message, sender);
+ const temporaryStateParams = (message.temporary && !message.temporaryAggressive) ?
+ {
+ temporary: true,
+ temporaryAggressive: false,
+ } :
+ (!message.temporary && message.temporaryAggressive) ?
+ {
+ temporary: false,
+ temporaryAggressive: true,
+ } :
+ (message.temporaryAggressive === false && message.temporary === false) ?
+ {
+ temporary: false,
+ temporaryAggressive: false,
+ } :
+ {};
const tab = await TabsGroup.groupTabs(Array.from(tabs), {
title: message.title,
broadcast: true,
+ ...TabsGroup.temporaryStateParams(configs.groupTabTemporaryStateForAPI),
+ ...temporaryStateParams,
});
if (!tab)
return null;
diff --git a/waterfox/browser/components/sidebar/background/handle-moved-tabs.js b/waterfox/browser/components/sidebar/background/handle-moved-tabs.js
index b819f6134c693..9060dc748ee64 100644
--- a/waterfox/browser/components/sidebar/background/handle-moved-tabs.js
+++ b/waterfox/browser/components/sidebar/background/handle-moved-tabs.js
@@ -52,8 +52,7 @@ Tab.onCreated.addListener((tab, info = {}) => {
info.movedBySelfWhileCreation) &&
(tab.$TST.nearestCompletelyOpenedNormalFollowingTab ||
tab.$TST.nearestCompletelyOpenedNormalPrecedingTab ||
- (info.treeForActionDetection &&
- info.treeForActionDetection.target &&
+ (info.treeForActionDetection?.target &&
(info.treeForActionDetection.target.next ||
info.treeForActionDetection.target.previous)))) {
tryFixupTreeForInsertedTab(tab, {
@@ -73,21 +72,21 @@ Tab.onMoving.addListener((tab, moveInfo) => {
const win = TabsStore.windows.get(tab.windowId);
const isNewlyOpenedTab = win.openingTabs.has(tab.id);
const positionControlled = configs.insertNewChildAt != Constants.kINSERT_NO_CONTROL;
- if (isNewlyOpenedTab &&
- !moveInfo.byInternalOperation &&
- !moveInfo.alreadyMoved &&
- !moveInfo.isSubstantiallyMoved &&
- positionControlled) {
- const opener = tab.$TST.openerTab;
- // if there is no valid opener, it can be a restored initial tab in a restored window
- // and can be just moved as a part of window restoration process.
- if (opener) {
- log('onTabMove for new child tab: move back '+moveInfo.toIndex+' => '+moveInfo.fromIndex);
- moveBack(tab, moveInfo);
- return false;
- }
- }
- return true;
+ if (!isNewlyOpenedTab ||
+ !positionControlled ||
+ moveInfo.byInternalOperation ||
+ moveInfo.alreadyMoved ||
+ !moveInfo.movedInBulk)
+ return true;
+
+ // if there is no valid opener, it can be a restored initial tab in a restored window
+ // and can be just moved as a part of window restoration process.
+ if (!tab.$TST.openerTab)
+ return true;
+
+ log('onTabMove for new child tab: move back '+moveInfo.toIndex+' => '+moveInfo.fromIndex);
+ moveBack(tab, moveInfo);
+ return false;
});
async function tryFixupTreeForInsertedTab(tab, moveInfo = {}) {
@@ -96,6 +95,7 @@ async function tryFixupTreeForInsertedTab(tab, moveInfo = {}) {
...moveInfo,
});
log('tryFixupTreeForInsertedTab ', {
+ tab: tab.id,
parentTabOperationBehavior,
moveInfo,
childIds: tab.$TST.childIds,
@@ -118,9 +118,10 @@ async function tryFixupTreeForInsertedTab(tab, moveInfo = {}) {
broadcast: true
});
}
- await Tree.detachTab(tab, {
- broadcast: true
- });
+ if (tab.$TST.parentId)
+ await Tree.detachTab(tab, {
+ broadcast: true
+ });
// Pinned tab is moved at first, so Tab.onPinned handler cannot know tree information
// before the pinned tab was moved. Thus we cache tree information for the handler.
wait(100).then(() => {
@@ -134,11 +135,19 @@ async function tryFixupTreeForInsertedTab(tab, moveInfo = {}) {
isMovingByShortcut: mMaybeTabMovingByShortcut,
...moveInfo,
});
+ log(' => action: ', action);
if (!action.action) {
log('no action');
return;
}
+ // When multiple tabs are moved at once by outside of TST (e.g. moving of multiselected tabs)
+ // Tree.detectTabActionFromNewPosition() may be called for other tabs asynchronously
+ // before this operation finishes. Thus we need to memorize the calculated "parent"
+ // and Tree.detectTabActionFromNewPosition() will use it.
+ if (action.parent)
+ tab.$TST.temporaryMetadata.set('goingToBeAttachedTo', action.parent);
+
// notify event to helper addons with action and allow or deny
const cache = {};
const allowed = await TSTAPI.tryOperationAllowed(
@@ -155,22 +164,26 @@ async function tryFixupTreeForInsertedTab(tab, moveInfo = {}) {
{ tabProperties: ['tab', 'parent', 'insertBefore', 'insertAfter'], cache }
);
TSTAPI.clearCache(cache);
+
if (!allowed) {
log('no action - canceled by a helper addon');
- return;
}
+ else {
+ log('action: ', action);
+ switch (action.action) {
+ case 'invalid':
+ moveBack(tab, moveInfo);
+ break;
- log('action: ', action);
- switch (action.action) {
- case 'invalid':
- moveBack(tab, moveInfo);
- return;
-
- default:
- log('tryFixupTreeForInsertedTab: apply action for unattached tab: ', tab, action);
- await action.apply();
- return;
+ default:
+ log('tryFixupTreeForInsertedTab: apply action for unattached tab: ', tab, action);
+ await action.apply();
+ break;
+ }
}
+
+ if (tab.$TST.temporaryMetadata.get('goingToBeAttachedTo') == action.parent)
+ tab.$TST.temporaryMetadata.delete('goingToBeAttachedTo');
}
function reserveToEnsureRootTabVisible(tab) {
@@ -196,14 +209,14 @@ function reserveToEnsureRootTabVisible(tab) {
reserveToEnsureRootTabVisible.tabIds = new Set();
Tab.onMoved.addListener((tab, moveInfo = {}) => {
- if (!moveInfo.byInternalOperation &&
- !moveInfo.isSubstantiallyMoved &&
- !tab.$TST.duplicating) {
- log('process moved tab');
- tryFixupTreeForInsertedTab(tab, moveInfo);
+ if (moveInfo.byInternalOperation ||
+ !moveInfo.movedInBulk ||
+ tab.$TST.duplicating) {
+ log('internal move');
}
else {
- log('internal move');
+ log('process moved tab');
+ tryFixupTreeForInsertedTab(tab, moveInfo);
}
reserveToEnsureRootTabVisible(tab);
});
@@ -255,7 +268,8 @@ function moveBack(tab, moveInfo) {
log('Move back tab from unexpected move: ', dumpTab(tab), moveInfo);
const id = tab.id;
const win = TabsStore.windows.get(tab.windowId);
- win.internalMovingTabs.add(id);
+ const index = moveInfo.fromIndex;
+ win.internalMovingTabs.set(id, index);
logApiTabs(`handle-moved-tabs:moveBack: browser.tabs.move() `, tab.id, {
windowId: moveInfo.windowId,
index: moveInfo.fromIndex
@@ -266,7 +280,7 @@ function moveBack(tab, moveInfo) {
windowId: moveInfo.windowId,
index: moveInfo.fromIndex
}).catch(ApiTabs.createErrorHandler(e => {
- if (win.internalMovingTabs.has(id))
+ if (win.internalMovingTabs.get(id) == index)
win.internalMovingTabs.delete(id);
ApiTabs.handleMissingTabError(e);
}));
diff --git a/waterfox/browser/components/sidebar/background/handle-tab-bunches.js b/waterfox/browser/components/sidebar/background/handle-tab-bunches.js
index edb58d022b7dd..52483a8fc0a37 100644
--- a/waterfox/browser/components/sidebar/background/handle-tab-bunches.js
+++ b/waterfox/browser/components/sidebar/background/handle-tab-bunches.js
@@ -104,9 +104,14 @@ async function tryDetectTabBunches(win) {
}
if (tabReferences.length > 1) {
- for (const tabReference of tabReferences) {
- Tab.get(tabReference.id).$TST.temporaryMetadata.set('openedWithOthers', true);
- }
+ await Promise.all(tabReferences.map(tabReference => {
+ const tab = Tab.get(tabReference.id);
+ tab.$TST.temporaryMetadata.set('openedWithOthers', true);
+ // We need to wait until all tabs are handlede completely.
+ // Otherwise `tab.$TST.needToBeGroupedSiblings` may contain unrelated tabs
+ // (tabs opened from any other parent tab) unexpectedly.
+ return tab.$TST.opened;
+ }));
}
if (areTabsFromOtherDeviceWithInsertAfterCurrent(tabReferences) &&
diff --git a/waterfox/browser/components/sidebar/background/prefs.js b/waterfox/browser/components/sidebar/background/prefs.js
index 780dccd36de83..7ae0b866800aa 100644
--- a/waterfox/browser/components/sidebar/background/prefs.js
+++ b/waterfox/browser/components/sidebar/background/prefs.js
@@ -11,8 +11,6 @@ import {
} from '/common/common.js';
import * as Constants from '/common/constants.js';
-import Tab from '/common/Tab.js';
-
export const onChanged = new EventListenerManager();
if (Constants.IS_BACKGROUND) {
@@ -100,16 +98,3 @@ configs.$addObserver(async name => {
mNamesSyncToChrome.delete(name);
});
});
-
-browser.waterfoxBridge.onHoverPreviewChanged.addListener(enabled => {
- if (enabled) {
- for (const tab of Tab.getAllTabs()) {
- tab.$TST.registerTooltipText(browser.runtime.id, '', true);
- }
- }
- else {
- for (const tab of Tab.getAllTabs()) {
- tab.$TST.unregisterTooltipText(browser.runtime.id);
- }
- }
-});
diff --git a/waterfox/browser/components/sidebar/background/successor-tab.js b/waterfox/browser/components/sidebar/background/successor-tab.js
index 3b80bab5d50d7..b931a5a735cc3 100644
--- a/waterfox/browser/components/sidebar/background/successor-tab.js
+++ b/waterfox/browser/components/sidebar/background/successor-tab.js
@@ -14,6 +14,7 @@ import {
import * as ApiTabs from '/common/api-tabs.js';
import * as Constants from '/common/constants.js';
import * as SidebarConnection from '/common/sidebar-connection.js';
+import * as TabsInternalOperation from '/common/tabs-internal-operation.js';
import * as TabsStore from '/common/tabs-store.js';
import * as TreeBehavior from '/common/tree-behavior.js';
@@ -42,6 +43,17 @@ browser.tabs.onUpdated.addListener((tabId, updateInfo, _tab) => {
// properties: ['successorTabId'],
});
+TabsInternalOperation.onBeforeTabsRemove.addListener(async tabs => {
+ let activeTab = null;
+ const tabIds = tabs.map(tab => {
+ if (tab.active)
+ activeTab = tab;
+ return tab.id;
+ });
+ if (activeTab)
+ await updateInternal(activeTab.id, tabIds);
+});
+
function setSuccessor(tabId, successorTabId = -1) {
const tab = Tab.get(tabId);
const successorTab = Tab.get(successorTabId);
@@ -133,7 +145,7 @@ function update(tabId) {
}
}, 0);
}
-async function updateInternal(tabId) {
+async function updateInternal(tabId, excludeTabIds = []) {
// tabs.onActivated can be notified before the tab is completely tracked...
await Tab.waitUntilTracked(tabId);
const tab = Tab.get(tabId);
@@ -190,6 +202,15 @@ async function updateInternal(tabId) {
let successor = null;
if (renewedTab.active) {
log('it is active, so reset successor');
+ const excludeTabIdsSet = new Set(excludeTabIds);
+ const findSuccessor = (...candidates) => {
+ for (const candidate of candidates) {
+ if (!excludeTabIdsSet.has(candidate?.id) &&
+ candidate)
+ return candidate;
+ }
+ return null;
+ };
if (configs.successorTabControlLevel == Constants.kSUCCESSOR_TAB_CONTROL_IN_TREE) {
const closeParentBehavior = TreeBehavior.getParentTabOperationBehavior(tab, {
context: Constants.kPARENT_TAB_OPERATION_CONTEXT_CLOSE,
@@ -203,7 +224,11 @@ async function updateInternal(tabId) {
const firstChild = collapsedChildSuccessorAllowed ? tab.$TST.firstChild : tab.$TST.firstVisibleChild;
const nextVisibleSibling = tab.$TST.nextVisibleSiblingTab;
const nearestVisiblePreceding = tab.$TST.nearestVisiblePrecedingTab;
- successor = firstChild || nextVisibleSibling || nearestVisiblePreceding;
+ successor = findSuccessor(
+ firstChild,
+ nextVisibleSibling,
+ nearestVisiblePreceding
+ );
log(` possible successor: ${dumpTab(tab)}: `, successor, {
closeParentBehavior,
collapsedChildSuccessorAllowed,
@@ -216,22 +241,29 @@ async function updateInternal(tabId) {
successor.discarded &&
configs.avoidDiscardedTabToBeActivatedIfPossible) {
log(` ${dumpTab(successor)} is discarded.`);
- successor = tab.$TST.nearestLoadedSiblingTab ||
- tab.$TST.nearestLoadedTabInTree ||
- tab.$TST.nearestLoadedTab ||
- successor;
+ successor = findSuccessor(
+ tab.$TST.nearestLoadedSiblingTab,
+ tab.$TST.nearestLoadedTabInTree,
+ tab.$TST.nearestLoadedTab,
+ successor
+ );
log(` => redirected successor is: ${dumpTab(successor)}`);
}
}
else {
- successor = tab.$TST.nearestVisibleFollowingTab || tab.$TST.nearestVisiblePrecedingTab;
+ successor = findSuccessor(
+ tab.$TST.nearestVisibleFollowingTab,
+ tab.$TST.nearestVisiblePrecedingTab
+ );
log(` possible successor: ${dumpTab(tab)}`);
if (successor &&
successor.discarded &&
configs.avoidDiscardedTabToBeActivatedIfPossible) {
log(` ${dumpTab(successor)} is discarded.`);
- successor = tab.$TST.nearestLoadedTab ||
- successor;
+ successor = findSuccessor(
+ tab.$TST.nearestLoadedTab,
+ successor
+ );
log(` => redirected successor is: ${dumpTab(successor)}`);
}
}
@@ -243,8 +275,7 @@ async function updateInternal(tabId) {
}
else {
log(` ${dumpTab(tab)} is out of control.`, {
- active: renewedTab.active,
- successor: successor && successor.id
+ active: renewedTab.active,
});
clearSuccessor(renewedTab.id);
}
diff --git a/waterfox/browser/components/sidebar/background/tab-context-menu.js b/waterfox/browser/components/sidebar/background/tab-context-menu.js
index 33ae764db4a1f..faeb564c9afd4 100644
--- a/waterfox/browser/components/sidebar/background/tab-context-menu.js
+++ b/waterfox/browser/components/sidebar/background/tab-context-menu.js
@@ -197,6 +197,9 @@ const mItemsById = {
title: browser.i18n.getMessage('tabContextMenu_close_label'),
titleMultiselected: browser.i18n.getMessage('tabContextMenu_close_label_multiselected')
},
+ 'context_closeDuplicatedTabs': {
+ title: browser.i18n.getMessage('tabContextMenu_closeDuplicatedTabs_label')
+ },
'context_closeMultipleTabs': {
title: browser.i18n.getMessage('tabContextMenu_closeMultipleTabs_label')
},
@@ -694,6 +697,7 @@ async function onShown(info, contextTab) {
const previousSiblingTab = contextTab && contextTab.$TST.previousSiblingTab;
const nextTab = contextTab && contextTab.$TST.nextTab;
const nextSiblingTab = contextTab && contextTab.$TST.nextSiblingTab;
+ const hasDuplicatedTabs = Tab.hasDuplicatedTabs(windowId);
const hasMultipleTabs = Tab.hasMultipleTabs(windowId);
const hasMultipleNormalTabs = Tab.hasMultipleTabs(windowId, { normal: true });
const multiselected = contextTab && contextTab.$TST.multiselected;
@@ -908,6 +912,11 @@ async function onShown(info, contextTab) {
multiselected
}) && modifiedItemsCount++;
+ updateItem('context_closeDuplicatedTabs', {
+ visible: emulate && !!contextTab,
+ enabled: hasDuplicatedTabs,
+ multiselected
+ }) && modifiedItemsCount++;
updateItem('context_closeMultipleTabs', {
visible: emulate && !!contextTab,
enabled: hasMultipleNormalTabs,
@@ -1251,6 +1260,28 @@ async function onClick(info, contextTab) {
case 'context_bookmarkSelected':
Commands.bookmarkTab(contextTab || activeTab);
break;
+ case 'context_closeDuplicatedTabs': {
+ const tabs = await browser.tabs.query({ windowId }).catch(ApiTabs.createErrorHandler());
+ tabs.sort((a, b) => b.lastAccessed - a.lastAccessed);
+ const tabKeys = new Set();
+ const closeTabs = [];
+ for (const tab of tabs) {
+ const key = `${tab.cookieStoreId}\n${tab.url}`;
+ if (tabKeys.has(key)) {
+ closeTabs.push(Tab.get(tab.id));
+ continue;
+ }
+ tabKeys.add(key);
+ }
+ const canceled = (await browser.runtime.sendMessage({
+ type: Constants.kCOMMAND_NOTIFY_TABS_CLOSING,
+ tabs: closeTabs.map(tab => tab.$TST.sanitized),
+ windowId,
+ }).catch(ApiTabs.createErrorHandler())) === false;
+ if (canceled)
+ break;
+ TabsInternalOperation.removeTabs(closeTabs);
+ } break;
case 'context_closeTabsToTheStart': {
const tabs = await browser.tabs.query({ windowId }).catch(ApiTabs.createErrorHandler());
const closeTabs = [];
diff --git a/waterfox/browser/components/sidebar/background/tabs-group.js b/waterfox/browser/components/sidebar/background/tabs-group.js
index 977594b7075db..cfdfc0601e2a3 100644
--- a/waterfox/browser/components/sidebar/background/tabs-group.js
+++ b/waterfox/browser/components/sidebar/background/tabs-group.js
@@ -30,7 +30,7 @@ function log(...args) {
internalLogger('background/tabs-group', ...args);
}
-export function makeGroupTabURI({ title, temporary, temporaryAggressive, openerTabId, aliasTabId } = {}) {
+export function makeGroupTabURI({ title, temporary, temporaryAggressive, openerTabId, aliasTabId, replacedParentCount } = {}) {
const url = new URL(Constants.kGROUP_TAB_URI);
if (title)
@@ -47,19 +47,31 @@ export function makeGroupTabURI({ title, temporary, temporaryAggressive, openerT
if (aliasTabId)
url.searchParams.set('aliasTabId', aliasTabId);
+ if (replacedParentCount)
+ url.searchParams.set('replacedParentCount', replacedParentCount);
+
return url.href;
}
export function temporaryStateParams(state) {
switch (state) {
case Constants.kGROUP_TAB_TEMPORARY_STATE_PASSIVE:
- return { temporary: true };
+ return {
+ temporary: true,
+ temporaryAggressive: false,
+ };
case Constants.kGROUP_TAB_TEMPORARY_STATE_AGGRESSIVE:
- return { temporaryAggressive: true };
+ return {
+ temporary: false,
+ temporaryAggressive: true,
+ };
default:
break;
}
- return {};
+ return {
+ temporary: false,
+ temporaryAggressive: false,
+ };
}
export async function groupTabs(tabs, { broadcast, parent, withDescendants, ...groupTabOptions } = {}) {
@@ -67,7 +79,7 @@ export async function groupTabs(tabs, { broadcast, parent, withDescendants, ...g
if (rootTabs.length <= 0)
return null;
- log('groupTabs: ', () => tabs.map(dumpTab));
+ log('groupTabs: ', () => tabs.map(dumpTab), { broadcast, parent, withDescendants });
const uri = makeGroupTabURI({
title: browser.i18n.getMessage('groupTab_label', rootTabs[0].title),
@@ -81,10 +93,19 @@ export async function groupTabs(tabs, { broadcast, parent, withDescendants, ...g
inBackground: true
});
- if (!withDescendants)
+ if (!withDescendants) {
+ const structure = TreeBehavior.getTreeStructureFromTabs(tabs);
+
await Tree.detachTabsFromTree(tabs, {
- broadcast: !!broadcast
+ broadcast: !!broadcast,
});
+
+ log('structure: ', structure);
+ await Tree.applyTreeStructureToTabs(tabs, structure, {
+ broadcast: !!broadcast,
+ });
+ }
+
await TabsMove.moveTabsAfter(tabs.slice(1), tabs[0], {
broadcast: !!broadcast
});
@@ -92,7 +113,7 @@ export async function groupTabs(tabs, { broadcast, parent, withDescendants, ...g
await Tree.attachTabTo(tab, groupTab, {
forceExpand: true, // this is required to avoid the group tab itself is active from active tab in collapsed tree
dontMove: true,
- broadcast: !!broadcast
+ broadcast: !!broadcast,
});
}
return groupTab;
@@ -163,7 +184,8 @@ export async function tryReplaceTabWithGroup(tab, { windowId, parent, children,
const firstChild = children[0];
const uri = makeGroupTabURI({
title: browser.i18n.getMessage('groupTab_label', firstChild.title),
- ...temporaryStateParams(configs.groupTabTemporaryStateForOrphanedTabs)
+ ...temporaryStateParams(configs.groupTabTemporaryStateForOrphanedTabs),
+ replacedParentCount: (tab?.$TST?.replacedParentGroupTabCount || 0) + 1,
});
const win = TabsStore.windows.get(windowId);
win.toBeOpenedTabsWithPositions++;
diff --git a/waterfox/browser/components/sidebar/background/tabs-move.js b/waterfox/browser/components/sidebar/background/tabs-move.js
index 1edb53a9c6432..1de08e9d9c119 100644
--- a/waterfox/browser/components/sidebar/background/tabs-move.js
+++ b/waterfox/browser/components/sidebar/background/tabs-move.js
@@ -14,7 +14,7 @@
* The Original Code is the Tree Style Tab.
*
* The Initial Developer of the Original Code is YUKI "Piro" Hiroshi.
- * Portions created by the Initial Developer are Copyright (C) 2011-2023
+ * Portions created by the Initial Developer are Copyright (C) 2011-2024
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): YUKI "Piro" Hiroshi
@@ -101,15 +101,15 @@ async function moveTabsInternallyBefore(tabs, referenceTab, options = {}) {
const oldNextTab = tab.$TST.unsafeNextTab;
if (oldNextTab && oldNextTab.id == referenceTab.id) // no move case
continue;
- if (SidebarConnection.isInitialized()) { // only on the background page
- win.internalMovingTabs.add(tab.id);
- win.alreadyMovedTabs.add(tab.id);
- }
const fromIndex = tab.index;
if (referenceTab.index > tab.index)
tab.index = referenceTab.index - 1;
else
tab.index = referenceTab.index;
+ if (SidebarConnection.isInitialized()) { // only on the background page
+ win.internalMovingTabs.set(tab.id, tab.index);
+ win.alreadyMovedTabs.set(tab.id, tab.index);
+ }
tab.reindexedBy = `moveTabsInternallyBefore (${tab.index})`;
Tab.track(tab);
movedTabs.push(tab);
@@ -211,10 +211,6 @@ async function moveTabsInternallyAfter(tabs, referenceTab, options = {}) {
if ((!oldNextTab && !nextTab) ||
(oldNextTab && nextTab && oldNextTab.id == nextTab.id)) // no move case
continue;
- if (SidebarConnection.isInitialized()) { // only on the background page
- win.internalMovingTabs.add(tab.id);
- win.alreadyMovedTabs.add(tab.id);
- }
const fromIndex = tab.index;
if (nextTab) {
if (nextTab.index > tab.index)
@@ -225,6 +221,10 @@ async function moveTabsInternallyAfter(tabs, referenceTab, options = {}) {
else {
tab.index = win.tabs.size - 1
}
+ if (SidebarConnection.isInitialized()) { // only on the background page
+ win.internalMovingTabs.set(tab.id, tab.index);
+ win.alreadyMovedTabs.set(tab.id, tab.index);
+ }
tab.reindexedBy = `moveTabsInternallyAfter (${tab.index})`;
Tab.track(tab);
movedTabs.push(tab);
@@ -363,8 +363,8 @@ async function syncToNativeTabsInternal(windowId) {
toIndex--;
log(`syncToNativeTabs(${windowId}): step1, move ${moveTabIds.join(',')} before ${referenceId} / from = ${fromIndex}, to = ${toIndex}`);
for (const movedId of moveTabIds) {
- win.internalMovingTabs.add(movedId);
- win.alreadyMovedTabs.add(movedId);
+ win.internalMovingTabs.set(movedId, -1);
+ win.alreadyMovedTabs.set(movedId, -1);
movedTabs.add(movedId);
}
logApiTabs(`tabs-move:syncToNativeTabs(${windowId}): step1, browser.tabs.move() `, moveTabIds, {
diff --git a/waterfox/browser/components/sidebar/background/tabs-open.js b/waterfox/browser/components/sidebar/background/tabs-open.js
index 5ab817910df55..22f2197b75bba 100644
--- a/waterfox/browser/components/sidebar/background/tabs-open.js
+++ b/waterfox/browser/components/sidebar/background/tabs-open.js
@@ -299,7 +299,8 @@ function onMessage(message, openerTab) {
windowId: message.windowId,
parent: Tab.get(message.parentId),
insertBefore: Tab.get(message.insertBeforeId),
- insertAfter: Tab.get(message.insertAfterId)
+ insertAfter: Tab.get(message.insertAfterId),
+ active: !!message.active,
});
});
break;
diff --git a/waterfox/browser/components/sidebar/background/tree.js b/waterfox/browser/components/sidebar/background/tree.js
index fd12819d6c10e..494b732546ba4 100644
--- a/waterfox/browser/components/sidebar/background/tree.js
+++ b/waterfox/browser/components/sidebar/background/tree.js
@@ -455,11 +455,11 @@ export function getReferenceTabsForNewChild(child, parent, { insertAt, ignoreTab
case Constants.kINSERT_END:
default:
insertAfter = lastDescendant;
- log(` insert ${child && child.id} after lastDescendant ${insertAfter && insertAfter.id} (insertAt=kINSERT_END)`);
+ log(` insert ${child?.id} after lastDescendant ${insertAfter?.id} (insertAt=kINSERT_END)`);
break;
case Constants.kINSERT_TOP:
insertBefore = firstChild;
- log(` insert ${child && child.id} before firstChild ${insertBefore && insertBefore.id} (insertAt=kINSERT_TOP)`);
+ log(` insert ${child?.id} before firstChild ${insertBefore?.id} (insertAt=kINSERT_TOP)`);
break;
case Constants.kINSERT_NEAREST: {
const allTabs = Tab.getOtherTabs((child || parent).windowId, ignoreTabs);
@@ -468,11 +468,11 @@ export function getReferenceTabsForNewChild(child, parent, { insertAt, ignoreTab
if (index < allTabs.indexOf(firstChild)) {
insertBefore = firstChild;
insertAfter = parent;
- log(` insert ${child && child.id} between parent ${insertAfter && insertAfter.id} and firstChild ${insertBefore && insertBefore.id} (insertAt=kINSERT_NEAREST)`);
+ log(` insert ${child?.id} between parent ${insertAfter?.id} and firstChild ${insertBefore?.id} (insertAt=kINSERT_NEAREST)`);
}
else if (index > allTabs.indexOf(lastDescendant)) {
insertAfter = lastDescendant;
- log(` insert ${child && child.id} after lastDescendant ${insertAfter && insertAfter.id} (insertAt=kINSERT_NEAREST)`);
+ log(` insert ${child?.id} after lastDescendant ${insertAfter?.id} (insertAt=kINSERT_NEAREST)`);
}
else { // inside the tree
if (parent && !children)
@@ -483,12 +483,12 @@ export function getReferenceTabsForNewChild(child, parent, { insertAt, ignoreTab
if (index > allTabs.indexOf(child))
continue;
insertBefore = child;
- log(` insert ${child && child.id} before nearest following child ${insertBefore && insertBefore.id} (insertAt=kINSERT_NEAREST)`);
+ log(` insert ${child?.id} before nearest following child ${insertBefore?.id} (insertAt=kINSERT_NEAREST)`);
break;
}
if (!insertBefore) {
insertAfter = lastDescendant;
- log(` insert ${child && child.id} after lastDescendant ${insertAfter && insertAfter.id} (insertAt=kINSERT_NEAREST)`);
+ log(` insert ${child?.id} after lastDescendant ${insertAfter?.id} (insertAt=kINSERT_NEAREST)`);
}
}
}; break;
@@ -500,11 +500,11 @@ export function getReferenceTabsForNewChild(child, parent, { insertAt, ignoreTab
lastRelatedTab = child && parent.$TST.lastRelatedTabId == child.id ? parent.$TST.previousLastRelatedTab : parent.$TST.lastRelatedTab; // it could be updated already...
if (lastRelatedTab) {
insertAfter = lastRelatedTab.$TST.lastDescendant || lastRelatedTab;
- log(` insert ${child && child.id} after lastRelatedTab ${lastRelatedTab.id} (insertAt=kINSERT_NEXT_TO_LAST_RELATED_TAB)`);
+ log(` insert ${child?.id} after lastRelatedTab ${lastRelatedTab.id} (insertAt=kINSERT_NEXT_TO_LAST_RELATED_TAB)`);
}
else {
insertBefore = firstChild;
- log(` insert ${child && child.id} before firstChild (insertAt=kINSERT_NEXT_TO_LAST_RELATED_TAB)`);
+ log(` insert ${child?.id} before firstChild (insertAt=kINSERT_NEXT_TO_LAST_RELATED_TAB)`);
}
}; break;
case Constants.kINSERT_NO_CONTROL:
@@ -513,23 +513,23 @@ export function getReferenceTabsForNewChild(child, parent, { insertAt, ignoreTab
}
else {
insertAfter = parent;
- log(` insert ${child && child.id} after parent`);
+ log(` insert ${child?.id} after parent`);
}
if (insertBefore == child) {
// Return unsafe tab, to avoid placing the child after hidden tabs
// (too far from the place it should be.)
- insertBefore = insertBefore && insertBefore.$TST.unsafeNextTab;
- log(` => insert ${child && child.id} before next tab ${insertBefore && insertBefore.id} of the child tab itelf`);
+ insertBefore = insertBefore?.$TST.unsafeNextTab;
+ log(` => insert ${child?.id} before next tab ${insertBefore?.id} of the child tab itelf`);
}
if (insertAfter == child) {
- insertAfter = insertAfter && insertAfter.$TST.previousTab;
- log(` => insert ${child && child.id} after previous tab ${insertAfter && insertAfter.id} of the child tab itelf`);
+ insertAfter = insertAfter?.$TST.previousTab;
+ log(` => insert ${child?.id} after previous tab ${insertAfter?.id} of the child tab itelf`);
}
// disallow to place tab in invalid position
if (insertBefore) {
if (parent && insertBefore.index <= parent.index) {
insertBefore = null;
- log(` => do not put ${child && child.id} before a tab preceding to the parent`);
+ log(` => do not put ${child?.id} before a tab preceding to the parent`);
}
//TODO: we need to reject more cases...
}
@@ -541,7 +541,7 @@ export function getReferenceTabsForNewChild(child, parent, { insertAt, ignoreTab
if (lastMember != insertAfter &&
insertAfter.index >= lastMember.index) {
insertAfter = lastMember;
- log(` => do not put ${child && child.id} after the last tab ${insertAfter && insertAfter.id} in the tree`);
+ log(` => do not put ${child?.id} after the last tab ${insertAfter?.id} in the tree`);
}
//TODO: we need to reject more cases...
}
@@ -551,8 +551,7 @@ export function getReferenceTabsForNewChild(child, parent, { insertAt, ignoreTab
export function getReferenceTabsForNewNextSibling(base, options = {}) {
log('getReferenceTabsForNewNextSibling ', base);
let insertBefore = base.$TST.nextSiblingTab;
- if (insertBefore &&
- insertBefore.pinned &&
+ if (insertBefore?.pinned &&
!options.pinned) {
insertBefore = Tab.getFirstNormalTab(base.windowId);
}
@@ -634,6 +633,7 @@ export async function detachTabsFromTree(tabs, options = {}) {
promisedAttach.push(detachAllChildren(tab, {
...options,
behavior: Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_FIRST_CHILD,
+ ignoreTabs: tabs,
}));
}
if (promisedAttach.length > 0)
@@ -642,7 +642,7 @@ export async function detachTabsFromTree(tabs, options = {}) {
export async function detachAllChildren(
tab = null,
- { windowId, children, descendants, parent, nearestFollowingRootTab, newParent, behavior, dontExpand, dontSyncParentToOpenerTab,
+ { windowId, children, descendants, parent, nearestFollowingRootTab, newParent, ignoreTabs, behavior, dontExpand, dontSyncParentToOpenerTab,
...options } = {}
) {
if (tab) {
@@ -652,12 +652,14 @@ export async function detachAllChildren(
descendants = tab.$TST.descendants;
}
log('detachAllChildren: ',
- tab && tab.id,
+ tab?.id,
{ children, parent, nearestFollowingRootTab, newParent, behavior, dontExpand, dontSyncParentToOpenerTab },
options);
// the "children" option is used for removing tab.
children = children ? children.map(TabsStore.ensureLivingTab) : tab.$TST.children;
+ const ignoreTabsSet = new Set(ignoreTabs || []);
+
if (behavior == Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_FIRST_CHILD &&
newParent &&
!children.includes(newParent))
@@ -675,9 +677,11 @@ export async function detachAllChildren(
options.dontUpdateInsertionPositionInfo = true;
// the "parent" option is used for removing tab.
- parent = TabsStore.ensureLivingTab(parent) || (tab && tab.$TST.parent);
- if (tab &&
- tab.$TST.isGroupTab &&
+ parent = TabsStore.ensureLivingTab(parent) || tab?.$TST.parent;
+ while (ignoreTabsSet.has(parent)) {
+ parent = parent.$TST.parent;
+ }
+ if (tab?.$TST.isGroupTab &&
Tab.getRemovingTabs(tab.windowId).length == children.length) {
behavior = Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_ALL_CHILDREN;
options.dontUpdateIndent = false;
@@ -689,7 +693,7 @@ export async function detachAllChildren(
!configs.moveTabsToBottomWhenDetachedFromClosedParent) {
nextTab = nearestFollowingRootTab !== undefined ?
nearestFollowingRootTab :
- tab && tab.$TST.nearestFollowingRootTab;
+ tab?.$TST.nearestFollowingRootTab;
previousTab = nextTab ?
nextTab.$TST.previousTab :
Tab.getLastTab(windowId || tab.windowId);
@@ -734,9 +738,9 @@ export async function detachAllChildren(
promises.push(detachTab(child, { ...options, dontSyncParentToOpenerTab }));
// reference tabs can be closed while waiting...
- if (nextTab && nextTab.$TST.removing)
+ if (nextTab?.$TST.removing)
nextTab = null;
- if (previousTab && previousTab.$TST.removing)
+ if (previousTab?.$TST.removing)
previousTab = null;
if (nextTab) {
@@ -801,8 +805,7 @@ export async function behaveAutoAttachedTab(
baseTab = baseTab || Tab.getActiveTab(TabsStore.getCurrentWindowId() || tab.windowId);
log('behaveAutoAttachedTab ', tab.id, baseTab.id, { baseTab, behavior });
- if (baseTab &&
- baseTab.$TST.ancestors.includes(tab)) {
+ if (baseTab?.$TST.ancestors.includes(tab)) {
log(' => ignore possibly restored ancestor tab to avoid cyclic references');
return false;
}
@@ -1095,7 +1098,7 @@ async function collapseExpandSubtreeInternal(tab, params = {}) {
tabId: tab.id,
collapsed: !!params.collapsed,
justNow: params.justNow,
- anchorId: anchor && anchor.id,
+ anchorId: anchor?.id,
visibilityChangedTabIds,
last: true
});
@@ -1231,7 +1234,7 @@ export async function collapseExpandTab(tab, params = {}) {
type: Constants.kCOMMAND_NOTIFY_TAB_COLLAPSED_STATE_CHANGED,
windowId: tab.windowId,
tabId: tab.id,
- anchorId: collapseExpandInfo.anchor && collapseExpandInfo.anchor.id,
+ anchorId: collapseExpandInfo.anchor?.id,
justNow: params.justNow,
collapsed: params.collapsed,
last,
@@ -1257,55 +1260,60 @@ export async function collapseExpandTreesIntelligentlyFor(tab, options = {}) {
}
win.doingIntelligentlyCollapseExpandCount++;
- const expandedAncestors = [tab.id]
- .concat(tab.$TST.ancestors.map(ancestor => ancestor.id))
- .concat(tab.$TST.descendants.map(descendant => descendant.id));
- const collapseTabs = Tab.getSubtreeCollapsedTabs(tab.windowId, {
- '!id': expandedAncestors
- });
- logCollapseExpand(`${collapseTabs.length} tabs can be collapsed, ancestors: `, expandedAncestors);
- const allowedToCollapse = new Set();
- await Promise.all(collapseTabs.map(async tab => {
- const allowed = await TSTAPI.tryOperationAllowed(
- TSTAPI.kNOTIFY_TRY_COLLAPSE_TREE_FROM_OTHER_EXPANSION,
- { tab },
- { tabProperties: ['tab'] }
- );
- if (allowed)
- allowedToCollapse.add(tab);
- }));
- for (const collapseTab of collapseTabs) {
- if (!allowedToCollapse.has(collapseTab))
- continue;
- let dontCollapse = false;
- const parentTab = collapseTab.$TST.parent;
- if (parentTab) {
- dontCollapse = true;
- if (!parentTab.$TST.subtreeCollapsed) {
- for (const ancestor of collapseTab.$TST.ancestors) {
- if (!expandedAncestors.includes(ancestor.id))
- continue;
- dontCollapse = false;
- break;
+ try {
+ const expandedAncestors = [tab.id]
+ .concat(tab.$TST.ancestors.map(ancestor => ancestor.id))
+ .concat(tab.$TST.descendants.map(descendant => descendant.id));
+ const collapseTabs = Tab.getSubtreeCollapsedTabs(tab.windowId, {
+ '!id': expandedAncestors
+ });
+ logCollapseExpand(`${collapseTabs.length} tabs can be collapsed, ancestors: `, expandedAncestors);
+ const allowedToCollapse = new Set();
+ await Promise.all(collapseTabs.map(async tab => {
+ const allowed = await TSTAPI.tryOperationAllowed(
+ TSTAPI.kNOTIFY_TRY_COLLAPSE_TREE_FROM_OTHER_EXPANSION,
+ { tab },
+ { tabProperties: ['tab'] }
+ );
+ if (allowed)
+ allowedToCollapse.add(tab);
+ }));
+ for (const collapseTab of collapseTabs) {
+ if (!allowedToCollapse.has(collapseTab))
+ continue;
+ let dontCollapse = false;
+ const parentTab = collapseTab.$TST.parent;
+ if (parentTab) {
+ dontCollapse = true;
+ if (!parentTab.$TST.subtreeCollapsed) {
+ for (const ancestor of collapseTab.$TST.ancestors) {
+ if (!expandedAncestors.includes(ancestor.id))
+ continue;
+ dontCollapse = false;
+ break;
+ }
}
}
+ logCollapseExpand(`${collapseTab.id}: dontCollapse = ${dontCollapse}`);
+
+ const manuallyExpanded = collapseTab.$TST.states.has(Constants.kTAB_STATE_SUBTREE_EXPANDED_MANUALLY);
+ if (!dontCollapse &&
+ !manuallyExpanded &&
+ collapseTab.$TST.descendants.every(tab => !tab.$TST.canBecomeSticky))
+ collapseExpandSubtree(collapseTab, {
+ ...options,
+ collapsed: true
+ });
}
- logCollapseExpand(`${collapseTab.id}: dontCollapse = ${dontCollapse}`);
- const manuallyExpanded = collapseTab.$TST.states.has(Constants.kTAB_STATE_SUBTREE_EXPANDED_MANUALLY);
- if (!dontCollapse &&
- !manuallyExpanded &&
- collapseTab.$TST.descendants.every(tab => !tab.$TST.canBecomeSticky))
- collapseExpandSubtree(collapseTab, {
- ...options,
- collapsed: true
- });
+ collapseExpandSubtree(tab, {
+ ...options,
+ collapsed: false
+ });
+ }
+ catch(error) {
+ log(`failed to collapse/expand tree under ${tab.id}: ${String(error)}`, error);
}
-
- collapseExpandSubtree(tab, {
- ...options,
- collapsed: false
- });
win.doingIntelligentlyCollapseExpandCount--;
}
@@ -1345,12 +1353,12 @@ export async function fixupSubtreeCollapsedState(tab, options = {}) {
export async function moveTabSubtreeBefore(tab, nextTab, options = {}) {
if (!tab)
return;
- if (nextTab && nextTab.$TST.isAllPlacedBeforeSelf([tab].concat(tab.$TST.descendants))) {
+ if (nextTab?.$TST.isAllPlacedBeforeSelf([tab].concat(tab.$TST.descendants))) {
log('moveTabSubtreeBefore:no need to move');
return;
}
- log('moveTabSubtreeBefore: ', tab.id, nextTab && nextTab.id);
+ log('moveTabSubtreeBefore: ', tab.id, nextTab?.id);
const win = TabsStore.windows.get(tab.windowId);
win.subTreeMovingCount++;
try {
@@ -1359,8 +1367,8 @@ export async function moveTabSubtreeBefore(tab, nextTab, options = {}) {
throw new Error('the tab was removed before moving of descendants');
await followDescendantsToMovedRoot(tab, options);
}
- catch(e) {
- log(`failed to move subtree: ${String(e)}`);
+ catch(error) {
+ log(`failed to move subtree: ${String(error)}`, error);
}
await wait(0);
win.subTreeMovingCount--;
@@ -1370,8 +1378,8 @@ export async function moveTabSubtreeAfter(tab, previousTab, options = {}) {
if (!tab)
return;
- log('moveTabSubtreeAfter: ', tab.id, previousTab && previousTab.id);
- if (previousTab && previousTab.$TST.isAllPlacedAfterSelf([tab].concat(tab.$TST.descendants))) {
+ log('moveTabSubtreeAfter: ', tab.id, previousTab?.id);
+ if (previousTab?.$TST.isAllPlacedAfterSelf([tab].concat(tab.$TST.descendants))) {
log(' => no need to move');
return;
}
@@ -1384,8 +1392,8 @@ export async function moveTabSubtreeAfter(tab, previousTab, options = {}) {
throw new Error('the tab was removed before moving of descendants');
await followDescendantsToMovedRoot(tab, options);
}
- catch(e) {
- log(`failed to move subtree: ${String(e)}`);
+ catch(error) {
+ log(`failed to move subtree: ${String(error)}`, error);
}
await wait(0);
win.subTreeMovingCount--;
@@ -1399,7 +1407,12 @@ async function followDescendantsToMovedRoot(tab, options = {}) {
const win = TabsStore.windows.get(tab.windowId);
win.subTreeChildrenMovingCount++;
win.subTreeMovingCount++;
- await TabsMove.moveTabsAfter(tab.$TST.descendants, tab, options);
+ try {
+ await TabsMove.moveTabsAfter(tab.$TST.descendants, tab, options);
+ }
+ catch(error) {
+ log(`failed to move descendants of ${tab.id}: ${String(error)}`, error);
+ }
win.subTreeChildrenMovingCount--;
win.subTreeMovingCount--;
}
@@ -1529,8 +1542,7 @@ export async function moveTabs(tabs, options = {}) {
log('moveTabs: all windows and tabs are ready, ', movedTabIds, destinationWindowId);
let toIndex = (tabs.some(tab => tab.pinned) ? Tab.getPinnedTabs(destinationWindowId) : Tab.getAllTabs(destinationWindowId)).length;
log('toIndex = ', toIndex);
- if (options.insertBefore &&
- options.insertBefore.windowId == destinationWindowId) {
+ if (options.insertBefore?.windowId == destinationWindowId) {
try {
toIndex = Tab.get(options.insertBefore.id).index;
}
@@ -1539,8 +1551,7 @@ export async function moveTabs(tabs, options = {}) {
log('options.insertBefore is unavailable');
}
}
- else if (options.insertAfter &&
- options.insertAfter.windowId == destinationWindowId) {
+ else if (options.insertAfter?.windowId == destinationWindowId) {
try {
toIndex = Tab.get(options.insertAfter.id).index + 1;
}
@@ -1886,8 +1897,8 @@ class TabActionForNewPosition {
}
export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
- const isTabCreating = moveInfo && !!moveInfo.isTabCreating;
- const isMovingByShortcut = moveInfo && !!moveInfo.isMovingByShortcut;
+ const isTabCreating = !!moveInfo?.isTabCreating;
+ const isMovingByShortcut = !!moveInfo?.isMovingByShortcut;
if (tab.pinned)
return new TabActionForNewPosition(tab.$TST.parentId ? 'detach' : 'move', {
@@ -1910,18 +1921,48 @@ export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
const prevTab = tree.tabsById[target.previous];
const nextTab = tree.tabsById[target.next];
- log('prevTab: ', dumpTab(prevTab));
- log('nextTab: ', dumpTab(nextTab));
- const prevParent = prevTab && tree.tabsById[prevTab.parent];
- const nextParent = nextTab && tree.tabsById[nextTab.parent];
+ // When multiple tabs are moved at once by outside of TST (e.g. moving of multiselected tabs)
+ // this method may be called multiple times asynchronously before previous operation finishes.
+ // Thus we need to refer the calculated "parent" if it is given.
+ const futurePrevParent = Tab.get(Tab.get(prevTab?.id)?.$TST?.temporaryMetadata.get('goingToBeAttachedTo'));
+ const futureNextParent = Tab.get(Tab.get(nextTab?.id)?.$TST?.temporaryMetadata.get('goingToBeAttachedTo'));
+
+ const prevParent = prevTab && tree.tabsById[prevTab.parent] ||
+ snapshotTab(Tab.get(prevTab?.parent)) || // Given treeForActionDetection may not contain the parent tab, so failsafe
+ snapshotTab(futurePrevParent);
+ const nextParent = nextTab && tree.tabsById[nextTab.parent] ||
+ snapshotTab(Tab.get(nextTab?.parent)) || // Given treeForActionDetection may not contain the parent tab, so failsafe
+ snapshotTab(futureNextParent);
+ if (prevParent)
+ tree.tabsById[prevParent.id] = prevParent;
+ if (nextParent)
+ tree.tabsById[nextParent.id] = nextParent;
+
+ // Given treeForActionDetection may not contain the parent tab, so we fixup the information.
+ if (prevTab &&
+ !prevTab.parent &&
+ prevParent) {
+ prevTab.parent = prevParent.id;
+ prevTab.level = prevParent.level + 1;
+ }
+ if (nextTab &&
+ !nextTab.parent &&
+ nextParent) {
+ nextTab.parent = nextParent.id;
+ nextTab.level = nextParent.level + 1;
+ }
+ log('prevTab: ', dumpTab(prevTab), `parent: ${prevTab?.parent}`);
+ log('nextTab: ', dumpTab(nextTab), `parent: ${nextTab?.parent}`);
const prevLevel = prevTab ? prevTab.level : -1 ;
const nextLevel = nextTab ? nextTab.level : -1 ;
log('prevLevel: '+prevLevel);
log('nextLevel: '+nextLevel);
- const oldParent = tree.tabsById[target.parent];
+ const oldParent = tree.tabsById[target.parent] || snapshotTab(Tab.get(target.parent));
+ if (oldParent)
+ tree.tabsById[oldParent.id] = oldParent;
let newParent = null;
let mustToApply = false;
@@ -1936,7 +1977,7 @@ export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
tab,
isTabCreating,
isMovingByShortcut,
- insertAfter: prevTab && prevTab.id,
+ insertAfter: prevTab?.id,
mustToApply,
});
}
@@ -1948,7 +1989,7 @@ export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
}
else if (oldParent &&
prevTab &&
- oldParent == prevTab) {
+ oldParent?.id == prevTab?.id) {
log('=> no need to fix case');
newParent = oldParent;
}
@@ -1961,7 +2002,7 @@ export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
log('=> moved to last position');
let ancestor = oldParent;
while (ancestor) {
- if (ancestor == prevParent) {
+ if (ancestor.id == prevParent?.id) {
log(' => moving in related tree: keep it attached in existing tree');
newParent = prevParent;
break;
@@ -1971,15 +2012,15 @@ export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
if (!newParent) {
log(' => moving from other tree: keep it orphaned');
}
- mustToApply = !!oldParent && newParent != oldParent;
+ mustToApply = !!oldParent && newParent?.id != oldParent.id;
}
- else if (prevParent == nextParent) {
+ else if (prevParent?.id == nextParent?.id) {
log('=> moved into existing tree');
newParent = prevParent;
- mustToApply = !oldParent || newParent != oldParent;
+ mustToApply = !oldParent || newParent?.id != oldParent.id;
}
else if (prevLevel > nextLevel &&
- nextTab.parent != tab.id) {
+ nextTab?.parent != tab.id) {
log('=> moved to end of existing tree');
if (!target.active &&
target.children.length == 0 &&
@@ -1992,35 +2033,35 @@ export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
const realDelta = Math.abs(toIndex - fromIndex);
newParent = realDelta < 2 ? prevParent : (oldParent || nextParent) ;
}
- while (newParent && newParent.collapsed) {
+ while (newParent?.collapsed) {
log('=> the tree is collapsed, up to parent tree')
newParent = tree.tabsById[newParent.parent];
}
- mustToApply = !!oldParent && newParent != oldParent;
+ mustToApply = !!oldParent && newParent?.id != oldParent.id;
}
else if (prevLevel < nextLevel &&
- nextTab.parent == prevTab.id) {
+ nextTab?.parent == prevTab?.id) {
log('=> moved to first child position of existing tree');
newParent = prevTab || oldParent || nextParent;
- mustToApply = !!oldParent && newParent != oldParent;
+ mustToApply = !!oldParent && newParent?.id != oldParent.id;
}
log('calculated parent: ', {
- old: oldParent && oldParent.id,
- new: newParent && newParent.id
+ old: oldParent?.id,
+ new: newParent?.id
});
if (newParent) {
let ancestor = newParent;
while (ancestor) {
- if (ancestor == target) {
+ if (ancestor.id == target.id) {
if (moveInfo.toIndex - moveInfo.fromIndex == 1) {
log('=> maybe move-down by keyboard shortcut or something.');
let nearestForeigner = tab.$TST.nearestFollowingForeignerTab;
if (nearestForeigner &&
nearestForeigner == tab)
nearestForeigner = nearestForeigner.$TST.nextTab;
- log('nearest foreigner tab: ', nearestForeigner && nearestForeigner.id);
+ log('nearest foreigner tab: ', nearestForeigner?.id);
if (nearestForeigner) {
if (nearestForeigner.$TST.hasChild)
return new TabActionForNewPosition('attach', {
@@ -2054,8 +2095,8 @@ export function detectTabActionFromNewPosition(tab, moveInfo = {}) {
isTabCreating,
isMovingByShortcut,
parent: newParent.id,
- insertBefore: nextTab && nextTab.id,
- insertAfter: prevTab && prevTab.id,
+ insertBefore: nextTab?.id,
+ insertAfter: prevTab?.id,
mustToApply,
});
}
@@ -2085,11 +2126,11 @@ export function snapshotForActionDetection(targetTab) {
const prevTab = targetTab.$TST.nearestCompletelyOpenedNormalPrecedingTab;
const nextTab = targetTab.$TST.nearestCompletelyOpenedNormalFollowingTab;
const tabs = Array.from(new Set([
- ...(prevTab && prevTab.$TST.ancestors || []),
+ ...(prevTab?.$TST?.ancestors || []),
prevTab,
targetTab,
nextTab,
- targetTab.$TST.parent
+ targetTab.$TST.parent,
]))
.filter(TabsStore.ensureLivingTab)
.sort((a, b) => a.index - b.index);
@@ -2103,37 +2144,40 @@ function snapshotTree(targetTab, tabs) {
function snapshotChild(tab) {
if (!TabsStore.ensureLivingTab(tab) || tab.pinned)
return null;
- return snapshotById[tab.id] = {
- id: tab.id,
- url: tab.url,
- cookieStoreId: tab.cookieStoreId,
- active: tab.active,
- children: tab.$TST.children.map(child => child.id),
- collapsed: tab.$TST.subtreeCollapsed,
- pinned: tab.pinned,
- level: tab.$TST.ancestorIds.length, // parseInt(tab.$TST.getAttribute(Constants.kLEVEL) || 0), // we need to use the number of real ancestors instead of a cached "level", because it will be updated with delay
- trackedAt: tab.$TST.trackedAt,
- mayBeReplacedWithContainer: tab.$TST.mayBeReplacedWithContainer
- };
+ return snapshotById[tab.id] = snapshotTab(tab);
}
const snapshotArray = allTabs.map(tab => snapshotChild(tab));
for (const tab of allTabs) {
const item = snapshotById[tab.id];
if (!item)
continue;
- const parent = tab.$TST.parent;
- item.parent = parent && parent.id;
- const next = tab.$TST.nearestCompletelyOpenedNormalFollowingTab;
- item.next = next && next.id;
- const previous = tab.$TST.nearestCompletelyOpenedNormalPrecedingTab;
- item.previous = previous && previous.id;
+ item.parent = tab.$TST.parent?.id;
+ item.next = tab.$TST.nearestCompletelyOpenedNormalFollowingTab?.id;
+ item.previous = tab.$TST.nearestCompletelyOpenedNormalPrecedingTab?.id;
}
const activeTab = Tab.getActiveTab(targetTab.windowId);
return {
target: snapshotById[targetTab.id],
active: activeTab && snapshotById[activeTab.id],
tabs: snapshotArray,
- tabsById: snapshotById
+ tabsById: snapshotById,
+ };
+}
+
+function snapshotTab(tab) {
+ if (!tab)
+ return null;
+ return {
+ id: tab.id,
+ url: tab.url,
+ cookieStoreId: tab.cookieStoreId,
+ active: tab.active,
+ children: tab.$TST.children.map(child => child.id),
+ collapsed: tab.$TST.subtreeCollapsed,
+ pinned: tab.pinned,
+ level: tab.$TST.level, // parseInt(tab.$TST.getAttribute(Constants.kLEVEL) || 0), // we need to use the number of real ancestors instead of a cached "level", because it will be updated with delay
+ trackedAt: tab.$TST.trackedAt,
+ mayBeReplacedWithContainer: tab.$TST.mayBeReplacedWithContainer,
};
}
diff --git a/waterfox/browser/components/sidebar/common/Tab.js b/waterfox/browser/components/sidebar/common/Tab.js
index 4c9a484c9c4f2..84c3bfa9d8800 100644
--- a/waterfox/browser/components/sidebar/common/Tab.js
+++ b/waterfox/browser/components/sidebar/common/Tab.js
@@ -389,6 +389,10 @@ export default class Tab {
return this.states.has(Constants.kTAB_STATE_STICKY);
}
+ get stuck() {
+ return this.element?.parentNode?.classList.contains('sticky-tabs-container');
+ }
+
get isNewTabCommandTab() {
if (!this.tab ||
!configs.guessNewOrphanTabAsOpenedByNewTabCommand)
@@ -444,6 +448,13 @@ export default class Tab {
return (new URL(this.tab.url)).searchParams.get('temporaryAggressive') == 'true';
}
+ get replacedParentGroupTabCount() {
+ if (!this.tab || !this.isGroupTab)
+ return 0;
+ const count = parseInt((new URL(this.tab.url)).searchParams.get('replacedParentCount'));
+ return isNaN(count) ? 0 : count;
+ }
+
// Firefox Multi-Account Containers
// https://addons.mozilla.org/firefox/addon/multi-account-containers/
// Temporary Containers
@@ -962,6 +973,10 @@ export default class Tab {
return ancestors;
}
+ get level() {
+ return this.ancestorIds.length;
+ }
+
invalidateCachedAncestors() {
this.cachedAncestorIds = null;
for (const child of this.children) {
@@ -1176,13 +1191,16 @@ export default class Tab {
get needToBeGroupedSiblings() {
if (!this.tab)
return [];
+ const openerTabUniqueId = this.getAttribute(Constants.kPERSISTENT_ORIGINAL_OPENER_TAB_ID);
+ if (!openerTabUniqueId)
+ return [];
return TabsStore.queryAll({
windowId: this.tab.windowId,
tabs: TabsStore.toBeGroupedTabsInWindow.get(this.tab.windowId),
normal: true,
'!id': this.id,
attributes: [
- Constants.kPERSISTENT_ORIGINAL_OPENER_TAB_ID, this.getAttribute(Constants.kPERSISTENT_ORIGINAL_OPENER_TAB_ID),
+ Constants.kPERSISTENT_ORIGINAL_OPENER_TAB_ID, openerTabUniqueId,
Constants.kPERSISTENT_ALREADY_GROUPED_FOR_PINNED_OPENER, ''
],
ordered: true
@@ -1908,30 +1926,27 @@ export default class Tab {
if (!this.tab) // already closed tab
return;
log(`memorizeNeighbors ${this.tab.id} as ${hint}`);
-
- const previousTab = this.unsafePreviousTab;
- this.lastPreviousTabId = previousTab && previousTab.id;
-
- const nextTab = this.unsafeNextTab;
- this.lastNextTabId = nextTab && nextTab.id;
+ this.lastPreviousTabId = this.unsafePreviousTab?.id;
+ this.lastNextTabId = this.unsafeNextTab?.id;
}
- get isSubstantiallyMoved() {
+ // https://github.com/piroor/treestyletab/issues/2309#issuecomment-518583824
+ get movedInBulk() {
const previousTab = this.unsafePreviousTab;
if (this.lastPreviousTabId &&
- this.lastPreviousTabId != (previousTab && previousTab.id)) {
- log(`isSubstantiallyMoved lastPreviousTabId=${this.lastNextTabId}, previousTab=${previousTab && previousTab.id}`);
- return true;
+ this.lastPreviousTabId != previousTab?.id) {
+ log(`not bulkMoved lastPreviousTabId=${this.lastNextTabId}, previousTab=${previousTab?.id}`);
+ return false;
}
const nextTab = this.unsafeNextTab;
if (this.lastNextTabId &&
- this.lastNextTabId != (nextTab && nextTab.id)) {
- log(`isSubstantiallyMoved lastNextTabId=${this.lastNextTabId}, nextTab=${nextTab && nextTab.id}`);
- return true;
+ this.lastNextTabId != nextTab?.id) {
+ log(`not bulkMoved lastNextTabId=${this.lastNextTabId}, nextTab=${nextTab?.id}`);
+ return false;
}
- return false;
+ return true;
}
get sanitized() {
@@ -2040,6 +2055,8 @@ export default class Tab {
ancestorTabIds: this.tab.$TST.ancestorIds,
bundledTabId: this.tab.$TST.bundledTabId,
};
+ if (this.stuck)
+ exportedTab.states.push(Constants.kTAB_STATE_STUCK);
if (configs.cacheAPITreeItems && light)
this.$exportedForAPI = exportedTab;
}
@@ -2893,6 +2910,24 @@ Tab.hasLoadingTab = windowId => {
});
};
+Tab.hasDuplicatedTabs = (windowId, options = {}) => {
+ const tabs = TabsStore.queryAll({
+ windowId,
+ tabs: TabsStore.getTabsMap(TabsStore.livingTabsInWindow, windowId),
+ living: true,
+ ...options,
+ iterator: true
+ });
+ const tabKeys = new Set();
+ for (const tab of tabs) {
+ const key = `${tab.cookieStoreId}\n${tab.url}`;
+ if (tabKeys.has(key))
+ return true;
+ tabKeys.add(key);
+ }
+ return false;
+};
+
Tab.hasMultipleTabs = (windowId, options = {}) => {
const tabs = TabsStore.queryAll({
windowId,
diff --git a/waterfox/browser/components/sidebar/common/Window.js b/waterfox/browser/components/sidebar/common/Window.js
index a32e2d6325741..a30f4bc86f0b6 100644
--- a/waterfox/browser/components/sidebar/common/Window.js
+++ b/waterfox/browser/components/sidebar/common/Window.js
@@ -35,8 +35,8 @@ export default class Window {
this.containerClassList = null;
this.pinnedContainerElement = null;
- this.internalMovingTabs = new Set();
- this.alreadyMovedTabs = new Set();
+ this.internalMovingTabs = new Map();
+ this.alreadyMovedTabs = new Map();
this.internalClosingTabs = new Set();
this.keepDescendantsTabs = new Set();
this.highlightingTabs = new Set();
@@ -45,11 +45,12 @@ export default class Window {
this.subTreeMovingCount =
this.subTreeChildrenMovingCount =
this.doingIntelligentlyCollapseExpandCount =
- this.internalFocusCount =
- this.internalSilentlyFocusCount =
- this.internalByMouseFocusCount =
this.duplicatingTabsCount = 0;
+ this.internallyFocusingTabs = new Set();
+ this.internallyFocusingByMouseTabs = new Set();
+ this.internallyFocusingSilentlyTabs = new Set();
+
this.preventToDetectTabBunchesUntil = Date.now() + configs.tabBunchesDetectionDelayOnNewWindow;
this.openingTabs = new Set();
diff --git a/waterfox/browser/components/sidebar/common/api-tabs.js b/waterfox/browser/components/sidebar/common/api-tabs.js
index 9fe3d36c9c4ec..ea19a513b8a84 100644
--- a/waterfox/browser/components/sidebar/common/api-tabs.js
+++ b/waterfox/browser/components/sidebar/common/api-tabs.js
@@ -29,55 +29,6 @@ export async function getIndexes(...queriedTabIds) {
return indexes.map(tab => tab ? tab.index : -1);
}
-export async function blur(tab, unactivatableTabs = []) {
- const unactivatableIndices = Array.from(new Set([tab, ...unactivatableTabs].map(tab => tab.index)));
- const minUnactivatableIndex = Math.min.apply(null, unactivatableIndices);
- const maxUnactivatableIndex = Math.max.apply(null, unactivatableIndices);
- const unactivatableTabById = new Map();
- for (const tab of unactivatableTabs) {
- unactivatableTabById.set(tab.id, tab);
- }
-
- const allTabs = await browser.tabs.query({ windowId: tab.windowId });
- const unactivatableTabIds = new Set([tab.id, ...unactivatableTabs.map(tab => tab.id)]);
- if (allTabs.length == unactivatableTabIds.size)
- return; // there is no other focusible tab!
-
- const restTabs = allTabs.filter(tab => unactivatableTabIds.has(tab.id));
- const middleTabs = restTabs.filter(tab => tab.index > minUnactivatableIndex || tab.index < maxUnactivatableIndex);
- const previousTab = minUnactivatableIndex > 0 && allTabs[minUnactivatableIndex - 1];
- const nextTab = maxUnactivatableIndex < allTabs.length - 1 && allTabs[maxUnactivatableIndex + 1];
- const allTabById = new Map();
- for (const tab of allTabs) {
- allTabById.set(tab.id, tab);
- }
-
- const scannedTabIds = new Set();
- let successorTab = tab;
- do {
- if (scannedTabIds.has(successorTab.id))
- break; // prevent infinite loop!
- scannedTabIds.add(successorTab.id);
- let nextSuccessorTab = unactivatableTabById.get(successorTab.successorTabId);
- if (nextSuccessorTab) {
- successorTab = nextSuccessorTab;
- continue;
- }
- nextSuccessorTab = allTabById.get(successorTab.successorTabId) ||
- nextTab ||
- (middleTabs.length > 0 && middleTabs[0]) ||
- previousTab ||
- restTabs[0];
- if (unactivatableTabById.has(nextSuccessorTab.id)) {
- successorTab = nextSuccessorTab;
- continue;
- }
- await browser.tabs.update(nextSuccessorTab.id, { active: true });
- break;
- }
- while (successorTab);
-}
-
export function isMissingTabError(error) {
return (
error &&
diff --git a/waterfox/browser/components/sidebar/common/bookmark.js b/waterfox/browser/components/sidebar/common/bookmark.js
index 75f693ff5adf7..836b0adc6d574 100644
--- a/waterfox/browser/components/sidebar/common/bookmark.js
+++ b/waterfox/browser/components/sidebar/common/bookmark.js
@@ -20,6 +20,7 @@ import {
isLinux,
} from './common.js';
import * as ApiTabs from './api-tabs.js';
+import * as TreeBehavior from './tree-behavior.js';
import * as Constants from './constants.js';
import * as ContextualIdentities from './contextual-identities.js';
import * as Dialog from './dialog.js';
@@ -637,6 +638,11 @@ reserveToGroupCreatedBookmarks.retryCount = 0;
async function tryGroupCreatedBookmarks() {
log('tryGroupCreatedBookmarks ', mCreatedBookmarks);
+ if (!configs.autoCreateFolderForBookmarksFromTree) {
+ log(' => autoCreateFolderForBookmarksFromTree is false');
+ return;
+ }
+
const lastDraggedTabs = configs.lastDraggedTabs;
if (lastDraggedTabs &&
lastDraggedTabs.tabIds.length > mCreatedBookmarks.length) {
@@ -677,37 +683,69 @@ async function tryGroupCreatedBookmarks() {
for (const bookmark of bookmarks) {
parentIds.add(bookmark.parentId);
}
+ log('parentIds: ', parentIds);
if (parentIds.size > 1) {
log(' => ignore bookmarks created under multiple folders');
return;
}
}
+ const tabs = lastDraggedTabs ?
+ lastDraggedTabs.tabIds.map(id => Tab.get(id)) :
+ (await Promise.all(bookmarks.map(async bookmark => {
+ const tabs = await browser.tabs.query({ url: bookmark.url });
+ if (tabs.length == 0)
+ return null;
+ const tab = tabs.find(tab => tab.highlighted) || tabs[0];
+ return Tab.get(tab);
+ }))).filter(tab => !!tab);
+ log('tabs: ', tabs);
+ if (tabs.length != bookmarks.length) {
+ log(' => ignore bookmarks created from non-tab sources');
+ return;
+ }
+
+ const treeStructure = TreeBehavior.getTreeStructureFromTabs(tabs);
+ log('treeStructure: ', treeStructure);
+ const topLevelTabsCount = treeStructure.filter(item => item.parent < 0).length;
+ if (topLevelTabsCount == treeStructure.length) {
+ log(' => no need to group bookmarks from dragged flat tabs');
+ return;
+ }
+
+ let titles = getTitlesWithTreeStructure(tabs);
+ if (tabs[0].$TST.isGroupTab &&
+ titles.filter(title => !/^>/.test(title)).length == 1) {
+ log('delete needless bookmark for a group tab');
+ browser.bookmarks.remove(bookmarks[0].id);
+ tabs.shift();
+ bookmarks.shift();
+ titles = getTitlesWithTreeStructure(tabs);
+ }
+ log('titles: ', titles);
+
+ log('save tree structure to bookmarks');
+ for (let i = 0, maxi = bookmarks.length; i < maxi; i++) {
+ const title = titles[i];
+ if (title == tabs[i].title)
+ continue;
+ browser.bookmarks.update(bookmarks[i].id, { title });
+ }
+
+ log('ready to group bookmarks under a folder');
+
const parentId = bookmarks[0].parentId;
{
// Do nothing if all bookmarks are created under a new
// blank folder.
const allChildren = await browser.bookmarks.getChildren(parentId);
+ log('allChildren.length vs bookmarks.length: ', allChildren.length, bookmarks.length);
if (allChildren.length == bookmarks.length) {
- log(' => ignore bookmarks created under a new blank folder');
+ log(' => no need to create folder for bookmarks under a new blank folder');
return;
}
}
- const possibleSourceTabs = (await Promise.all(bookmarks.map(async bookmark => {
- const tabs = await browser.tabs.query({ url: bookmark.url });
- if (tabs.length == 0)
- return null;
- return tabs[0];
- }))).filter(tab => !!tab);
- console.log('possibleSourceTabs ', possibleSourceTabs);
- if (possibleSourceTabs.length != bookmarks.length) {
- log(' => ignore bookmarks created from non-tab sources');
- return;
- }
-
- log('ready to group bookmarks under a folder');
-
log('create a folder for grouping');
mCreatingCount++;
const folder = await browser.bookmarks.create({
@@ -728,28 +766,6 @@ async function tryGroupCreatedBookmarks() {
index: movedCount++
});
}
-
- if (!lastDraggedTabs)
- return;
-
- const tabs = lastDraggedTabs.tabIds.map(id => Tab.get(id));
- let titles = getTitlesWithTreeStructure(tabs);
- if (tabs[0].$TST.isGroupTab &&
- titles.filter(title => !/^>/.test(title)).length == 1) {
- log('delete needless bookmark for a group tab');
- browser.bookmarks.remove(bookmarks[0].id);
- tabs.shift();
- bookmarks.shift();
- titles = getTitlesWithTreeStructure(tabs);
- }
-
- log('save tree structure to bookmarks');
- for (let i = 0, maxi = bookmarks.length; i < maxi; i++) {
- const title = titles[i];
- if (title == tabs[i].title)
- continue;
- browser.bookmarks.update(bookmarks[i].id, { title });
- }
}
if (Constants.IS_BACKGROUND &&
diff --git a/waterfox/browser/components/sidebar/common/cache-storage.js b/waterfox/browser/components/sidebar/common/cache-storage.js
index 40227939b7605..e70805047ba14 100644
--- a/waterfox/browser/components/sidebar/common/cache-storage.js
+++ b/waterfox/browser/components/sidebar/common/cache-storage.js
@@ -6,10 +6,14 @@
'use strict';
import * as UniqueId from '/common/unique-id.js';
+import {
+ asyncRunWithTimeout,
+} from '/common/common.js';
const DB_NAME = 'PermanentStorage';
const DB_VERSION = 3;
const EXPIRATION_TIME_IN_MSEC = 7 * 24 * 60 * 60 * 1000; // 7 days
+const TIMEOUT_IN_MSEC = 1000 * 5; // 5 sec
export const BACKGROUND = 'backgroundCaches';
const SIDEBAR = 'sidebarCaches'; // obsolete, but left here to delete old storage
@@ -70,28 +74,42 @@ export async function setValue({ windowId, key, value } = {}) {
const store = BACKGROUND;
const cacheKey = `${windowUniqueId}-${key}`;
- const timestamp = Date.now();
- try {
- const transaction = db.transaction([store], 'readwrite');
- const cacheStore = transaction.objectStore(store);
-
- cacheStore.put({
- key: cacheKey,
- windowId: windowUniqueId,
- value,
- timestamp,
- });
-
- transaction.oncomplete = () => {
- //db.close();
- windowId = undefined;
- key = undefined;
- value = undefined;
- };
- }
- catch(error) {
- console.error(`Failed to store cache ${cacheKey} in the store ${store}`, error);
- }
+ asyncRunWithTimeout({
+ task: () => new Promise((resolve, reject) => {
+ const timestamp = Date.now();
+ try {
+ const transaction = db.transaction([store], 'readwrite');
+ const cacheStore = transaction.objectStore(store);
+ const cacheRequest = cacheStore.put({
+ key: cacheKey,
+ windowId: windowUniqueId,
+ value,
+ timestamp,
+ });
+
+ transaction.oncomplete = () => {
+ //db.close();
+ windowId = undefined;
+ key = undefined;
+ value = undefined;
+ resolve();
+ };
+
+ cacheRequest.onerror = event => {
+ console.error(`Failed to store cache ${cacheKey} in the store ${store}`, event);
+ reject(event);
+ };
+ }
+ catch(error) {
+ console.error(`Failed to store cache ${cacheKey} in the store ${store}`, error);
+ reject(error);
+ }
+ }),
+ timeout: TIMEOUT_IN_MSEC,
+ onTimedOut() {
+ throw new Error(`CacheStorage.setValue for {windowId}/key timed out`);
+ },
+ });
}
export async function deleteValue({ windowId, key } = {}) {
@@ -107,22 +125,47 @@ export async function deleteValue({ windowId, key } = {}) {
const store = BACKGROUND;
const cacheKey = `${windowUniqueId}-${key}`;
- try {
- const transaction = db.transaction([store], 'readwrite');
- const cacheStore = transaction.objectStore(store);
- cacheStore.delete(cacheKey);
- transaction.oncomplete = () => {
- //db.close();
- windowId = undefined;
- key = undefined;
- };
- }
- catch(error) {
- console.error(`Failed to delete cache ${cacheKey} in the store ${store}`, error);
- }
+ asyncRunWithTimeout({
+ task: () => new Promise((resolve, reject) => {
+ try {
+ const transaction = db.transaction([store], 'readwrite');
+ const cacheStore = transaction.objectStore(store);
+ const cacheRequest = cacheStore.delete(cacheKey);
+
+ transaction.oncomplete = () => {
+ //db.close();
+ windowId = undefined;
+ key = undefined;
+ resolve();
+ };
+
+ cacheRequest.onerror = event => {
+ console.error(`Failed to delete cache ${cacheKey} in the store ${store}`, event);
+ reject(event);
+ };
+ }
+ catch(error) {
+ console.error(`Failed to delete cache ${cacheKey} in the store ${store}`, error);
+ reject(error);
+ }
+ }),
+ timeout: TIMEOUT_IN_MSEC,
+ onTimedOut() {
+ throw new Error(`CacheStorage.deleteValue for {windowId}/key timed out`);
+ },
+ });
}
export async function getValue({ windowId, key } = {}) {
+ return asyncRunWithTimeout({
+ task: () => getValueInternal({ windowId, key }),
+ timeout: TIMEOUT_IN_MSEC,
+ onTimedOut() {
+ throw new Error(`CacheStorage.getValue for {windowId}/${key} timed out`);
+ },
+ });
+}
+async function getValueInternal({ windowId, key } = {}) {
return new Promise(async (resolve, _reject) => {
const [db, windowUniqueId] = await Promise.all([
openDB(),
@@ -163,6 +206,11 @@ export async function getValue({ windowId, key } = {}) {
cache.value = undefined;
};
+ cacheRequest.onerror = event => {
+ console.error('Failed to get from cache:', event);
+ resolve(null);
+ };
+
transaction.oncomplete = () => {
//db.close();
windowId = undefined;
@@ -177,6 +225,15 @@ export async function getValue({ windowId, key } = {}) {
}
export async function clearForWindow(windowId) {
+ return asyncRunWithTimeout({
+ task: () => clearForWindowInternal(windowId),
+ timeout: TIMEOUT_IN_MSEC,
+ onTimedOut() {
+ throw new Error(`CacheStorage.clearForWindow for {windowId} timed out`);
+ },
+ });
+}
+async function clearForWindowInternal(windowId) {
reserveToExpireOldEntries();
return new Promise(async (resolve, reject) => {
const [db, windowUniqueId] = await Promise.all([
diff --git a/waterfox/browser/components/sidebar/common/common.js b/waterfox/browser/components/sidebar/common/common.js
index 12fd4e2024c1e..cf2b929546d2e 100644
--- a/waterfox/browser/components/sidebar/common/common.js
+++ b/waterfox/browser/components/sidebar/common/common.js
@@ -13,7 +13,6 @@ import * as Constants from './constants.js';
const WATERFOX_SPECIFIC_VALUES = {
hideHorizontalTabsWhileActive: true,
showTabPreview: true,
- hoverTabPreviewDelayMs: 500,
sidebarPosition: Constants.kTABBAR_POSITION_LEFT,
suppressGapFromShownOrHiddenToolbarOnlyOnMouseOperation: false,
@@ -303,6 +302,8 @@ export const configs = new Configs({
autoExpandOnLongHoverDelay: 500,
autoExpandOnLongHoverRestoreIniitalState: true,
+ autoCreateFolderForBookmarksFromTree: true,
+
accelKey: '',
skipCollapsedTabsForTabSwitchingShortcuts: false,
@@ -341,11 +342,13 @@ export const configs = new Configs({
groupTabTemporaryStateForChildrenOfPinned: Constants.kGROUP_TAB_TEMPORARY_STATE_PASSIVE,
groupTabTemporaryStateForChildrenOfFirefoxView: Constants.kGROUP_TAB_TEMPORARY_STATE_PASSIVE,
groupTabTemporaryStateForOrphanedTabs: Constants.kGROUP_TAB_TEMPORARY_STATE_AGGRESSIVE,
+ groupTabTemporaryStateForAPI: Constants.kGROUP_TAB_TEMPORARY_STATE_NOTHING,
renderTreeInGroupTabs: true,
warnOnAutoGroupNewTabs: true,
warnOnAutoGroupNewTabsWithListing: true,
warnOnAutoGroupNewTabsWithListingMaxRows: 5,
showAutoGroupOptionHint: true,
+ showAutoGroupOptionHintWithOpener: true,
// behavior around newly opened tabs
@@ -392,11 +395,13 @@ export const configs = new Configs({
moveParentBehavior_outsideSidebar_expanded: Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_FIRST_CHILD,
moveParentBehavior_noSidebar_collapsed: Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_FIRST_CHILD,
moveParentBehavior_noSidebar_expanded: Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_FIRST_CHILD,
+ closeParentBehavior_replaceWithGroup_thresholdToPrevent: 1, // negative value means "never prevent"
moveTabsToBottomWhenDetachedFromClosedParent: false,
promoteAllChildrenWhenClosedParentIsLastChild: true,
successorTabControlLevel: Constants.kSUCCESSOR_TAB_CONTROL_IN_TREE,
simulateSelectOwnerOnClose: true,
simulateLockTabSizing: true,
+ deferScrollingToOutOfViewportSuccessor: true,
simulateTabsLoadInBackgroundInverted: false,
supportTabsMultiselect: typeof browser.menus.overrideContext == 'function',
warnOnCloseTabs: true,
@@ -847,6 +852,20 @@ export function nextFrame() {
});
}
+export async function asyncRunWithTimeout({ task, timeout, onTimedOut }) {
+ let succeeded = false;
+ return Promise.race([
+ task().then(result => {
+ succeeded = true;
+ return result;
+ }),
+ wait(timeout).then(() => {
+ if (!succeeded)
+ return onTimedOut();
+ }),
+ ]);
+}
+
const mNotificationTasks = new Map();
@@ -1067,7 +1086,7 @@ export function watchOverflowStateChange({ target, moreResizeTargets, onOverflow
};
let resizeObserver/*, mutationObserver*/;
- if (useLegacyOverflowEvents) {
+ if (!useLegacyOverflowEvents) {
const resizeTargets = new Set([target, ...(moreResizeTargets || [])]);
resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
diff --git a/waterfox/browser/components/sidebar/common/constants.js b/waterfox/browser/components/sidebar/common/constants.js
index 8a728934f27ee..65f14d6b0ee92 100644
--- a/waterfox/browser/components/sidebar/common/constants.js
+++ b/waterfox/browser/components/sidebar/common/constants.js
@@ -217,6 +217,7 @@ export const kTAB_STATE_GROUP_TAB = 'group-tab';
export const kTAB_STATE_NEW_TAB_COMMAND_TAB = 'newtab-command-tab';
export const kTAB_STATE_OPENED_FOR_SAME_WEBSITE = 'opened-for-same-website';
export const kTAB_STATE_STICKY = 'sticky';
+export const kTAB_STATE_STUCK = 'stuck'; // virtual state
export const kTAB_INTERNAL_STATES = new Set([ // TST specific states
'tab',
kTAB_STATE_LAST_ROW,
@@ -239,6 +240,7 @@ export const kTAB_INTERNAL_STATES = new Set([ // TST specific states
kTAB_STATE_FROM_FIREFOX_VIEW,
kTAB_STATE_OPENED_FOR_SAME_WEBSITE,
kTAB_STATE_STICKY,
+ kTAB_STATE_STUCK,
]);
export const kTAB_TEMPORARY_STATES = new Set([ // states not trigger updating of cache
kTAB_STATE_CREATING,
@@ -269,6 +271,7 @@ export const kTAB_SAFE_STATES = new Set([ // exportable via API
kTAB_STATE_FROM_FIREFOX_VIEW,
kTAB_STATE_OPENED_FOR_SAME_WEBSITE,
kTAB_STATE_STICKY,
+ kTAB_STATE_STUCK,
]);
export const kTAB_SAFE_STATES_ARRAY = Array.from(kTAB_SAFE_STATES);
diff --git a/waterfox/browser/components/sidebar/common/permissions.js b/waterfox/browser/components/sidebar/common/permissions.js
index 5df2a8a294972..c7f2396e3b8f8 100644
--- a/waterfox/browser/components/sidebar/common/permissions.js
+++ b/waterfox/browser/components/sidebar/common/permissions.js
@@ -71,9 +71,10 @@ browser.runtime.onMessage.addListener((message, _sender) => {
for (const request of requests) {
const { onChanged, checkbox } = destroyRequest(request);
- if (onChanged)
- onChanged(true);
- checkbox.checked = true;
+ const checked =onChanged ?
+ onChanged(true) :
+ undefined;
+ checkbox.checked = checked !== undefined ? !!checked : true;
}
});
@@ -114,7 +115,10 @@ export function bindToCheckbox(permissions, checkbox, options = {}) {
isGranted(permissions)
.then(granted => {
- checkbox.checked = granted;
+ const checked = options.onInitialized ?
+ options.onInitialized(granted) :
+ undefined;
+ checkbox.checked = checked !== undefined ? !!checked : granted;
})
.catch(_error => {
checkbox.setAttribute('readonly', true);
@@ -168,11 +172,12 @@ export function bindToCheckbox(permissions, checkbox, options = {}) {
return;
if (granted) {
+ const checked = options.onChanged ?
+ options.onChanged(true) :
+ undefined;
for (const checkbox of checkboxes) {
- checkbox.checked = true;
+ checkbox.checked = checked !== undefined ? !!checked : true;
}
- if (options.onChanged)
- options.onChanged(true);
browser.runtime.sendMessage({
type: Constants.kCOMMAND_NOTIFY_PERMISSIONS_GRANTED,
permissions
diff --git a/waterfox/browser/components/sidebar/common/retrieve-url.js b/waterfox/browser/components/sidebar/common/retrieve-url.js
index 6c8c13942336b..a73c730f3b8af 100644
--- a/waterfox/browser/components/sidebar/common/retrieve-url.js
+++ b/waterfox/browser/components/sidebar/common/retrieve-url.js
@@ -14,6 +14,7 @@ function log(...args) {
internalLogger('common/retrieve-url', ...args);
}
+export const kTYPE_PLAIN_TEXT = 'text/plain';
export const kTYPE_X_MOZ_URL = 'text/x-moz-url';
export const kTYPE_URI_LIST = 'text/uri-list';
export const kTYPE_MOZ_TEXT_INTERNAL = 'text/x-moz-text-internal';
@@ -23,7 +24,7 @@ const ACCEPTABLE_DATA_TYPES = [
kTYPE_URI_LIST,
kTYPE_X_MOZ_URL,
kTYPE_MOZ_TEXT_INTERNAL,
- 'text/plain'
+ kTYPE_PLAIN_TEXT,
];
let mFileURLResolver = null;
@@ -102,7 +103,7 @@ export async function fromClipboard({ selection } = {}) {
if (await mSelectionClipboardProvider.isAvailable()) {
const maybeUrlString = await mSelectionClipboardProvider.getTextData();
if (maybeUrlString)
- urls.push(...fromData(maybeUrlString, 'text/plain'));
+ urls.push(...fromData(maybeUrlString, kTYPE_PLAIN_TEXT));
return sanitizeURLs(urls);
}
}
@@ -172,7 +173,7 @@ function fromData(data, type) {
.trim()
.split('\n');
- case 'text/plain':
+ case kTYPE_PLAIN_TEXT:
return data
.replace(/\r/g, '\n')
.replace(/\n\n+/g, '\n')
diff --git a/waterfox/browser/components/sidebar/common/tabs-internal-operation.js b/waterfox/browser/components/sidebar/common/tabs-internal-operation.js
index 3d47da4683a1f..44477b9e15695 100644
--- a/waterfox/browser/components/sidebar/common/tabs-internal-operation.js
+++ b/waterfox/browser/components/sidebar/common/tabs-internal-operation.js
@@ -7,6 +7,8 @@
// internal operations means operations bypassing WebExtensions' tabs APIs.
+import EventListenerManager from '/extlib/EventListenerManager.js';
+
import {
log as internalLogger,
dumpTab,
@@ -26,6 +28,8 @@ function log(...args) {
internalLogger('common/tabs-internal-operation', ...args);
}
+export const onBeforeTabsRemove = new EventListenerManager();
+
export async function activateTab(tab, { byMouseOperation, keepMultiselection, silently } = {}) {
if (!Constants.IS_BACKGROUND)
throw new Error('Error: TabsInternalOperation.activateTab is available only on the background page, use a `kCOMMAND_ACTIVATE_TAB` message instead.');
@@ -35,17 +39,15 @@ export async function activateTab(tab, { byMouseOperation, keepMultiselection, s
return;
log('activateTab: ', dumpTab(tab));
const win = TabsStore.windows.get(tab.windowId);
- win.internalFocusCount++;
+ win.internallyFocusingTabs.add(tab.id);
if (byMouseOperation)
- win.internalByMouseFocusCount++;
+ win.internallyFocusingByMouseTabs.add(tab.id);
if (silently)
- win.internalSilentlyFocusCount++;
+ win.internallyFocusingSilentlyTabs.add(tab.id);
const onError = (e) => {
- win.internalFocusCount--;
- if (byMouseOperation)
- win.internalByMouseFocusCount--;
- if (silently)
- win.internalSilentlyFocusCount--;
+ win.internallyFocusingTabs.delete(tab.id);
+ win.internallyFocusingByMouseTabs.delete(tab.id);
+ win.internallyFocusingSilentlyTabs.delete(tab.id);
ApiTabs.handleMissingTabError(e);
};
if (configs.supportTabsMultiselect &&
@@ -77,30 +79,52 @@ export async function blurTab(bluredTabs, { windowId, silently } = {}) {
const bluredTabIds = new Set(Array.from(bluredTabs || [], tab => tab.id || tab));
- let bluredTabsFound = false;
- let nextActiveTab = null;
- for (const tab of Tab.getVisibleTabs(windowId || bluredTabs[0].windowId)) {
- const blured = bluredTabIds.has(tab.id);
- if (blured)
- bluredTabsFound = true;
- if (!bluredTabsFound)
- nextActiveTab = tab;
- if (bluredTabsFound &&
- !blured) {
- nextActiveTab = tab;
+ // First, try to find successor based on successorTabId from left tabs.
+ let successorTab = Tab.get(bluredTabs.find(tab => tab.active)?.successorTabId);
+ const scannedTabIds = new Set();
+ while (successorTab && bluredTabIds.has(successorTab.id)) {
+ if (scannedTabIds.has(successorTab.id))
+ break; // prevent infinite loop!
+ scannedTabIds.add(successorTab.id);
+ const nextSuccessorTab = (successorTab.successorTabId > 0 && successorTab.successorTabId != successorTab.id) ?
+ Tab.get(successorTab.successorTabId) :
+ null;
+ if (!nextSuccessorTab)
break;
+ successorTab = nextSuccessorTab;
+ }
+ log('blurTab/step 1: found successor = ', successorTab?.id);
+
+ // Second, try to detect successor based on their order.
+ if (!successorTab || bluredTabIds.has(successorTab.id)) {
+ if (successorTab)
+ log(' => it cannot become the successor, find again');
+ let bluredTabsFound = false;
+ for (const tab of Tab.getVisibleTabs(windowId || bluredTabs[0].windowId)) {
+ const blured = bluredTabIds.has(tab.id);
+ if (blured)
+ bluredTabsFound = true;
+ if (!bluredTabsFound)
+ successorTab = tab;
+ if (bluredTabsFound &&
+ !blured) {
+ successorTab = tab;
+ break;
+ }
}
+ log('blurTab/step 2: found successor = ', successorTab?.id);
}
- if (nextActiveTab)
- await activateTab(nextActiveTab, { silently });
- return nextActiveTab;
+
+ if (successorTab)
+ await activateTab(successorTab, { silently });
+ return successorTab;
}
export function removeTab(tab) {
return removeTabs([tab]);
}
-export function removeTabs(tabs, { keepDescendants, byMouseOperation, originalStructure, triggerTab } = {}) {
+export async function removeTabs(tabs, { keepDescendants, byMouseOperation, originalStructure, triggerTab } = {}) {
if (!Constants.IS_BACKGROUND)
throw new Error('TabsInternalOperation.removeTabs is available only on the background page, use a `kCOMMAND_REMOVE_TABS_INTERNALLY` message instead.');
@@ -108,6 +132,8 @@ export function removeTabs(tabs, { keepDescendants, byMouseOperation, originalSt
if (tabs.length == 0)
return;
+ await onBeforeTabsRemove.dispatch(tabs);
+
const win = TabsStore.windows.get(tabs[0].windowId);
const tabIds = [];
let willChangeFocus = false;
@@ -136,13 +162,12 @@ export function removeTabs(tabs, { keepDescendants, byMouseOperation, originalSt
clearCache(tab);
if (keepDescendants)
win.keepDescendantsTabs.add(tab.id);
- }
- if (willChangeFocus && byMouseOperation) {
- win.internalByMouseFocusCount++;
- setTimeout(() => { // the operation can be canceled
- if (win.internalByMouseFocusCount > 0)
- win.internalByMouseFocusCount--;
- }, 250);
+ if (willChangeFocus && byMouseOperation) {
+ win.internallyFocusingByMouseTabs.add(tab.id);
+ setTimeout(() => { // the operation can be canceled
+ win.internallyFocusingByMouseTabs.delete(tab.id);
+ }, 250);
+ }
}
}
diff --git a/waterfox/browser/components/sidebar/common/tree-behavior.js b/waterfox/browser/components/sidebar/common/tree-behavior.js
index 17f53194be618..f6d4a0c644733 100644
--- a/waterfox/browser/components/sidebar/common/tree-behavior.js
+++ b/waterfox/browser/components/sidebar/common/tree-behavior.js
@@ -82,15 +82,24 @@ export function getParentTabOperationBehavior(tab, { context, byInternalOperatio
log(' => behavior: ', behavior);
- if (behavior == Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_INTELLIGENTLY) {
- behavior = parentTab ? Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_ALL_CHILDREN : Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_FIRST_CHILD;
- log(' => intelligent behavior: ', behavior);
+ const replacedParentCount = tab?.$TST?.replacedParentGroupTabCount;
+ if (behavior == Constants.kPARENT_TAB_OPERATION_BEHAVIOR_REPLACE_WITH_GROUP_TAB &&
+ configs.closeParentBehavior_replaceWithGroup_thresholdToPrevent >= 0 &&
+ replacedParentCount &&
+ replacedParentCount >= configs.closeParentBehavior_replaceWithGroup_thresholdToPrevent) {
+ behavior = Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_INTELLIGENTLY;
+ log(' => the group tab is already replaced parent, fallback to kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_INTELLIGENTLY');
}
if (behavior == Constants.kPARENT_TAB_OPERATION_BEHAVIOR_ENTIRE_TREE &&
preventEntireTreeBehavior) {
+ behavior = Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_INTELLIGENTLY;
+ log(' => preventEntireTreeBehavior behavior, fallback to kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_INTELLIGENTLY');
+ }
+
+ if (behavior == Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_INTELLIGENTLY) {
behavior = parentTab ? Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_ALL_CHILDREN : Constants.kPARENT_TAB_OPERATION_BEHAVIOR_PROMOTE_FIRST_CHILD;
- log(' => preventEntireTreeBehavior behavior: ', behavior);
+ log(' => intelligent behavior: ', behavior);
}
// Promote all children to upper level, if this is the last child of the parent.
diff --git a/waterfox/browser/components/sidebar/common/tst-api.js b/waterfox/browser/components/sidebar/common/tst-api.js
index b0a0a9a710c38..b139a8693d90d 100644
--- a/waterfox/browser/components/sidebar/common/tst-api.js
+++ b/waterfox/browser/components/sidebar/common/tst-api.js
@@ -134,6 +134,7 @@ export const kNOTIFY_TRY_COLLAPSE_TREE_FROM_COLLAPSE_COMMAND = 'try-collapse-tre
export const kNOTIFY_TRY_COLLAPSE_TREE_FROM_COLLAPSE_ALL_COMMAND = 'try-collapse-tree-from-collapse-all-command';
export const kNOTIFY_TRY_FIXUP_TREE_ON_TAB_MOVED = 'try-fixup-tree-on-tab-moved';
export const kNOTIFY_TRY_HANDLE_NEWTAB = 'try-handle-newtab';
+export const kNOTIFY_TRY_SCROLL_TO_ACTIVATED_TAB = 'try-scroll-to-activated-tab';
export const kGET_TREE = 'get-tree';
export const kGET_LIGHT_TREE = 'get-light-tree';
export const kATTACH = 'attach';
@@ -272,6 +273,11 @@ export function clearCache(cache) {
// bacause instances of the class will be very short-life and increases RAM usage on
// massive tabs case.
export async function exportTab(sourceTab, { addonId, light, isContextTab, interval, permissions, cache, cacheKey } = {}) {
+ const normalizedSourceTab = Tab.get(sourceTab);
+ if (!normalizedSourceTab)
+ throw new Error(`Fatal error: tried to export not a tab. ${sourceTab}`);
+ sourceTab = normalizedSourceTab;
+
if (!interval)
interval = 0;
if (!cache)
diff --git a/waterfox/browser/components/sidebar/experiments/prefs.js b/waterfox/browser/components/sidebar/experiments/prefs.js
index e3af91d4abd32..38e7604f94599 100644
--- a/waterfox/browser/components/sidebar/experiments/prefs.js
+++ b/waterfox/browser/components/sidebar/experiments/prefs.js
@@ -36,7 +36,7 @@ function initSidebarCategory(document, { locale, BASE_URL, BASE_PREF }) {
if (document.querySelector('#category-tabsSidebar'))
return true;
- const generalItem = document.querySelector('#category-search');
+ const generalItem = document.querySelector('#category-general');
if (!generalItem)
return false;
@@ -859,9 +859,25 @@ function initSidebarCategory(document, { locale, BASE_URL, BASE_PREF }) {
document.defaultView.register_module('paneTabsSidebar', document.defaultView.gSidebarPage);
if (document.URL.endsWith('#tabsSidebar')) {
- document.querySelector('#categories').selectItem(document.querySelector('#category-tabsSidebar'));
- document.defaultView.gotoPref('paneTabsSidebar');
- document.defaultView.gCategoryInits.get('paneTabsSidebar').init();
+ let done = false;
+ const initialize = () => {
+ document.querySelector('#categories').selectItem(document.querySelector('#category-tabsSidebar'));
+ document.defaultView.gotoPref('paneTabsSidebar');
+ document.defaultView.gCategoryInits.get('paneTabsSidebar').init();
+ done = true;
+ };
+ if (document.defaultView.gSearchResultsPane.inited) {
+ initialize();
+ }
+ else {
+ document.addEventListener('DOMContentLoaded', initialize, { once: true });
+ document.defaultView.setTimeout(1000, () => {
+ if (done)
+ return;
+ document.removeEventListener('DOMContentLoaded', initialize, { once: true });
+ initialize();
+ });
+ }
}
}
catch (error) {
@@ -995,7 +1011,7 @@ const AboutPreferencesWatcher = {
const startAt = Date.now();
const topWin = loadInfo.browsingContext.topChromeWindow;
const timer = topWin.setInterval(() => {
- if (Date.now() - startAt > 1000) {
+ if (Date.now() - startAt > 5000) {
// timeout
topWin.clearInterval(timer);
return;
diff --git a/waterfox/browser/components/sidebar/experiments/waterfoxBridge.js b/waterfox/browser/components/sidebar/experiments/waterfoxBridge.js
index e7d216415897b..8c15bbee132cb 100644
--- a/waterfox/browser/components/sidebar/experiments/waterfoxBridge.js
+++ b/waterfox/browser/components/sidebar/experiments/waterfoxBridge.js
@@ -71,7 +71,7 @@ const BrowserWindowWatcher = {
if (win.location.href.startsWith('chrome://browser/content/browser.xhtml')) {
const installed = this.installTabsSidebar(win);
if (installed) {
- this.patchToTabPreviewModules(win);
+ this.patchToTabHoverPreviewModules(win);
this.patchToPlacesModules(win);
this.patchToFullScreenModules(win);
win.addEventListener('DOMAudioPlaybackBlockStarted', this, { capture: true });
@@ -96,7 +96,7 @@ const BrowserWindowWatcher = {
if (win.location.href.startsWith('chrome://browser/content/browser.xhtml')) {
this.uninstallTabsSidebar(win);
try {
- this.unpatchTabPreviewModules(win);
+ this.unpatchTabHoverPreviewModules(win);
this.unpatchPlacesModules(win);
this.unpatchFullScreenModules(win);
win.removeEventListener('DOMAudioPlaybackBlockStarted', this, { capture: true });
@@ -216,9 +216,9 @@ const BrowserWindowWatcher = {
:root.tabs-sidebar-right #tabs-sidebar-moveRight {
display: none;
}
- @supports not -moz-bool-pref("userChrome.icon.disabled") {
- @supports -moz-bool-pref("userChrome.icon.menu") {
- @supports -moz-bool-pref("userChrome.icon.context_menu") {
+ @media not (-moz-bool-pref: "userChrome.icon.disabled") {
+ @media (-moz-bool-pref: "userChrome.icon.menu") {
+ @media (-moz-bool-pref: "userChrome.icon.context_menu") {
#tabs-sidebar-newTab {
list-style-image: var(--uc-new-tab-icon);
}
@@ -327,18 +327,24 @@ const BrowserWindowWatcher = {
transition: 0.8s margin-left ease-out,
0.8s margin-right ease-out;
}
- @supports -moz-bool-pref("userChrome.fullscreen.overlap") {
- @supports -moz-bool-pref("browser.fullscreen.autohide") {
+ @media (-moz-bool-pref: "userChrome.fullscreen.overlap") {
+ @media (-moz-bool-pref: "browser.fullscreen.autohide") {
:root[sizemode="fullscreen"] #tabs-sidebar-box,
:root[sizemode="fullscreen"] #tabs-sidebar-splitter {
+ height: 100% !important;
position: fixed !important;
z-index: 900 !important;
- height: 100% !important;
}
:root[sizemode="fullscreen"] #tabs-sidebar-splitter {
width: 5px !important;
z-index: 890 !important;
}
+ :root[sizemode="fullscreen"]:not(.tabs-sidebar-right) #sidebar-box:not([positionend="true"]) ~ #tabs-sidebar-splitter,
+ :root[sizemode="fullscreen"].tabs-sidebar-right #sidebar-box:not([positionend="true"]) ~ #tabs-sidebar-splitter,
+ :root[sizemode="fullscreen"]:not(.tabs-sidebar-right) #sidebar-box[positionend="true"] ~ #tabs-sidebar-splitter,
+ :root[sizemode="fullscreen"].tabs-sidebar-right #sidebar-box[positionend="true"] ~ #tabs-sidebar-splitter {
+ order: unset !important;
+ }
:root[sizemode="fullscreen"]:not(.tabs-sidebar-right) #tabs-sidebar-box,
:root[sizemode="fullscreen"]:not(.tabs-sidebar-right) #tabs-sidebar-splitter {
left: 0;
@@ -351,7 +357,7 @@ const BrowserWindowWatcher = {
}
@media (prefers-reduced-motion: no-preference) {
- @supports -moz-bool-pref("userChrome.decoration.animate") {
+ @media (-moz-bool-pref: "userChrome.decoration.animate") {
:root[sizemode="fullscreen"] #tabs-sidebar-box {
transition: margin-left 1.3s var(--animation-easing-function) 50ms,
margin-right 1.3s var(--animation-easing-function) 50ms !important;
@@ -366,11 +372,14 @@ const BrowserWindowWatcher = {
document.querySelector('#mainCommandSet').appendChild(element(document, XUL, 'command', {
id: 'toggle-tabs-sidebar-command',
}));
- document.querySelector('#mainKeyset').appendChild(element(document, XUL, 'key', {
- id: 'toggle-tabs-sidebar-key',
- keycode: 'VK_F1',
- command: 'toggle-tabs-sidebar-command',
- }));
+ // So, as a workaround we don't remove the key element on uninstallation and reuse existing key element.
+ if (!document.querySelector('#toggle-tabs-sidebar-key')) {
+ document.querySelector('#mainKeyset').appendChild(element(document, XUL, 'key', {
+ id: 'toggle-tabs-sidebar-key',
+ keycode: 'VK_F1',
+ command: 'toggle-tabs-sidebar-command',
+ }));
+ }
document.querySelector('#viewSidebarMenu').appendChild(element(document, XUL, 'menuseparator', {
id: 'viewmenu-tabs-sidebar-separator',
@@ -655,7 +664,8 @@ const BrowserWindowWatcher = {
for (const node of document.querySelectorAll(`
#tabs-sidebar-splitter,
#tabs-sidebar-box,
- #toggle-tabs-sidebar-key,
+ /* Don't remove key element because only the first element added is effective.*/
+ /*#toggle-tabs-sidebar-key,*/
#toggle-tabs-sidebar-command,
#viewmenu-tabs-sidebar-separator,
#viewmenu-toggle-tabs-sidebar,
@@ -697,17 +707,27 @@ const BrowserWindowWatcher = {
}
},
- patchToTabPreviewModules(win) {
- const tabPreview = win.document.querySelector('#tabbrowser-tab-preview');
+ patchToTabHoverPreviewModules(win) {
+ const tabs = win.document.getElementById('tabbrowser-tabs');
+ if (!tabs)
+ return;
+
+ if (!tabs._previewPanel) {
+ // https://searchfox.org/mozilla-esr128/rev/7998c47697fb3ba3e380eda1281c8280da81a0b1/browser/components/tabbrowser/content/tabs.js#187
+ const TabHoverPreviewPanel = ChromeUtils.importESModule('chrome://browser/content/tabbrowser/tab-hover-preview.mjs').default;
+ tabs._previewPanel = new TabHoverPreviewPanel(win.document.getElementById('tab-preview-panel'));
+ }
+
+ const tabPreview = tabs._previewPanel;
if (!tabPreview ||
- !tabPreview.showPreview)
+ !tabPreview.activate)
return;
tabPreview.__ws__calculateCoordinates = () => {
const tabsSidebar = win.document.querySelector('#tabs-sidebar');
const tabsSidebarRect = tabsSidebar.getBoundingClientRect();
const align = win.document.documentElement.classList.contains('tabs-sidebar-right') ? 'right' : 'left';
- const previewRect = tabPreview.panel.getBoundingClientRect();
+ const previewRect = tabPreview._panel.getBoundingClientRect();
const x = align == 'left' ?
tabsSidebar.screenX + tabsSidebarRect.width - 2 :
tabsSidebar.screenX - previewRect.width + 2;
@@ -715,32 +735,50 @@ const BrowserWindowWatcher = {
return [x, y];
};
- if (!tabPreview.__ws_orig__showPreview)
- tabPreview.__ws_orig__showPreview = tabPreview.showPreview;
- tabPreview.showPreview = function() {
- if (typeof this.__ws__top == 'number') {
- const [x, y] = tabPreview.__ws__calculateCoordinates();
- this.panel.openPopupAtScreen(x, y, false);
+ if (!tabPreview.__ws_orig__activate)
+ tabPreview.__ws_orig__activate = tabPreview.activate;
+ // https://searchfox.org/mozilla-esr128/rev/7998c47697fb3ba3e380eda1281c8280da81a0b1/browser/components/tabbrowser/content/tab-hover-preview.mjs#97
+ const self = this;
+ tabPreview.activate = function(tab) {
+ if (this._isDisabled()) {
+ return;
}
- else {
- this.panel.openPopup(this.tab, {
- position: 'bottomleft topleft',
- y: -2,
- isContextMenu: false,
- });
+ this._tab = tab;
+ this._movePanel();
+
+ this._thumbnailElement = null;
+ this._maybeRequestThumbnail();
+ if (this._panel.state == 'open') {
+ this._updatePreview();
}
- win.addEventListener('wheel', this, {
- capture: true,
- passive: true,
- });
- win.addEventListener('TabSelect', this);
- this.panel.addEventListener('popuphidden', this);
+ if (this._timer) {
+ return;
+ }
+ this._timer = this._win.setTimeout(() => {
+ this._timer = null;
+ if (self.shouldShowSidebar(win.document) &&
+ typeof this.__ws__top == 'number') {
+ const [x, y] = tabPreview.__ws__calculateCoordinates();
+ this._panel.openPopupAtScreen(x, y, false);
+ }
+ else {
+ this._panel.openPopup(this._tab, {
+ // POPUP_OPTIONS https://searchfox.org/mozilla-esr128/rev/7998c47697fb3ba3e380eda1281c8280da81a0b1/browser/components/tabbrowser/content/tab-hover-preview.mjs#9
+ position: 'bottomleft topleft',
+ x: 0,
+ y: -2,
+ });
+ }
+ }, this._prefPreviewDelay);
+ this._win.addEventListener('TabSelect', this);
+ this._panel.addEventListener('popupshowing', this);
};
- if (!tabPreview.panel.__ws_orig__moveToAnchor)
- tabPreview.panel.__ws_orig__moveToAnchor = tabPreview.panel.moveToAnchor;
- tabPreview.panel.moveToAnchor = function(...args) {
- if (typeof tabPreview.__ws__top == 'number') {
+ if (!tabPreview._panel.__ws_orig__moveToAnchor)
+ tabPreview._panel.__ws_orig__moveToAnchor = tabPreview._panel.moveToAnchor;
+ tabPreview._panel.moveToAnchor = function(...args) {
+ if (self.shouldShowSidebar(win.document) &&
+ typeof tabPreview.__ws__top == 'number') {
const [x, y] = tabPreview.__ws__calculateCoordinates();
this.moveTo(x, y);
return;
@@ -749,22 +787,29 @@ const BrowserWindowWatcher = {
};
},
- unpatchTabPreviewModules(win) {
- const tabPreview = win.document.querySelector('#tabbrowser-tab-preview');
+ unpatchTabHoverPreviewModules(win) {
+ const tabPreview = win.document.getElementById('tabbrowser-tabs')?._previewPanel;
if (!tabPreview ||
- !tabPreview.showPreview)
+ !tabPreview.activate)
return;
- if (tabPreview.__ws_orig__showPreview) {
- tabPreview.showPreview = tabPreview.__ws_orig__showPreview;
- tabPreview.__ws_orig__showPreview = null;
+ if (tabPreview.__ws_orig__activate) {
+ tabPreview.activate = tabPreview.__ws_orig__activate;
+ tabPreview.__ws_orig__activate = null;
+ }
+
+ if (tabPreview._panel?.__ws_orig__moveToAnchor) {
+ tabPreview._panel.moveToAnchor = tabPreview._panel.__ws_orig__moveToAnchor;
+ tabPreview._panel.__ws_orig__moveToAnchor = null;
}
},
patchToFullScreenModules(win) {
const FullScreen = win.FullScreen;
- const sidebarBox = win.document.querySelector('#tabs-sidebar-box');
- const sidebarSplitter = win.document.querySelector('#tabs-sidebar-splitter');
+ const sidebarMainBox = win.document.querySelector('#sidebar-main');
+ const sidebarBox = win.document.querySelector('#sidebar-box');
+ const tabsSidebarBox = win.document.querySelector('#tabs-sidebar-box');
+ const tabsSidebarSplitter = win.document.querySelector('#tabs-sidebar-splitter');
if (!FullScreen.__ws_orig__toggle)
FullScreen.__ws_orig__toggle = FullScreen.toggle;
@@ -775,7 +820,7 @@ const BrowserWindowWatcher = {
if (enterFS) {
if (!win.document.fullscreenElement) {
if (win.gNavToolbox.getAttribute('fullscreenShouldAnimate') == 'true')
- sidebarBox.setAttribute('fullscreenShouldAnimate', true);
+ tabsSidebarBox.setAttribute('fullscreenShouldAnimate', true);
FullScreen.__ws_sidebar.hide();
}
@@ -809,30 +854,32 @@ const BrowserWindowWatcher = {
updateMouseTargetRect() {
const contentsAreaRect = win.gBrowser.tabpanels.getBoundingClientRect();
+ const tabsSidebarBoxRect = tabsSidebarBox.getBoundingClientRect();
+ const sidebarMainBoxRect = sidebarMainBox.getBoundingClientRect();
const sidebarBoxRect = sidebarBox.getBoundingClientRect();
const isRight = this.isRightSide();
this._mouseTargetRect = {
top: contentsAreaRect.top,
bottom: contentsAreaRect.bottom,
- left: contentsAreaRect.left + (isRight ? 0 : sidebarBoxRect.width + 50),
- right: contentsAreaRect.right - (isRight ? sidebarBoxRect.width + 50 : 0),
+ left: Math.min(sidebarMainBoxRect.left, sidebarBoxRect.left, contentsAreaRect.left) + (isRight ? 0 : tabsSidebarBoxRect.width + 50),
+ right: Math.max(sidebarMainBoxRect.right, sidebarBoxRect.right, contentsAreaRect.right) - (isRight ? tabsSidebarBoxRect.width + 50 : 0),
};
win.MousePosTracker.addListener(this);
},
hide() {
if (this.isRightSide())
- sidebarBox.style.marginRight = `-${sidebarBox.getBoundingClientRect().width}px`;
+ tabsSidebarBox.style.marginRight = `-${tabsSidebarBox.getBoundingClientRect().width}px`;
else
- sidebarBox.style.marginLeft = `-${sidebarBox.getBoundingClientRect().width}px`;
+ tabsSidebarBox.style.marginLeft = `-${tabsSidebarBox.getBoundingClientRect().width}px`;
win.MousePosTracker.removeListener(this);
this.startListenToShow();
},
show({ exitting } = {}) {
- sidebarBox.removeAttribute('fullscreenShouldAnimate');
- sidebarBox.style.marginLeft = sidebarBox.style.marginRight = '';
+ tabsSidebarBox.removeAttribute('fullscreenShouldAnimate');
+ tabsSidebarBox.style.marginLeft = tabsSidebarBox.style.marginRight = '';
this.endListenToShow();
if (!exitting) {
@@ -849,9 +896,9 @@ const BrowserWindowWatcher = {
if (this.listeningToShow)
return;
this.listeningToShow = true;
- sidebarSplitter.addEventListener('mouseover', FullScreen.__ws_sidebar);
- sidebarSplitter.addEventListener('dragenter', FullScreen.__ws_sidebar);
- sidebarSplitter.addEventListener('touchmove', FullScreen.__ws_sidebar, {
+ tabsSidebarSplitter.addEventListener('mouseover', FullScreen.__ws_sidebar);
+ tabsSidebarSplitter.addEventListener('dragenter', FullScreen.__ws_sidebar);
+ tabsSidebarSplitter.addEventListener('touchmove', FullScreen.__ws_sidebar, {
passive: true,
});
},
@@ -860,9 +907,9 @@ const BrowserWindowWatcher = {
if (!this.listeningToShow)
return;
this.listeningToShow = false;
- sidebarSplitter.removeEventListener('mouseover', FullScreen.__ws_sidebar);
- sidebarSplitter.removeEventListener('dragenter', FullScreen.__ws_sidebar);
- sidebarSplitter.removeEventListener('touchmove', FullScreen.__ws_sidebar, {
+ tabsSidebarSplitter.removeEventListener('mouseover', FullScreen.__ws_sidebar);
+ tabsSidebarSplitter.removeEventListener('dragenter', FullScreen.__ws_sidebar);
+ tabsSidebarSplitter.removeEventListener('touchmove', FullScreen.__ws_sidebar, {
passive: true,
});
},
@@ -1070,10 +1117,12 @@ const BrowserWindowWatcher = {
break;
case 'tabs-sidebar-bookmarkTab':
- case 'tabs-sidebar-bookmarkTabs':
- win.PlacesUIUtils.showBookmarkPagesDialog(win.PlacesCommandHook.uniqueSelectedPages); // same to #toolbar-context-bookmarkSelectedTab / #toolbar-context-bookmarkSelectedTabs
+ case 'tabs-sidebar-bookmarkTabs': {
+ const pages = win.PlacesCommandHook.getUniquePages(win.gBrowser.selectedTabs)
+ .map(page => Object.assign(page, { uri: Services.io.createExposableURI(page.uri) }));
+ win.PlacesUIUtils.showBookmarkPagesDialog(pages); // same to #toolbar-context-bookmarkSelectedTab / #toolbar-context-bookmarkSelectedTabs
this.tryHidePopup(event);
- break;
+ }; break;
case 'tabs-sidebar-selectAll':
win.gBrowser.selectAllTabs(); // same to #toolbar-context-selectAllTabs
@@ -1130,16 +1179,22 @@ const BrowserWindowWatcher = {
moveToolbarButtonToDefaultPosition() {
try {
- const state = JSON.parse(Services.prefs.getStringPref('browser.uiCustomization.state', '{}'));
+ const rawState = Services.prefs.getStringPref('browser.uiCustomization.state', '{}');
+ if (!rawState)
+ return false;
+
+ const state = JSON.parse(rawState);
if (!state?.placements)
return false;
let foundInNavBar = false;
+ let found = false;
for (const [name, items] of Object.entries(state.placements)) {
if (!items.includes(this.id))
continue;
if (name == 'nav-bar')
foundInNavBar = true;
+ found = true;
break;
}
@@ -1154,9 +1209,17 @@ const BrowserWindowWatcher = {
if (foundInNavBar)
lazy.CustomizableUI.moveWidgetWithinArea(this.id, index);
- else
+ else if (!found)
lazy.CustomizableUI.addWidgetToArea(this.id, this.defaultArea, index);
+ const win = Services.wm.getMostRecentBrowserWindow();
+ win.setTimeout(() => {
+ if (win.document.getElementById(this.id))
+ return;
+ console.log('failed to insert tabs sidebar button due to unhandled error in CustomizableUI module: retrying with reset');
+ win.gCustomizeMode.reset();
+ }, 250); // for safety, this delay need to be large enough
+
return true;
}
catch (error) {
@@ -1223,13 +1286,18 @@ const BrowserWindowWatcher = {
}
}; break;
- case 'browser.uiCustomization.state':
+ case 'browser.uiCustomization.state': {
if (!Services.prefs.getStringPref(name)) { // resetting!
- Services.wm.getMostRecentBrowserWindow().setTimeout(() => {
+ const tryInsertButton = () => {
+ if (!Services.prefs.getStringPref(name)) {
+ Services.wm.getMostRecentBrowserWindow().setTimeout(tryInsertButton, 10);
+ return;
+ }
this.moveToolbarButtonToDefaultPosition();
- }, 100);
+ };
+ tryInsertButton();
}
- break;
+ }; break;
}
},
@@ -1546,17 +1614,15 @@ this.waterfoxBridge = class extends ExtensionAPI {
async showPreviewPanel(tabId, top) {
const tab = tabId && context.extension.tabManager.get(tabId);
- if (!tab ||
- !Services.prefs.getBoolPref('browser.tabs.cardPreview.enabled', false))
+ if (!tab)
return;
const document = tab.nativeTab.ownerDocument;
- const tabPreview = document.querySelector('#tabbrowser-tab-preview');
+ const tabPreview = document.getElementById('tabbrowser-tabs')?._previewPanel;
if (!tabPreview)
return;
tabPreview.__ws__top = top;
- tabPreview.tab = tab.nativeTab;
- tabPreview.showPreview();
+ tabPreview.activate(tab.nativeTab);
},
async hidePreviewPanel(windowId) {
@@ -1564,28 +1630,12 @@ this.waterfoxBridge = class extends ExtensionAPI {
if (!win)
return;
- const tabPreview = win.window.document.querySelector('#tabbrowser-tab-preview');
+ const tabPreview = document.getElementById('tabbrowser-tabs')?._previewPanel;
if (!tabPreview)
return;
tabPreview.__ws__top = null;
- tabPreview.tab = null;
},
- onHoverPreviewChanged: new EventManager({
- context,
- name: 'waterfoxBridge.onHoverPreviewChanged',
- register: (fire) => {
- const observe = (_subject, _topic, data) => {
- fire.async(Services.prefs.getBoolPref('browser.tabs.cardPreview.enabled', false)).catch(() => {}); // ignore Message Manager disconnects
- };
- Services.prefs.addObserver('browser.tabs.cardPreview.enabled', observe);
- observe();
- return () => {
- Services.prefs.removeObserver('browser.tabs.cardPreview.enabled', observe);
- };
- },
- }).api(),
-
async openPreferences() {
BrowserWindowWatcher.openOptions();
},
diff --git a/waterfox/browser/components/sidebar/experiments/waterfoxBridge.json b/waterfox/browser/components/sidebar/experiments/waterfoxBridge.json
index 692a383fdb73a..38cb4c8760291 100644
--- a/waterfox/browser/components/sidebar/experiments/waterfoxBridge.json
+++ b/waterfox/browser/components/sidebar/experiments/waterfoxBridge.json
@@ -434,17 +434,6 @@
}
]
},
- {
- "name": "onHoverPreviewChanged",
- "description": "Notified when hover preview is activated or deactivated.",
- "type": "function",
- "parameters": [
- {
- "type": "boolean",
- "name": "hoverPreviewEnabled"
- }
- ]
- },
{
"name": "onAutoplayBlocked",
"description": "Notified when a tab's autoplay is blocked.",
diff --git a/waterfox/browser/components/sidebar/manifest.json b/waterfox/browser/components/sidebar/manifest.json
index f86cfe07d445a..8466e134ec2a3 100644
--- a/waterfox/browser/components/sidebar/manifest.json
+++ b/waterfox/browser/components/sidebar/manifest.json
@@ -1,291 +1,272 @@
{
- "manifest_version": 2,
- "name": "__MSG_extensionName__",
- "version": "1.0.2.1",
- "author": "Waterfox",
- "hidden": true,
- "description": "__MSG_extensionDescription__",
- "permissions": [
- "activeTab",
- "bookmarks",
- "clipboardRead",
- "contextualIdentities",
- "cookies",
- "menus",
- "menus.overrideContext",
- "notifications",
- "scripting",
- "search",
- "sessions",
- "storage",
- "tabs",
- "theme"
- ],
- "optional_permissions": [
- ""
- ],
- "background": {
- "page": "background/background.html"
- },
- "commands": {
- "reloadTree": {
- "description": "__MSG_context_reloadTree_command__"
- },
- "reloadDescendants": {
- "description": "__MSG_context_reloadDescendants_command__"
- },
- "unblockAutoplayTree": {
- "description": "__MSG_context_unblockAutoplayTree_command__"
- },
- "unblockAutoplayDescendants": {
- "description": "__MSG_context_unblockAutoplayDescendants_command__"
- },
- "toggleMuteTree": {
- "description": "__MSG_context_toggleMuteTree_command__"
- },
- "toggleMuteDescendants": {
- "description": "__MSG_context_toggleMuteDescendants_command__"
- },
- "closeTree": {
- "description": "__MSG_context_closeTree_command__"
- },
- "closeDescendants": {
- "description": "__MSG_context_closeDescendants_command__"
- },
- "closeOthers": {
- "description": "__MSG_context_closeOthers_command__"
- },
- "toggleSticky": {
- "description": "__MSG_context_toggleSticky_command__"
- },
- "collapseTree": {
- "description": "__MSG_context_collapseTree_command__"
- },
- "collapseTreeRecursively": {
- "description": "__MSG_context_collapseTreeRecursively_command__"
- },
- "collapseAll": {
- "description": "__MSG_context_collapseAll_command__"
- },
- "expandTree": {
- "description": "__MSG_context_expandTree_command__"
- },
- "expandTreeRecursively": {
- "description": "__MSG_context_expandTreeRecursively_command__"
- },
- "expandAll": {
- "description": "__MSG_context_expandAll_command__"
- },
- "toggleTreeCollapsed": {
- "description": "__MSG_command_toggleTreeCollapsed__"
- },
- "toggleTreeCollapsedRecursively": {
- "description": "__MSG_command_toggleTreeCollapsedRecursively__"
- },
- "bookmarkTree": {
- "description": "__MSG_context_bookmarkTree_command__"
- },
- "newIndependentTab": {
- "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_independent_command__"
- },
- "newChildTab": {
- "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_child_command__"
- },
- "newChildTabTop": {
- "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_childTop_command__"
- },
- "newChildTabEnd": {
- "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_childEnd_command__"
- },
- "newSiblingTab": {
- "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_sibling_command__"
- },
- "newNextSiblingTab": {
- "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_nextSibling_command__"
- },
- "newContainerTab": {
- "description": "__MSG_tabbar_newTabWithContexualIdentity_tooltip__"
- },
- "tabMoveUp": {
- "description": "__MSG_command_tabMoveUp__"
- },
- "treeMoveUp": {
- "description": "__MSG_command_treeMoveUp__"
- },
- "tabMoveDown": {
- "description": "__MSG_command_tabMoveDown__"
- },
- "treeMoveDown": {
- "description": "__MSG_command_treeMoveDown__"
- },
- "focusPrevious": {
- "description": "__MSG_command_focusPrevious__"
- },
- "focusPreviousSilently": {
- "description": "__MSG_command_focusPreviousSilently__"
- },
- "focusNext": {
- "description": "__MSG_command_focusNext__"
- },
- "focusNextSilently": {
- "description": "__MSG_command_focusNextSilently__"
- },
- "focusParent": {
- "description": "__MSG_command_focusParent__"
- },
- "focusParentOrCollapse": {
- "description": "__MSG_command_focusParentOrCollapse__"
- },
- "focusFirstChild": {
- "description": "__MSG_command_focusFirstChild__"
- },
- "focusFirstChildOrExpand": {
- "description": "__MSG_command_focusFirstChildOrExpand__"
- },
- "focusLastChild": {
- "description": "__MSG_command_focusLastChild__"
- },
- "focusPreviousSibling": {
- "description": "__MSG_command_focusPreviousSibling__"
- },
- "focusNextSibling": {
- "description": "__MSG_command_focusNextSibling__"
- },
- "simulateUpOnTree": {
- "description": "__MSG_command_simulateUpOnTree__",
- "suggested_key": {
- "default": "Alt+Shift+Up"
- }
- },
- "simulateDownOnTree": {
- "description": "__MSG_command_simulateDownOnTree__",
- "suggested_key": {
- "default": "Alt+Shift+Down"
- }
- },
- "simulateLeftOnTree": {
- "description": "__MSG_command_simulateLeftOnTree__",
- "suggested_key": {
- "default": "Alt+Shift+Left"
- }
- },
- "simulateRightOnTree": {
- "description": "__MSG_command_simulateRightOnTree__",
- "suggested_key": {
- "default": "Alt+Shift+Right"
- }
- },
- "tabbarUp": {
- "description": "__MSG_command_tabbarUp__",
- "suggested_key": {
- "default": "Alt+Up"
- }
- },
- "tabbarPageUp": {
- "description": "__MSG_command_tabbarPageUp__",
- "suggested_key": {
- "default": "Alt+PageUp"
- }
- },
- "tabbarHome": {
- "description": "__MSG_command_tabbarHome__",
- "suggested_key": {
- "default": "Alt+Shift+Home"
- }
- },
- "tabbarDown": {
- "description": "__MSG_command_tabbarDown__",
- "suggested_key": {
- "default": "Alt+Down"
- }
- },
- "tabbarPageDown": {
- "description": "__MSG_command_tabbarPageDown__",
- "suggested_key": {
- "default": "Alt+PageDown"
- }
- },
- "tabbarEnd": {
- "description": "__MSG_command_tabbarEnd__",
- "suggested_key": {
- "default": "Alt+End"
- }
- },
- "toggleSubPanel": {
- "description": "__MSG_command_toggleSubPanel__",
- "suggested_key": {
- "default": "F2"
- }
- },
- "switchSubPanel": {
- "description": "__MSG_command_switchSubPanel__"
- },
- "increaseSubPanel": {
- "description": "__MSG_command_increaseSubPanel__"
- },
- "decreaseSubPanel": {
- "description": "__MSG_command_decreaseSubPanel__"
- }
- },
- "web_accessible_resources": [
- "/resources/group-tab.html*"
- ],
- "protocol_handlers": [
- {
- "protocol": "ext+ws",
- "name": "Waterfox",
- "uriTemplate": "/resources/protocol-handler.html?%s"
- }
- ],
- "experiment_apis": {
- "prefs": {
- "schema": "experiments/prefs.json",
- "parent": {
- "scopes": [
- "addon_parent"
- ],
- "paths": [
- [
- "prefs"
- ]
- ],
- "script": "experiments/prefs.js"
- }
- },
- "syncPrefs": {
- "schema": "experiments/syncPrefs.json",
- "child": {
- "scopes": [
- "addon_child"
- ],
- "paths": [
- [
- "syncPrefs"
- ]
- ],
- "script": "experiments/syncPrefs.js"
- }
- },
- "waterfoxBridge": {
- "schema": "experiments/waterfoxBridge.json",
- "parent": {
- "scopes": [
- "addon_parent"
- ],
- "paths": [
- [
- "waterfoxBridge"
- ]
- ],
- "script": "experiments/waterfoxBridge.js"
- }
- }
- },
- "default_locale": "en",
- "browser_specific_settings": {
- "gecko": {
- "id": "sidebar@waterfox.net",
- "strict_min_version": "115.0"
- }
- }
+ "manifest_version": 2,
+ "name": "__MSG_extensionName__",
+ "version": "1.0.3",
+ "author": "Waterfox",
+ "hidden": true,
+ "description": "__MSG_extensionDescription__",
+ "permissions": [
+ "activeTab",
+ "bookmarks",
+ "clipboardRead",
+ "contextualIdentities",
+ "cookies",
+ "menus",
+ "menus.overrideContext",
+ "notifications",
+ "search",
+ "sessions",
+ "storage",
+ "tabs",
+ "theme",
+ ""
+ ],
+ "background": {
+ "page": "background/background.html"
+ },
+ "commands": {
+ "reloadTree": {
+ "description": "__MSG_context_reloadTree_command__"
+ },
+ "reloadDescendants": {
+ "description": "__MSG_context_reloadDescendants_command__"
+ },
+ "unblockAutoplayTree": {
+ "description": "__MSG_context_unblockAutoplayTree_command__"
+ },
+ "unblockAutoplayDescendants": {
+ "description": "__MSG_context_unblockAutoplayDescendants_command__"
+ },
+ "toggleMuteTree": {
+ "description": "__MSG_context_toggleMuteTree_command__"
+ },
+ "toggleMuteDescendants": {
+ "description": "__MSG_context_toggleMuteDescendants_command__"
+ },
+ "closeTree": {
+ "description": "__MSG_context_closeTree_command__"
+ },
+ "closeDescendants": {
+ "description": "__MSG_context_closeDescendants_command__"
+ },
+ "closeOthers": {
+ "description": "__MSG_context_closeOthers_command__"
+ },
+ "toggleSticky": {
+ "description": "__MSG_context_toggleSticky_command__"
+ },
+ "collapseTree": {
+ "description": "__MSG_context_collapseTree_command__"
+ },
+ "collapseTreeRecursively": {
+ "description": "__MSG_context_collapseTreeRecursively_command__"
+ },
+ "collapseAll": {
+ "description": "__MSG_context_collapseAll_command__"
+ },
+ "expandTree": {
+ "description": "__MSG_context_expandTree_command__"
+ },
+ "expandTreeRecursively": {
+ "description": "__MSG_context_expandTreeRecursively_command__"
+ },
+ "expandAll": {
+ "description": "__MSG_context_expandAll_command__"
+ },
+ "toggleTreeCollapsed": {
+ "description": "__MSG_command_toggleTreeCollapsed__"
+ },
+ "toggleTreeCollapsedRecursively": {
+ "description": "__MSG_command_toggleTreeCollapsedRecursively__"
+ },
+ "bookmarkTree": {
+ "description": "__MSG_context_bookmarkTree_command__"
+ },
+ "newIndependentTab": {
+ "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_independent_command__"
+ },
+ "newChildTab": {
+ "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_child_command__"
+ },
+ "newChildTabTop": {
+ "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_childTop_command__"
+ },
+ "newChildTabEnd": {
+ "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_childEnd_command__"
+ },
+ "newSiblingTab": {
+ "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_sibling_command__"
+ },
+ "newNextSiblingTab": {
+ "description": "__MSG_tabbar_newTabButton_tooltip__: __MSG_tabbar_newTabAction_nextSibling_command__"
+ },
+ "newContainerTab": {
+ "description": "__MSG_tabbar_newTabWithContexualIdentity_tooltip__"
+ },
+ "tabMoveUp": {
+ "description": "__MSG_command_tabMoveUp__"
+ },
+ "treeMoveUp": {
+ "description": "__MSG_command_treeMoveUp__"
+ },
+ "tabMoveDown": {
+ "description": "__MSG_command_tabMoveDown__"
+ },
+ "treeMoveDown": {
+ "description": "__MSG_command_treeMoveDown__"
+ },
+ "focusPrevious": {
+ "description": "__MSG_command_focusPrevious__"
+ },
+ "focusPreviousSilently": {
+ "description": "__MSG_command_focusPreviousSilently__"
+ },
+ "focusNext": {
+ "description": "__MSG_command_focusNext__"
+ },
+ "focusNextSilently": {
+ "description": "__MSG_command_focusNextSilently__"
+ },
+ "focusParent": {
+ "description": "__MSG_command_focusParent__"
+ },
+ "focusParentOrCollapse": {
+ "description": "__MSG_command_focusParentOrCollapse__"
+ },
+ "focusFirstChild": {
+ "description": "__MSG_command_focusFirstChild__"
+ },
+ "focusFirstChildOrExpand": {
+ "description": "__MSG_command_focusFirstChildOrExpand__"
+ },
+ "focusLastChild": {
+ "description": "__MSG_command_focusLastChild__"
+ },
+ "focusPreviousSibling": {
+ "description": "__MSG_command_focusPreviousSibling__"
+ },
+ "focusNextSibling": {
+ "description": "__MSG_command_focusNextSibling__"
+ },
+ "simulateUpOnTree": {
+ "description": "__MSG_command_simulateUpOnTree__",
+ "suggested_key": {
+ "default": "Alt+Shift+Up",
+ "mac": "MacCtrl+Shift+Up"
+ }
+ },
+ "simulateDownOnTree": {
+ "description": "__MSG_command_simulateDownOnTree__",
+ "suggested_key": {
+ "default": "Alt+Shift+Down",
+ "mac": "MacCtrl+Shift+Down"
+ }
+ },
+ "simulateLeftOnTree": {
+ "description": "__MSG_command_simulateLeftOnTree__",
+ "suggested_key": {
+ "default": "Alt+Shift+Left",
+ "mac": "MacCtrl+Shift+Left"
+ }
+ },
+ "simulateRightOnTree": {
+ "description": "__MSG_command_simulateRightOnTree__",
+ "suggested_key": {
+ "default": "Alt+Shift+Right",
+ "mac": "MacCtrl+Shift+Right"
+ }
+ },
+ "tabbarUp": {
+ "description": "__MSG_command_tabbarUp__",
+ "suggested_key": {
+ "default": "Alt+Up"
+ }
+ },
+ "tabbarPageUp": {
+ "description": "__MSG_command_tabbarPageUp__",
+ "suggested_key": {
+ "default": "Alt+PageUp"
+ }
+ },
+ "tabbarHome": {
+ "description": "__MSG_command_tabbarHome__",
+ "suggested_key": {
+ "default": "Alt+Shift+Home"
+ }
+ },
+ "tabbarDown": {
+ "description": "__MSG_command_tabbarDown__",
+ "suggested_key": {
+ "default": "Alt+Down"
+ }
+ },
+ "tabbarPageDown": {
+ "description": "__MSG_command_tabbarPageDown__",
+ "suggested_key": {
+ "default": "Alt+PageDown"
+ }
+ },
+ "tabbarEnd": {
+ "description": "__MSG_command_tabbarEnd__",
+ "suggested_key": {
+ "default": "Alt+End"
+ }
+ },
+ "toggleSubPanel": {
+ "description": "__MSG_command_toggleSubPanel__",
+ "suggested_key": {
+ "default": "F2"
+ }
+ },
+ "switchSubPanel": {
+ "description": "__MSG_command_switchSubPanel__"
+ },
+ "increaseSubPanel": {
+ "description": "__MSG_command_increaseSubPanel__"
+ },
+ "decreaseSubPanel": {
+ "description": "__MSG_command_decreaseSubPanel__"
+ }
+ },
+ "web_accessible_resources": ["/resources/group-tab.html*"],
+ "protocol_handlers": [
+ {
+ "protocol": "ext+ws",
+ "name": "Waterfox",
+ "uriTemplate": "/resources/protocol-handler.html?%s"
+ }
+ ],
+ "experiment_apis": {
+ "prefs": {
+ "schema": "experiments/prefs.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "paths": [["prefs"]],
+ "script": "experiments/prefs.js"
+ }
+ },
+ "syncPrefs": {
+ "schema": "experiments/syncPrefs.json",
+ "child": {
+ "scopes": ["addon_child"],
+ "paths": [["syncPrefs"]],
+ "script": "experiments/syncPrefs.js"
+ }
+ },
+ "waterfoxBridge": {
+ "schema": "experiments/waterfoxBridge.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "paths": [["waterfoxBridge"]],
+ "script": "experiments/waterfoxBridge.js"
+ }
+ }
+ },
+ "default_locale": "en",
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "sidebar@waterfox.net",
+ "strict_min_version": "128.0"
+ }
+ }
}
diff --git a/waterfox/browser/components/sidebar/options/init.js b/waterfox/browser/components/sidebar/options/init.js
index 6b32944b93fed..cf988b81aa6fe 100644
--- a/waterfox/browser/components/sidebar/options/init.js
+++ b/waterfox/browser/components/sidebar/options/init.js
@@ -55,7 +55,8 @@ const options = new Options(configs, {
});
document.title = browser.i18n.getMessage('config_title');
-if ((location.hash && location.hash != '#') ||
+if ((location.hash &&
+ /^#!?$/.test(location.hash)) ||
/independent=true/.test(location.search))
document.body.classList.add('independent');
@@ -139,6 +140,8 @@ let mUserStyleRulesFieldEditor;
const mDarkModeMedia = window.matchMedia('(prefers-color-scheme: dark)');
+let mShowExpertOptionsTemporarily = false;
+
function onConfigChanged(key) {
const value = configs[key];
switch (key) {
@@ -181,19 +184,24 @@ function onConfigChanged(key) {
}
}; break;
- case 'showExpertOptions':
- document.documentElement.classList.toggle('show-expert-options', configs.showExpertOptions);
+ case 'showExpertOptions': {
+ if (mShowExpertOptionsTemporarily && !configs.showExpertOptions)
+ document.querySelector('#showExpertOptions').checked = true;
+ const show = mShowExpertOptionsTemporarily || configs.showExpertOptions;
+ document.documentElement.classList.toggle('show-expert-options', show);
for (const item of document.querySelectorAll('#parentTabOperationBehaviorModeGroup li li')) {
const radio = item.querySelector('input[type="radio"]');
- if (configs.showExpertOptions || radio.checked) {
+ if (show || radio.checked) {
item.style.display = '';
- radio.style.display = configs.showExpertOptions ? '' : 'none';
+ radio.style.display = show || radio.checked ? '' : 'none';
}
else {
- item.style.display = radio.style.display = 'none';
+ item.style.display = radio.style.display = 'none';
}
}
- break;
+ if (mShowExpertOptionsTemporarily && !configs.showExpertOptions)
+ mShowExpertOptionsTemporarily = false;
+ }; break;
case 'syncDeviceInfo': {
const name = (configs.syncDeviceInfo || {}).name || '';
@@ -614,6 +622,12 @@ window.addEventListener('DOMContentLoaded', async () => {
console.error(error);
}
+ mShowExpertOptionsTemporarily = !!(
+ location.hash &&
+ !/^#!?$/.test(location.hash) &&
+ document.querySelector(`.expert #${location.hash.replace(/^#!?/, '')}, .expert#${location.hash.replace(/^#!?/, '')}`)
+ );
+
try {
options.buildUIForAllConfigs(document.querySelector('#group-allConfigs'));
onConfigChanged('successorTabControlLevel');
@@ -801,6 +815,9 @@ function initPermissionOptions() {
Permissions.CLIPBOARD_READ,
document.querySelector('#clipboardReadPermissionGranted_middleClickPasteURLOnNewTabButton'),
{
+ onInitialized: (granted) => {
+ return granted && configs.middleClickPasteURLOnNewTabButton;
+ },
onChanged: (granted) => {
configs.middleClickPasteURLOnNewTabButton = granted;
}
diff --git a/waterfox/browser/components/sidebar/options/options.css b/waterfox/browser/components/sidebar/options/options.css
index ce60bc4ba183e..797fdde652ced 100644
--- a/waterfox/browser/components/sidebar/options/options.css
+++ b/waterfox/browser/components/sidebar/options/options.css
@@ -131,7 +131,7 @@ label.has-radio:not(.inline) {
label input[type="radio"] ~ img,
label input[type="checkbox"] ~ img {
- border: 1px solid ThreeDShadow;
+ border: 1px solid var(--ThreeDShadow);
vertical-align: middle;
margin-top: 0.15em;
margin-bottom: 0.15em;
diff --git a/waterfox/browser/components/sidebar/options/options.html b/waterfox/browser/components/sidebar/options/options.html
index b1d0951566d92..97bd776775a82 100644
--- a/waterfox/browser/components/sidebar/options/options.html
+++ b/waterfox/browser/components/sidebar/options/options.html
@@ -770,6 +770,16 @@ __MSG_config_newTab_caption__
__MSG_groupTab_temporaryAggressive_label__
__MSG_config_groupTabTemporaryState_option_checked_after__
+
@@ -1286,6 +1296,12 @@ __MSG_config_drag_caption__
__MSG_config_insertDroppedTabsAt_end__
+
+
+