1162 lines
37 KiB
JavaScript
1162 lines
37 KiB
JavaScript
/*
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
'use strict';
|
|
|
|
import {
|
|
log as internalLogger,
|
|
wait,
|
|
mapAndFilterUniq,
|
|
configs,
|
|
loadUserStyleRules,
|
|
doProgressively,
|
|
} from '/common/common.js';
|
|
import * as ApiTabs from '/common/api-tabs.js';
|
|
import * as Bookmark from '/common/bookmark.js';
|
|
import * as BrowserTheme from '/common/browser-theme.js';
|
|
import * as Constants from '/common/constants.js';
|
|
import * as ContextualIdentities from '/common/contextual-identities.js';
|
|
import * as Permissions from '/common/permissions.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 TabsUpdate from '/common/tabs-update.js';
|
|
import * as TreeBehavior from '/common/tree-behavior.js';
|
|
import * as TSTAPI from '/common/tst-api.js';
|
|
|
|
import { Tab, TreeItem } from '/common/TreeItem.js';
|
|
|
|
import * as Background from './background.js';
|
|
import * as Commands from './commands.js';
|
|
import * as Migration from './migration.js';
|
|
import * as TabsGroup from './tabs-group.js';
|
|
import * as TabsOpen from './tabs-open.js';
|
|
import * as Tree from './tree.js';
|
|
|
|
function log(...args) {
|
|
internalLogger('background/handle-misc', ...args);
|
|
}
|
|
|
|
|
|
/* message observer */
|
|
|
|
// We cannot start listening of messages of browser.runtime.onMessage(External)
|
|
// at here and wait processing until promises are resolved like ApiTabsListener
|
|
// and BackgroundConnection, because making listeners asynchornous (async
|
|
// functions) will break things - those listeners must not return Promise for
|
|
// unneeded cases.
|
|
// So we simply ignore messages delivered before completely initialized, for now.
|
|
// See also: https://github.com/piroor/treestyletab/issues/2200
|
|
|
|
const PHASE_LOADING = 0;
|
|
const PHASE_BACKGROUND_INITIALIZED = 1;
|
|
const PHASE_BACKGROUND_BUILT = 2;
|
|
const PHASE_BACKGROUND_READY = 3;
|
|
let mInitializationPhase = PHASE_LOADING;
|
|
|
|
Background.onInit.addListener(() => {
|
|
mInitializationPhase = PHASE_BACKGROUND_INITIALIZED;
|
|
});
|
|
|
|
Background.onBuilt.addListener(() => {
|
|
mInitializationPhase = PHASE_BACKGROUND_BUILT;
|
|
});
|
|
|
|
Background.onReady.addListener(() => {
|
|
mInitializationPhase = PHASE_BACKGROUND_READY;
|
|
});
|
|
|
|
if (browser.sidebarAction)
|
|
(browser.action || browser.browserAction)?.onClicked.addListener(onToolbarButtonClick);
|
|
browser.commands.onCommand.addListener(onShortcutCommand);
|
|
browser.runtime.onMessage.addListener(onMessage);
|
|
TSTAPI.onMessageExternal.addListener(onMessageExternal);
|
|
|
|
|
|
Background.onReady.addListener(() => {
|
|
Bookmark.startTracking();
|
|
});
|
|
|
|
Background.onDestroy.addListener(() => {
|
|
browser.runtime.onMessage.removeListener(onMessage);
|
|
TSTAPI.onMessageExternal.removeListener(onMessageExternal);
|
|
if (browser.sidebarAction)
|
|
(browser.action || browser.browserAction)?.onClicked.removeListener(onToolbarButtonClick);
|
|
});
|
|
|
|
|
|
function onToolbarButtonClick(tab) {
|
|
if (mInitializationPhase < PHASE_BACKGROUND_INITIALIZED ||
|
|
Permissions.requestPostProcess()) {
|
|
return;
|
|
}
|
|
|
|
if (Migration.isInitialStartup()) {
|
|
Migration.openInitialStartupPage();
|
|
return;
|
|
}
|
|
|
|
if (typeof browser.sidebarAction.toggle == 'function')
|
|
browser.sidebarAction.toggle();
|
|
else if (SidebarConnection.isSidebarOpen(tab.windowId))
|
|
browser.sidebarAction.close();
|
|
else
|
|
browser.sidebarAction.open();
|
|
}
|
|
|
|
async function onShortcutCommand(command) {
|
|
if (mInitializationPhase < PHASE_BACKGROUND_INITIALIZED)
|
|
return;
|
|
|
|
let activeTabs = command.tab ? [command.tab] : await browser.tabs.query({
|
|
active: true,
|
|
currentWindow: true,
|
|
}).catch(ApiTabs.createErrorHandler());
|
|
if (activeTabs.length == 0)
|
|
activeTabs = await browser.tabs.query({
|
|
currentWindow: true,
|
|
}).catch(ApiTabs.createErrorHandler());
|
|
const activeTab = Tab.get(activeTabs[0].id);
|
|
const selectedTabs = activeTab.$TST.multiselected ? Tab.getSelectedTabs(activeTab.windowId) : [activeTab];
|
|
log('onShortcutCommand ', { command, activeTab, selectedTabs });
|
|
|
|
switch (command) {
|
|
case '_execute_browser_action':
|
|
return;
|
|
|
|
case 'reloadTree':
|
|
Commands.reloadTree(selectedTabs);
|
|
return;
|
|
case 'reloadDescendants':
|
|
Commands.reloadDescendants(selectedTabs);
|
|
return;
|
|
case 'toggleMuteTree':
|
|
Commands.toggleMuteTree(selectedTabs);
|
|
return;
|
|
case 'toggleMuteDescendants':
|
|
Commands.toggleMuteDescendants(selectedTabs);
|
|
return;
|
|
case 'closeTree':
|
|
Commands.closeTree(selectedTabs);
|
|
return;
|
|
case 'closeDescendants':
|
|
Commands.closeDescendants(selectedTabs);
|
|
return;
|
|
case 'closeOthers':
|
|
Commands.closeOthers(selectedTabs);
|
|
return;
|
|
case 'toggleSticky':
|
|
Commands.toggleSticky(selectedTabs);
|
|
return;
|
|
case 'collapseTree':
|
|
Commands.collapseTree(selectedTabs);
|
|
return;
|
|
case 'collapseTreeRecursively':
|
|
Commands.collapseTree(selectedTabs, { recursively: true });
|
|
return;
|
|
case 'collapseAll':
|
|
Commands.collapseAll(activeTab.windowId);
|
|
return;
|
|
case 'expandTree':
|
|
Commands.expandTree(selectedTabs);
|
|
return;
|
|
case 'expandTreeRecursively':
|
|
Commands.expandTree(selectedTabs, { recursively: true });
|
|
return;
|
|
case 'expandAll':
|
|
Commands.expandAll(activeTab.windowId);
|
|
return;
|
|
case 'bookmarkTree':
|
|
Commands.bookmarkTree(selectedTabs);
|
|
return;
|
|
|
|
case 'newIndependentTab':
|
|
Commands.openNewTabAs({
|
|
baseTab: activeTab,
|
|
as: Constants.kNEWTAB_OPEN_AS_ORPHAN
|
|
});
|
|
return;
|
|
case 'newChildTab':
|
|
Commands.openNewTabAs({
|
|
baseTab: activeTab,
|
|
as: Constants.kNEWTAB_OPEN_AS_CHILD
|
|
});
|
|
return;
|
|
case 'newChildTabTop':
|
|
Commands.openNewTabAs({
|
|
baseTab: activeTab,
|
|
as: Constants.kNEWTAB_OPEN_AS_CHILD_TOP
|
|
});
|
|
return;
|
|
case 'newChildTabEnd':
|
|
Commands.openNewTabAs({
|
|
baseTab: activeTab,
|
|
as: Constants.kNEWTAB_OPEN_AS_CHILD_END
|
|
});
|
|
return;
|
|
case 'newSiblingTab':
|
|
Commands.openNewTabAs({
|
|
baseTab: activeTab,
|
|
as: Constants.kNEWTAB_OPEN_AS_SIBLING
|
|
});
|
|
return;
|
|
case 'newNextSiblingTab':
|
|
Commands.openNewTabAs({
|
|
baseTab: activeTab,
|
|
as: Constants.kNEWTAB_OPEN_AS_NEXT_SIBLING
|
|
});
|
|
return;
|
|
|
|
case 'newContainerTab':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SHOW_CONTAINER_SELECTOR,
|
|
windowId: activeTab.windowId
|
|
});
|
|
return;
|
|
|
|
case 'tabMoveUp':
|
|
Commands.moveUp(activeTab, { followChildren: false });
|
|
return;
|
|
case 'treeMoveUp':
|
|
Commands.moveUp(activeTab, { followChildren: true });
|
|
return;
|
|
case 'tabMoveDown':
|
|
Commands.moveDown(activeTab, { followChildren: false });
|
|
return;
|
|
case 'treeMoveDown':
|
|
Commands.moveDown(activeTab, { followChildren: true });
|
|
return;
|
|
|
|
case 'focusPrevious':
|
|
focusPrevious(activeTab);
|
|
return;
|
|
case 'focusPreviousSilently':
|
|
focusPreviousSilently(activeTab);
|
|
return;
|
|
case 'focusNext':
|
|
focusNext(activeTab);
|
|
return;
|
|
case 'focusNextSilently':
|
|
focusNextSilently(activeTab);
|
|
return;
|
|
case 'focusParent':
|
|
TabsInternalOperation.activateTab(activeTab.$TST.parent);
|
|
return;
|
|
case 'focusParentOrCollapse':
|
|
collapseOrFocusToParent(activeTab);
|
|
return;
|
|
case 'focusFirstChild':
|
|
TabsInternalOperation.activateTab(activeTab.$TST.firstChild);
|
|
return;
|
|
case 'focusFirstChildOrExpand':
|
|
expandOrFocusToFirstChild(activeTab);
|
|
return;
|
|
case 'focusLastChild':
|
|
TabsInternalOperation.activateTab(activeTab.$TST.lastChild);
|
|
return;
|
|
case 'focusPreviousSibling':
|
|
TabsInternalOperation.activateTab(
|
|
activeTab.$TST.previousSiblingTab ||
|
|
(activeTab.$TST.parent ?
|
|
activeTab.$TST.parent.$TST.lastChild :
|
|
Tab.getLastRootTab(activeTab.windowId))
|
|
);
|
|
return;
|
|
case 'focusNextSibling':
|
|
TabsInternalOperation.activateTab(
|
|
activeTab.$TST.nextSiblingTab ||
|
|
(activeTab.$TST.parent ?
|
|
activeTab.$TST.parent.$TST.firstChild :
|
|
Tab.getFirstVisibleTab(activeTab.windowId))
|
|
);
|
|
return;
|
|
|
|
case 'simulateUpOnTree':
|
|
if (SidebarConnection.isOpen(activeTab.windowId)) {
|
|
if (configs.faviconizePinnedTabs &&
|
|
(activeTab.pinned ||
|
|
activeTab == Tab.getFirstNormalTab(activeTab.windowId))) {
|
|
const nextActiveId = await browser.runtime.sendMessage({
|
|
type: Constants.kCOMMAND_GET_ABOVE_TAB,
|
|
windowId: activeTab.windowId,
|
|
tabId: activeTab.id,
|
|
});
|
|
log(`simulateUpOnTree: nextActiveId = ${nextActiveId}`);
|
|
const nextActive = (
|
|
Tab.get(nextActiveId) ||
|
|
Tab.getLastVisibleTab(activeTab.windowId)
|
|
);
|
|
TabsInternalOperation.activateTab(nextActive, {
|
|
silently: true,
|
|
});
|
|
}
|
|
else {
|
|
focusPreviousSilently(activeTab);
|
|
}
|
|
}
|
|
else {
|
|
focusPrevious(activeTab);
|
|
}
|
|
return;
|
|
case 'simulateDownOnTree':
|
|
if (SidebarConnection.isOpen(activeTab.windowId)) {
|
|
if (configs.faviconizePinnedTabs &&
|
|
activeTab.pinned) {
|
|
const nextActiveId = await browser.runtime.sendMessage({
|
|
type: Constants.kCOMMAND_GET_BELOW_TAB,
|
|
windowId: activeTab.windowId,
|
|
tabId: activeTab.id,
|
|
});
|
|
log(`simulateDownOnTree: nextActiveId = ${nextActiveId}`);
|
|
const nextActive = (
|
|
Tab.get(nextActiveId) ||
|
|
Tab.getFirstNormalTab(activeTab.windowId)
|
|
);
|
|
TabsInternalOperation.activateTab(nextActive, {
|
|
silently: true,
|
|
});
|
|
}
|
|
else {
|
|
focusNextSilently(activeTab);
|
|
}
|
|
}
|
|
else {
|
|
focusNext(activeTab);
|
|
}
|
|
return;
|
|
case 'simulateLeftOnTree':
|
|
if (SidebarConnection.isOpen(activeTab.windowId)) {
|
|
if (configs.faviconizePinnedTabs &&
|
|
activeTab.pinned) {
|
|
const nextActiveId = await browser.runtime.sendMessage({
|
|
type: Constants.kCOMMAND_GET_LEFT_TAB,
|
|
windowId: activeTab.windowId,
|
|
tabId: activeTab.id,
|
|
});
|
|
log(`simulateLeftOnTree: nextActiveId = ${nextActiveId}`);
|
|
TabsInternalOperation.activateTab(Tab.get(nextActiveId), {
|
|
silently: true,
|
|
});
|
|
}
|
|
else if (await isSidebarRightSide(activeTab.windowId)) {
|
|
expandOrFocusToFirstChild(activeTab);
|
|
}
|
|
else {
|
|
collapseOrFocusToParent(activeTab);
|
|
}
|
|
}
|
|
else {
|
|
focusPrevious(activeTab);
|
|
}
|
|
return;
|
|
case 'simulateRightOnTree':
|
|
if (SidebarConnection.isOpen(activeTab.windowId)) {
|
|
if (configs.faviconizePinnedTabs &&
|
|
activeTab.pinned) {
|
|
const nextActiveId = await browser.runtime.sendMessage({
|
|
type: Constants.kCOMMAND_GET_RIGHT_TAB,
|
|
windowId: activeTab.windowId,
|
|
tabId: activeTab.id,
|
|
});
|
|
log(`simulateRightOnTree: nextActiveId = ${nextActiveId}`);
|
|
TabsInternalOperation.activateTab(Tab.get(nextActiveId), {
|
|
silently: true,
|
|
});
|
|
}
|
|
else if (await isSidebarRightSide(activeTab.windowId)) {
|
|
collapseOrFocusToParent(activeTab);
|
|
}
|
|
else {
|
|
expandOrFocusToFirstChild(activeTab);
|
|
}
|
|
}
|
|
else {
|
|
focusNext(activeTab);
|
|
}
|
|
return;
|
|
|
|
case 'tabbarUp':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SCROLL_TABBAR,
|
|
windowId: activeTab.windowId,
|
|
by: 'lineup'
|
|
});
|
|
return;
|
|
case 'tabbarPageUp':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SCROLL_TABBAR,
|
|
windowId: activeTab.windowId,
|
|
by: 'pageup'
|
|
});
|
|
return;
|
|
case 'tabbarHome':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SCROLL_TABBAR,
|
|
windowId: activeTab.windowId,
|
|
to: 'top'
|
|
});
|
|
return;
|
|
|
|
case 'tabbarDown':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SCROLL_TABBAR,
|
|
windowId: activeTab.windowId,
|
|
by: 'linedown'
|
|
});
|
|
return;
|
|
case 'tabbarPageDown':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SCROLL_TABBAR,
|
|
windowId: activeTab.windowId,
|
|
by: 'pagedown'
|
|
});
|
|
return;
|
|
case 'tabbarEnd':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SCROLL_TABBAR,
|
|
windowId: activeTab.windowId,
|
|
to: 'bottom'
|
|
});
|
|
return;
|
|
|
|
case 'toggleTreeCollapsed':
|
|
if (activeTab.$TST.subtreeCollapsed)
|
|
Commands.expandTree(selectedTabs);
|
|
else
|
|
Commands.collapseTree(selectedTabs);
|
|
return;
|
|
case 'toggleTreeCollapsedRecursively':
|
|
if (activeTab.$TST.subtreeCollapsed)
|
|
Commands.expandTree(selectedTabs, { recursively: true });
|
|
else
|
|
Commands.collapseTree(selectedTabs, { recursively: true });
|
|
return;
|
|
|
|
case 'toggleSubPanel':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_TOGGLE_SUBPANEL,
|
|
windowId: activeTab.windowId
|
|
});
|
|
return;
|
|
case 'switchSubPanel':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_SWITCH_SUBPANEL,
|
|
windowId: activeTab.windowId
|
|
});
|
|
return;
|
|
case 'increaseSubPanel':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_INCREASE_SUBPANEL,
|
|
windowId: activeTab.windowId
|
|
});
|
|
return;
|
|
case 'decreaseSubPanel':
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kCOMMAND_DECREASE_SUBPANEL,
|
|
windowId: activeTab.windowId
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
function focusNext(activeTab) {
|
|
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,
|
|
});
|
|
}
|
|
|
|
async function isSidebarRightSide(windowId) {
|
|
const position = await browser.runtime.sendMessage({
|
|
type: Constants.kCOMMAND_GET_SIDEBAR_POSITION,
|
|
windowId,
|
|
});
|
|
return position == Constants.kTABBAR_POSITION_RIGHT;
|
|
}
|
|
|
|
function collapseOrFocusToParent(activeTab) {
|
|
if (!activeTab.$TST.subtreeCollapsed && activeTab.$TST.hasChild)
|
|
Commands.collapseTree(activeTab);
|
|
else
|
|
TabsInternalOperation.activateTab(activeTab.$TST.parent);
|
|
}
|
|
|
|
function expandOrFocusToFirstChild(activeTab) {
|
|
if (activeTab.$TST.subtreeCollapsed && activeTab.$TST.hasChild)
|
|
Commands.expandTree(activeTab);
|
|
else
|
|
TabsInternalOperation.activateTab(activeTab.$TST.firstChild, {
|
|
silently: true,
|
|
});
|
|
}
|
|
|
|
// This must be synchronous and return Promise on demando, to avoid
|
|
// blocking to other listeners.
|
|
function onMessage(message, sender) {
|
|
if (mInitializationPhase < PHASE_BACKGROUND_BUILT ||
|
|
!message ||
|
|
typeof message.type != 'string' ||
|
|
message.type.indexOf('ws:') != 0)
|
|
return;
|
|
|
|
//log('onMessage: ', message, sender);
|
|
switch (message.type) {
|
|
case Constants.kCOMMAND_GET_INSTANCE_ID:
|
|
return Promise.resolve(Background.instanceId);
|
|
|
|
case Constants.kCOMMAND_RELOAD:
|
|
return Background.reload({ all: message.all });
|
|
|
|
case Constants.kCOMMAND_REQUEST_UNIQUE_ID:
|
|
return (async () => {
|
|
if (!Tab.get(message.tabId))
|
|
await Tab.waitUntilTracked(message.tabId);
|
|
const tab = Tab.get(message.tabId);
|
|
return tab ? tab.$TST.promisedUniqueId : {};
|
|
})();
|
|
|
|
case Constants.kCOMMAND_GET_THEME_DECLARATIONS:
|
|
return browser.theme.getCurrent().then(theme => BrowserTheme.generateThemeDeclarations(theme));
|
|
|
|
case Constants.kCOMMAND_GET_CONTEXTUAL_IDENTITIES_COLOR_INFO:
|
|
return Promise.resolve(ContextualIdentities.getColorInfo());
|
|
|
|
case Constants.kCOMMAND_GET_CONFIG_VALUE:
|
|
if (Array.isArray(message.keys)) {
|
|
const values = {};
|
|
for (const key of message.keys) {
|
|
values[key] = configs[key];
|
|
}
|
|
return Promise.resolve(values);
|
|
}
|
|
return Promise.resolve(configs[message.key]);
|
|
|
|
case Constants.kCOMMAND_SET_CONFIG_VALUE:
|
|
return Promise.resolve(configs[message.key] = message.value);
|
|
|
|
case Constants.kCOMMAND_GET_USER_STYLE_RULES:
|
|
return Promise.resolve(loadUserStyleRules());
|
|
|
|
case Constants.kCOMMAND_PING_TO_BACKGROUND: // return tabs as the pong, to optimizie further initialization tasks in the sidebar
|
|
TabsUpdate.completeLoadingTabs(message.windowId); // don't wait here for better perfomance
|
|
return Promise.resolve(TabsStore.windows.get(message.windowId).export(true));
|
|
|
|
case Constants.kCOMMAND_PULL_TABS:
|
|
if (message.windowId) {
|
|
TabsUpdate.completeLoadingTabs(message.windowId); // don't wait here for better perfomance
|
|
return Promise.resolve(TabsStore.windows.get(message.windowId).export(true).tabs);
|
|
}
|
|
return Promise.resolve(message.tabIds.map(id => {
|
|
const tab = Tab.get(id);
|
|
return tab?.$TST.export(true);
|
|
}));
|
|
|
|
case Constants.kCOMMAND_PULL_TABS_ORDER:
|
|
return Promise.resolve(TabsStore.windows.get(message.windowId).order);
|
|
|
|
case Constants.kCOMMAND_PULL_TREE_STRUCTURE:
|
|
return (async () => {
|
|
while (mInitializationPhase < PHASE_BACKGROUND_READY) {
|
|
await wait(10);
|
|
}
|
|
const structure = TreeBehavior.getTreeStructureFromTabs(
|
|
message.windowId ?
|
|
Tab.getAllTabs(message.windowId) :
|
|
message.tabIds.map(id => Tab.get(id))
|
|
);
|
|
return { structure };
|
|
})();
|
|
|
|
case Constants.kCOMMAND_NOTIFY_PERMISSIONS_GRANTED:
|
|
return (async () => {
|
|
const grantedPermission = JSON.stringify(message.permissions);
|
|
if (grantedPermission == JSON.stringify(Permissions.ALL_URLS)) {
|
|
const tabs = await browser.tabs.query({}).catch(ApiTabs.createErrorHandler());
|
|
await Tab.waitUntilTracked(tabs.map(tab => tab.id));
|
|
for (const tab of tabs) {
|
|
Background.tryStartHandleAccelKeyOnTab(Tab.get(tab.id));
|
|
}
|
|
}
|
|
else if (grantedPermission == JSON.stringify(Permissions.BOOKMARKS)) {
|
|
Migration.migrateBookmarkUrls();
|
|
Bookmark.startTracking();
|
|
}
|
|
})();
|
|
|
|
case Constants.kCOMMAND_SIMULATE_SIDEBAR_MESSAGE: {
|
|
SidebarConnection.onMessage.dispatch(message.message.windowId, message.message);
|
|
}; break;
|
|
|
|
case Constants.kCOMMAND_CONFIRM_TO_CLOSE_TABS:
|
|
log('kCOMMAND_CONFIRM_TO_CLOSE_TABS: ', { message });
|
|
return Background.confirmToCloseTabs(message.tabs, message);
|
|
|
|
default:
|
|
if (TSTAPI.INTERNAL_CALL_PREFIX_MATCHER.test(message.type)) {
|
|
return onMessageExternal({
|
|
...message,
|
|
type: message.type.replace(TSTAPI.INTERNAL_CALL_PREFIX_MATCHER, ''),
|
|
}, sender);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This must be synchronous and return Promise on demando, to avoid
|
|
// blocking to other listeners.
|
|
function onMessageExternal(message, sender) {
|
|
if (mInitializationPhase < PHASE_BACKGROUND_INITIALIZED)
|
|
return;
|
|
|
|
switch (message.type) {
|
|
case TSTAPI.kGET_VERSION:
|
|
return Promise.resolve(browser.runtime.getManifest().version);
|
|
|
|
case TSTAPI.kGET_TREE:
|
|
return (async () => {
|
|
const tabs = await (message.rendered ?
|
|
TSTAPI.getTargetRenderedTabs(message, sender) :
|
|
TSTAPI.getTargetTabs(message, sender));
|
|
const cache = {};
|
|
const treeItems = Array.from(tabs, tab => TSTAPI.exportTab(tab, {
|
|
interval: message.interval,
|
|
cache,
|
|
}));
|
|
const result = TSTAPI.formatTabResult(
|
|
treeItems,
|
|
{
|
|
...message,
|
|
// This must return an array of root tabs if just the window id is specified.
|
|
// See also: https://github.com/piroor/treestyletab/issues/2763
|
|
...((message.window || message.windowId) && !message.tab && !message.tabs ? { tab: '*' } : {})
|
|
},
|
|
sender.id
|
|
);
|
|
TSTAPI.clearCache(cache);
|
|
return result;
|
|
})();
|
|
|
|
case TSTAPI.kGET_LIGHT_TREE:
|
|
return (async () => {
|
|
const tabs = await (message.rendered ?
|
|
TSTAPI.getTargetRenderedTabs(message, sender) :
|
|
TSTAPI.getTargetTabs(message, sender));
|
|
const cache = {};
|
|
const treeItems = Array.from(tabs, tab => TSTAPI.exportTab(tab, {
|
|
light: true,
|
|
interval: message.interval,
|
|
cache,
|
|
}));
|
|
const result = TSTAPI.formatTabResult(
|
|
treeItems,
|
|
{
|
|
...message,
|
|
// This must return an array of root tabs if just the window id is specified.
|
|
// See also: https://github.com/piroor/treestyletab/issues/2763
|
|
...((message.window || message.windowId) && !message.tab && !message.tabs ? { tab: '*' } : {})
|
|
},
|
|
sender.id
|
|
);
|
|
TSTAPI.clearCache(cache);
|
|
return result;
|
|
})();
|
|
|
|
case TSTAPI.kSTICK_TAB:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await doProgressively(
|
|
tabs,
|
|
tab => Commands.toggleSticky(tab, true),
|
|
message.interval
|
|
);
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kUNSTICK_TAB:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await doProgressively(
|
|
tabs,
|
|
tab => Commands.toggleSticky(tab, false),
|
|
message.interval
|
|
);
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kTOGGLE_STICKY_STATE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
let firstTabIsSticky = undefined;
|
|
await doProgressively(
|
|
tabs,
|
|
tab => {
|
|
if (firstTabIsSticky === undefined)
|
|
firstTabIsSticky = tab.$TST.sticky;
|
|
Commands.toggleSticky(tab, !firstTabIsSticky);
|
|
},
|
|
message.interval
|
|
);
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kCOLLAPSE_TREE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await doProgressively(
|
|
tabs,
|
|
tab => Commands.collapseTree(tab, {
|
|
recursively: !!message.recursively
|
|
}),
|
|
message.interval
|
|
);
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kEXPAND_TREE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await doProgressively(
|
|
tabs,
|
|
tab => Commands.expandTree(tab, {
|
|
recursively: !!message.recursively
|
|
}),
|
|
message.interval
|
|
);
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kTOGGLE_TREE_COLLAPSED:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await doProgressively(
|
|
tabs,
|
|
tab => Tree.collapseExpandSubtree(tab, {
|
|
collapsed: !tab.$TST.subtreeCollapsed,
|
|
broadcast: true
|
|
}),
|
|
message.interval
|
|
);
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kATTACH:
|
|
return (async () => {
|
|
await Tab.waitUntilTracked([
|
|
message.child,
|
|
message.parent,
|
|
message.insertBefore,
|
|
message.insertAfter
|
|
]);
|
|
const child = Tab.get(message.child);
|
|
const parent = Tab.get(message.parent);
|
|
if (!child ||
|
|
!parent ||
|
|
child.windowId != parent.windowId)
|
|
return false;
|
|
await Tree.attachTabTo(child, parent, {
|
|
broadcast: true,
|
|
insertBefore: Tab.get(message.insertBefore),
|
|
insertAfter: Tab.get(message.insertAfter)
|
|
});
|
|
if (child.$TST.collapsed &&
|
|
!parent.$TST.collapsed &&
|
|
!parent.$TST.subtreeCollapsed) {
|
|
await Tree.collapseExpandTabAndSubtree(child, {
|
|
collapsed: false,
|
|
bradcast: true
|
|
});
|
|
}
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kDETACH:
|
|
return (async () => {
|
|
await Tab.waitUntilTracked(message.tab);
|
|
const tab = Tab.get(message.tab);
|
|
if (!tab)
|
|
return false;
|
|
await Tree.detachTab(tab, {
|
|
broadcast: true
|
|
});
|
|
if (tab.$TST.collapsed) {
|
|
await Tree.collapseExpandTabAndSubtree(tab, {
|
|
collapsed: false,
|
|
bradcast: true
|
|
});
|
|
}
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kINDENT:
|
|
case TSTAPI.kDEMOTE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const results = await doProgressively(
|
|
tabs,
|
|
tab => Commands.indent(tab, message),
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(results, message);
|
|
})();
|
|
|
|
case TSTAPI.kOUTDENT:
|
|
case TSTAPI.kPROMOTE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const results = await doProgressively(
|
|
tabs,
|
|
tab => Commands.outdent(tab, message),
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(results, message);
|
|
})();
|
|
|
|
case TSTAPI.kMOVE_UP:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const results = await doProgressively(
|
|
tabs,
|
|
tab => Commands.moveUp(tab, message),
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(results, message);
|
|
})();
|
|
|
|
case TSTAPI.kMOVE_TO_START:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await Commands.moveTabsToStart(Array.from(tabs));
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kMOVE_DOWN:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const results = await doProgressively(
|
|
tabs,
|
|
tab => Commands.moveDown(tab, message),
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(results, message);
|
|
})();
|
|
|
|
case TSTAPI.kMOVE_TO_END:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await Commands.moveTabsToEnd(Array.from(tabs));
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kMOVE_BEFORE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const results = await doProgressively(
|
|
tabs,
|
|
tab => Commands.moveBefore(tab, message),
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(results, message);
|
|
})();
|
|
|
|
case TSTAPI.kMOVE_AFTER:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const results = await doProgressively(
|
|
tabs,
|
|
tab => Commands.moveAfter(tab, message),
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(results, message);
|
|
})();
|
|
|
|
case TSTAPI.kFOCUS:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const tabsArray = await doProgressively(
|
|
tabs,
|
|
tab => {
|
|
TabsInternalOperation.activateTab(tab, {
|
|
silently: message.silently
|
|
});
|
|
return tab;
|
|
},
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(tabsArray.map(() => true), message);
|
|
})();
|
|
|
|
case TSTAPI.kCREATE:
|
|
return (async () => {
|
|
const windowId = message.params.windowId;
|
|
const win = TabsStore.windows.get(windowId);
|
|
if (!win)
|
|
throw new Error(`invalid windowId ${windowId}: it must be valid window id`);
|
|
win.bypassTabControlCount++;
|
|
const tab = await TabsOpen.openURIInTab(message.params, { windowId });
|
|
return TSTAPI.exportTab(tab, { addonId: sender.id });
|
|
})();
|
|
|
|
case TSTAPI.kDUPLICATE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
let behavior = configs.autoAttachOnDuplicated;
|
|
switch (String(message.as || 'sibling').toLowerCase()) {
|
|
case 'child':
|
|
behavior = behavior == Constants.kNEWTAB_OPEN_AS_CHILD_TOP ?
|
|
Constants.kNEWTAB_OPEN_AS_CHILD_TOP :
|
|
behavior == Constants.kNEWTAB_OPEN_AS_CHILD_END ?
|
|
Constants.kNEWTAB_OPEN_AS_CHILD_END :
|
|
Constants.kNEWTAB_OPEN_AS_CHILD;
|
|
break;
|
|
case 'first-child':
|
|
behavior = Constants.kNEWTAB_OPEN_AS_CHILD_TOP;
|
|
break;
|
|
case 'last-child':
|
|
behavior = Constants.kNEWTAB_OPEN_AS_CHILD_END;
|
|
break;
|
|
case 'sibling':
|
|
behavior = Constants.kNEWTAB_OPEN_AS_SIBLING;
|
|
break;
|
|
case 'nextsibling':
|
|
behavior = Constants.kNEWTAB_OPEN_AS_NEXT_SIBLING;
|
|
break;
|
|
case 'orphan':
|
|
behavior = Constants.kNEWTAB_OPEN_AS_ORPHAN;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
const tabsArray = await doProgressively(
|
|
tabs,
|
|
async tab => {
|
|
return Commands.duplicateTab(tab, {
|
|
destinationWindowId: tab.windowId,
|
|
behavior,
|
|
multiselected: false
|
|
});
|
|
},
|
|
message.interval
|
|
);
|
|
return TSTAPI.formatResult(tabsArray.map(() => true), message);
|
|
})();
|
|
|
|
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;
|
|
return TSTAPI.exportTab(tab, { addonId: sender.id });
|
|
})();
|
|
|
|
case TSTAPI.kOPEN_IN_NEW_WINDOW:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const windowId = await Commands.openTabsInWindow(Array.from(tabs), {
|
|
multiselected: false
|
|
});
|
|
return windowId;
|
|
})();
|
|
|
|
case TSTAPI.kREOPEN_IN_CONTAINER:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
const reopenedTabs = await Commands.reopenInContainer(
|
|
Array.from(tabs),
|
|
message.containerId || 'firefox-default'
|
|
);
|
|
const cache = {};
|
|
const result = await TSTAPI.formatTabResult(
|
|
reopenedTabs.map(tab => TSTAPI.exportTab(tab, {
|
|
interval: message.interval,
|
|
addonId: sender.id,
|
|
cache,
|
|
})),
|
|
message
|
|
);
|
|
TSTAPI.clearCache(cache);
|
|
return result;
|
|
})();
|
|
|
|
case TSTAPI.kGET_TREE_STRUCTURE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
return Promise.resolve(TreeBehavior.getTreeStructureFromTabs(Array.from(tabs)));
|
|
})();
|
|
|
|
case TSTAPI.kSET_TREE_STRUCTURE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
await Tree.applyTreeStructureToTabs(
|
|
Array.from(tabs),
|
|
message.structure,
|
|
{ broadcast: true }
|
|
);
|
|
return Promise.resolve(true);
|
|
})();
|
|
|
|
case TSTAPI.kADD_TAB_STATE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
let states = message.state || message.states;
|
|
if (!Array.isArray(states))
|
|
states = [states];
|
|
states = mapAndFilterUniq(states, state => state && String(state) || undefined);
|
|
if (states.length == 0)
|
|
return true;
|
|
const tabsArray = await doProgressively(
|
|
tabs,
|
|
tab => {
|
|
for (const state of states) {
|
|
tab.$TST.addState(state);
|
|
}
|
|
return tab;
|
|
},
|
|
message.interval
|
|
);
|
|
Tab.broadcastState(tabsArray, {
|
|
add: states
|
|
});
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kREMOVE_TAB_STATE:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
let states = message.state || message.states;
|
|
if (!Array.isArray(states))
|
|
states = [states];
|
|
states = mapAndFilterUniq(states, state => state && String(state) || undefined);
|
|
if (states.length == 0)
|
|
return true;
|
|
const tabsArray = await doProgressively(
|
|
tabs,
|
|
tab => {
|
|
for (const state of states) {
|
|
tab.$TST.removeState(state);
|
|
}
|
|
return tab;
|
|
},
|
|
message.interval
|
|
);
|
|
Tab.broadcastState(tabsArray, {
|
|
remove: states
|
|
});
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kGRANT_TO_REMOVE_TABS:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
configs.grantedRemovingTabIds = mapAndFilterUniq(configs.grantedRemovingTabIds.concat(tabs), tab => {
|
|
tab = TabsStore.ensureLivingItem(tab);
|
|
return tab?.id || undefined;
|
|
});
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kSTART_CUSTOM_DRAG:
|
|
return (async () => {
|
|
SidebarConnection.sendMessage({
|
|
type: Constants.kNOTIFY_TAB_MOUSEDOWN_EXPIRED,
|
|
windowId: message.windowId || (await browser.windows.getLastFocused({ populate: false }).catch(ApiTabs.createErrorHandler())).id,
|
|
button: message.button || 0
|
|
});
|
|
})();
|
|
|
|
case TSTAPI.kOPEN_ALL_BOOKMARKS_WITH_STRUCTURE:
|
|
return Commands.openAllBookmarksWithStructure(
|
|
message.id || message.bookmarkId,
|
|
{ discarded: message.discarded }
|
|
);
|
|
|
|
case TSTAPI.kSET_TOOLTIP_TEXT:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
for (const tab of tabs) {
|
|
tab.$TST.registerTooltipText(sender.id, message.text || '', !!message.force);
|
|
}
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kCLEAR_TOOLTIP_TEXT:
|
|
return (async () => {
|
|
const tabs = await TSTAPI.getTargetTabs(message, sender);
|
|
for (const tab of tabs) {
|
|
tab.$TST.unregisterTooltipText(sender.id);
|
|
}
|
|
return true;
|
|
})();
|
|
|
|
case TSTAPI.kREGISTER_AUTO_STICKY_STATES:
|
|
TreeItem.registerAutoStickyState(sender.id, message.state || message.states);
|
|
break;
|
|
|
|
case TSTAPI.kUNREGISTER_AUTO_STICKY_STATES:
|
|
TreeItem.unregisterAutoStickyState(sender.id, message.state || message.states);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Tab.onStateChanged.addListener((tab, state, added) => {
|
|
switch (state) {
|
|
case Constants.kTAB_STATE_STICKY:
|
|
if (TSTAPI.hasListenerForMessageType(TSTAPI.kNOTIFY_TAB_STICKY_STATE_CHANGED)) {
|
|
TSTAPI.broadcastMessage({
|
|
type: TSTAPI.kNOTIFY_TAB_STICKY_STATE_CHANGED,
|
|
tab,
|
|
sticky: !!added,
|
|
}, { tabProperties: ['tab'] }).catch(_error => {});
|
|
}
|
|
break;
|
|
}
|
|
});
|