599 lines
23 KiB
JavaScript
599 lines
23 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,
|
|
dumpTab,
|
|
configs,
|
|
isFirefoxViewTab,
|
|
} from '/common/common.js';
|
|
import * as Constants from '/common/constants.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';
|
|
import * as TSTAPI from '/common/tst-api.js';
|
|
|
|
import { Tab } from '/common/TreeItem.js';
|
|
|
|
import * as TabsMove from './tabs-move.js';
|
|
import * as TabsOpen from './tabs-open.js';
|
|
import * as Tree from './tree.js';
|
|
|
|
function log(...args) {
|
|
internalLogger('background/handle-new-tabs', ...args);
|
|
}
|
|
|
|
|
|
Tab.onBeforeCreate.addListener(async (tab, info) => {
|
|
const activeTab = info.activeTab || Tab.getActiveTab(tab.windowId);
|
|
|
|
// Special case, when all these conditions are true:
|
|
// 1) A new blank tab is configured to be opened as a child of the active tab.
|
|
// 2) The active tab is pinned.
|
|
// 3) Tabs opened from a pinned parent are configured to be placed near the
|
|
// opener pinned tab.
|
|
// then we fakely attach the new blank tab to the active pinned tab.
|
|
// See also https://github.com/piroor/treestyletab/issues/3296
|
|
const shouldAttachToPinnedOpener = (
|
|
!tab.openerTabId &&
|
|
!tab.pinned &&
|
|
tab.$TST.isNewTabCommandTab &&
|
|
Constants.kCONTROLLED_NEWTAB_POSITION.has(configs.autoAttachOnNewTabCommand) &&
|
|
(
|
|
(activeTab?.pinned &&
|
|
Constants.kCONTROLLED_INSERTION_POSITION.has(configs.insertNewTabFromPinnedTabAt)) ||
|
|
(isFirefoxViewTab(activeTab) &&
|
|
Constants.kCONTROLLED_INSERTION_POSITION.has(configs.insertNewTabFromFirefoxViewAt))
|
|
)
|
|
);
|
|
if (shouldAttachToPinnedOpener)
|
|
tab.openerTabId = activeTab.id;
|
|
});
|
|
|
|
// this should return false if the tab is / may be moved while processing
|
|
Tab.onCreating.addListener((tab, info = {}) => {
|
|
if (info.duplicatedInternally)
|
|
return true;
|
|
|
|
log('Tabs.onCreating ', dumpTab(tab), tab.openerTabId, info);
|
|
|
|
const activeTab = info.activeTab || Tab.getActiveTab(tab.windowId);
|
|
const opener = tab.$TST.openerTab;
|
|
if (opener) {
|
|
tab.$TST.setAttribute(Constants.kPERSISTENT_ORIGINAL_OPENER_TAB_ID, opener.$TST.uniqueId.id);
|
|
if (!info.bypassTabControl)
|
|
TabsStore.addToBeGroupedTab(tab);
|
|
}
|
|
else {
|
|
let dontMove = false;
|
|
if (!info.maybeOrphan &&
|
|
!info.bypassTabControl &&
|
|
activeTab &&
|
|
!info.restored) {
|
|
let autoAttachBehavior = configs.autoAttachOnNewTabCommand;
|
|
if (tab.$TST.nextTab &&
|
|
activeTab == tab.$TST.previousTab) {
|
|
// New tab opened with browser.tabs.insertAfterCurrent=true may have
|
|
// next tab. In this case the tab is expected to be placed next to the
|
|
// active tab always, so we should change the behavior specially.
|
|
// See also:
|
|
// https://github.com/piroor/treestyletab/issues/2054
|
|
// https://github.com/piroor/treestyletab/issues/2194#issuecomment-505272940
|
|
dontMove = true;
|
|
switch (autoAttachBehavior) {
|
|
case Constants.kNEWTAB_OPEN_AS_ORPHAN:
|
|
case Constants.kNEWTAB_OPEN_AS_SIBLING:
|
|
case Constants.kNEWTAB_OPEN_AS_NEXT_SIBLING:
|
|
if (activeTab.$TST.hasChild)
|
|
autoAttachBehavior = Constants.kNEWTAB_OPEN_AS_CHILD;
|
|
else
|
|
autoAttachBehavior = Constants.kNEWTAB_OPEN_AS_NEXT_SIBLING;
|
|
break;
|
|
|
|
case Constants.kNEWTAB_OPEN_AS_CHILD:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (tab.$TST.isNewTabCommandTab) {
|
|
if (!info.positionedBySelf) {
|
|
log('behave as a tab opened by new tab command');
|
|
return handleNewTabFromActiveTab(tab, {
|
|
activeTab,
|
|
autoAttachBehavior,
|
|
dontMove,
|
|
openedWithCookieStoreId: info.openedWithCookieStoreId,
|
|
inheritContextualIdentityMode: configs.inheritContextualIdentityToChildTabMode,
|
|
context: TSTAPI.kNEWTAB_CONTEXT_NEWTAB_COMMAND,
|
|
}).then(moved => !moved);
|
|
}
|
|
return false;
|
|
}
|
|
else if (activeTab != tab) {
|
|
tab.$TST.temporaryMetadata.set('possibleOpenerTab', activeTab.id);
|
|
}
|
|
if (!info.fromExternal)
|
|
tab.$TST.temporaryMetadata.set('isNewTab', true);
|
|
}
|
|
if (info.fromExternal &&
|
|
!info.bypassTabControl) {
|
|
log('behave as a tab opened from external application');
|
|
// we may need to reopen the tab with loaded URL
|
|
if (configs.inheritContextualIdentityToTabsFromExternalMode != Constants.kCONTEXTUAL_IDENTITY_DEFAULT)
|
|
tab.$TST.temporaryMetadata.set('fromExternal', true);
|
|
return notifyToTryHandleNewTab(tab, {
|
|
context: TSTAPI.kNEWTAB_CONTEXT_FROM_EXTERNAL,
|
|
activeTab,
|
|
}).then(allowed => {
|
|
if (!allowed) {
|
|
log(' => handling is canceled by someone');
|
|
return true;
|
|
}
|
|
return Tree.behaveAutoAttachedTab(tab, {
|
|
baseTab: activeTab,
|
|
behavior: configs.autoAttachOnOpenedFromExternal,
|
|
dontMove,
|
|
broadcast: true
|
|
}).then(moved => !moved);
|
|
});
|
|
}
|
|
log('behave as a tab opened with any URL ', tab.title, tab.url);
|
|
if (!info.restored &&
|
|
!info.positionedBySelf &&
|
|
!info.bypassTabControl &&
|
|
configs.autoAttachOnAnyOtherTrigger != Constants.kNEWTAB_DO_NOTHING) {
|
|
if (configs.inheritContextualIdentityToTabsFromAnyOtherTriggerMode != Constants.kCONTEXTUAL_IDENTITY_DEFAULT)
|
|
tab.$TST.temporaryMetadata.set('anyOtherTrigger', true);
|
|
log('controlled as a new tab from other unknown trigger');
|
|
return notifyToTryHandleNewTab(tab, {
|
|
context: TSTAPI.kNEWTAB_CONTEXT_UNKNOWN,
|
|
activeTab,
|
|
}).then(allowed => {
|
|
if (!allowed) {
|
|
log(' => handling is canceled by someone');
|
|
return true;
|
|
}
|
|
return Tree.behaveAutoAttachedTab(tab, {
|
|
baseTab: activeTab,
|
|
behavior: configs.autoAttachOnAnyOtherTrigger,
|
|
dontMove,
|
|
broadcast: true
|
|
}).then(moved => !moved);
|
|
});
|
|
}
|
|
if (info.positionedBySelf)
|
|
tab.$TST.temporaryMetadata.set('positionedBySelf', true);
|
|
return true;
|
|
}
|
|
|
|
log(`opener: ${dumpTab(opener)}, positionedBySelf = ${info.positionedBySelf}`);
|
|
if (!info.bypassTabControl &&
|
|
opener &&
|
|
(opener.pinned || isFirefoxViewTab(opener)) &&
|
|
opener.windowId == tab.windowId) {
|
|
return handleTabsFromPinnedOpener(tab, opener, { activeTab }).then(moved => !moved);
|
|
}
|
|
else if (!info.maybeOrphan || info.bypassTabControl) {
|
|
if (info.fromExternal &&
|
|
!info.bypassTabControl &&
|
|
configs.inheritContextualIdentityToTabsFromExternalMode != Constants.kCONTEXTUAL_IDENTITY_DEFAULT)
|
|
tab.$TST.temporaryMetadata.set('fromExternal', true);
|
|
return notifyToTryHandleNewTab(tab, {
|
|
context: info.fromExternal && !info.bypassTabControl ?
|
|
TSTAPI.kNEWTAB_CONTEXT_FROM_EXTERNAL :
|
|
info.duplicated ?
|
|
TSTAPI.kNEWTAB_CONTEXT_DUPLICATED :
|
|
TSTAPI.kNEWTAB_CONTEXT_WITH_OPENER,
|
|
activeTab,
|
|
openerTab: opener,
|
|
}).then(allowed => {
|
|
if (!allowed) {
|
|
log(' => handling is canceled by someone');
|
|
return true;
|
|
}
|
|
const behavior = info.fromExternal && !info.bypassTabControl ?
|
|
configs.autoAttachOnOpenedFromExternal :
|
|
info.duplicated ?
|
|
configs.autoAttachOnDuplicated :
|
|
configs.autoAttachOnOpenedWithOwner;
|
|
return Tree.behaveAutoAttachedTab(tab, {
|
|
baseTab: opener,
|
|
behavior,
|
|
dontMove: info.positionedBySelf || info.mayBeReplacedWithContainer,
|
|
broadcast: true
|
|
}).then(moved => !moved);
|
|
});
|
|
}
|
|
return true;
|
|
});
|
|
|
|
async function notifyToTryHandleNewTab(tab, { context, activeTab, openerTab } = {}) {
|
|
const cache = {};
|
|
const result = TSTAPI.tryOperationAllowed(
|
|
TSTAPI.kNOTIFY_TRY_HANDLE_NEWTAB,
|
|
{ tab,
|
|
activeTab,
|
|
openerTab,
|
|
context },
|
|
{ tabProperties: ['tab', 'activeTab', 'openerTab'], cache }
|
|
);
|
|
TSTAPI.clearCache(cache);
|
|
return result;
|
|
}
|
|
|
|
async function handleNewTabFromActiveTab(tab, { url, activeTab, autoAttachBehavior, dontMove, openedWithCookieStoreId, inheritContextualIdentityMode, context } = {}) {
|
|
log('handleNewTabFromActiveTab: activeTab = ', dumpTab(activeTab), { url, activeTab, autoAttachBehavior, dontMove, inheritContextualIdentityMode, context });
|
|
if (activeTab &&
|
|
activeTab.$TST.ancestors.includes(tab)) {
|
|
log(' => ignore restored ancestor tab');
|
|
return false;
|
|
}
|
|
|
|
const allowed = await notifyToTryHandleNewTab(tab, {
|
|
context,
|
|
activeTab,
|
|
});
|
|
if (!allowed) {
|
|
log(' => handling is canceled by someone');
|
|
return false;
|
|
}
|
|
|
|
const moved = await Tree.behaveAutoAttachedTab(tab, {
|
|
baseTab: activeTab,
|
|
behavior: autoAttachBehavior,
|
|
broadcast: true,
|
|
dontMove: dontMove || false
|
|
});
|
|
if (openedWithCookieStoreId) {
|
|
log('handleNewTabFromActiveTab: do not reopen tab opened with contextual identity explicitly');
|
|
return moved;
|
|
}
|
|
if (tab.cookieStoreId && tab.cookieStoreId != 'firefox-default') {
|
|
log('handleNewTabFromActiveTab: do not reopen tab opened with non-default contextual identity ', tab.cookieStoreId);
|
|
return moved;
|
|
}
|
|
|
|
const parent = tab.$TST.parent;
|
|
let cookieStoreId = null;
|
|
switch (inheritContextualIdentityMode) {
|
|
case Constants.kCONTEXTUAL_IDENTITY_FROM_PARENT:
|
|
if (parent)
|
|
cookieStoreId = parent.cookieStoreId;
|
|
break;
|
|
|
|
case Constants.kCONTEXTUAL_IDENTITY_FROM_LAST_ACTIVE:
|
|
cookieStoreId = activeTab.cookieStoreId
|
|
break;
|
|
|
|
default:
|
|
return moved;
|
|
}
|
|
if ((tab.cookieStoreId || 'firefox-default') == (cookieStoreId || 'firefox-default')) {
|
|
log('handleNewTabFromActiveTab: no need to reopen with inherited contextual identity ', cookieStoreId);
|
|
return moved;
|
|
}
|
|
|
|
if (!configs.inheritContextualIdentityToUnopenableURLTabs &&
|
|
!TabsOpen.isOpenable(url)) {
|
|
log('handleNewTabFromActiveTab: not openable URL, skip reopening ', cookieStoreId, url);
|
|
return moved;
|
|
}
|
|
|
|
log('handleNewTabFromActiveTab: reopen with inherited contextual identity ', cookieStoreId);
|
|
// We need to prevent grouping of this original tab and the reopened tab
|
|
// by the "multiple tab opened in XXX msec" feature.
|
|
const win = TabsStore.windows.get(tab.windowId);
|
|
win.openedNewTabs.delete(tab.id);
|
|
await TabsOpen.openURIInTab(url || null, {
|
|
windowId: activeTab.windowId,
|
|
parent,
|
|
insertBefore: tab,
|
|
active: tab.active,
|
|
cookieStoreId
|
|
});
|
|
TabsInternalOperation.removeTab(tab);
|
|
return moved;
|
|
}
|
|
|
|
async function handleTabsFromPinnedOpener(tab, opener, { activeTab } = {}) {
|
|
const allowed = await notifyToTryHandleNewTab(tab, {
|
|
context: TSTAPI.kNEWTAB_CONTEXT_FROM_PINNED,
|
|
activeTab,
|
|
openerTab: opener,
|
|
});
|
|
if (!allowed) {
|
|
log('handleTabsFromPinnedOpener: handling is canceled by someone');
|
|
return false;
|
|
}
|
|
|
|
const parent = Tab.getGroupTabForOpener(opener);
|
|
if (parent) {
|
|
log('handleTabsFromPinnedOpener: attach to corresponding group tab');
|
|
tab.$TST.setAttribute(Constants.kPERSISTENT_ALREADY_GROUPED_FOR_PINNED_OPENER, true);
|
|
tab.$TST.temporaryMetadata.set('alreadyMovedAsOpenedFromPinnedOpener', true);
|
|
// it could be updated already...
|
|
const lastRelatedTab = opener.$TST.lastRelatedTabId == tab.id ?
|
|
opener.$TST.previousLastRelatedTab :
|
|
opener.$TST.lastRelatedTab;
|
|
// If there is already opened group tab, it is more natural that
|
|
// opened tabs are treated as a tab opened from unpinned tabs.
|
|
const insertAt = configs.autoAttachOnOpenedWithOwner == Constants.kNEWTAB_OPEN_AS_CHILD_NEXT_TO_LAST_RELATED_TAB ?
|
|
Constants.kINSERT_NEXT_TO_LAST_RELATED_TAB :
|
|
configs.autoAttachOnOpenedWithOwner == Constants.kNEWTAB_OPEN_AS_CHILD_TOP ?
|
|
Constants.kINSERT_TOP :
|
|
configs.autoAttachOnOpenedWithOwner == Constants.kNEWTAB_OPEN_AS_CHILD_END ?
|
|
Constants.kINSERT_END :
|
|
undefined;
|
|
return Tree.attachTabTo(tab, parent, {
|
|
lastRelatedTab,
|
|
insertAt,
|
|
forceExpand: true, // this is required to avoid the group tab itself is active from active tab in collapsed tree
|
|
broadcast: true
|
|
});
|
|
}
|
|
|
|
if ((configs.autoGroupNewTabsFromPinned ||
|
|
configs.autoGroupNewTabsFromFirefoxView) &&
|
|
tab.$TST.needToBeGroupedSiblings.length > 0) {
|
|
log('handleTabsFromPinnedOpener: controlled by auto-grouping');
|
|
return false;
|
|
}
|
|
|
|
switch (isFirefoxViewTab(opener) ? configs.insertNewTabFromFirefoxViewAt : configs.insertNewTabFromPinnedTabAt) {
|
|
case Constants.kINSERT_NEXT_TO_LAST_RELATED_TAB: {
|
|
// it could be updated already...
|
|
const lastRelatedTab = opener.$TST.lastRelatedTab != tab ?
|
|
opener.$TST.lastRelatedTab :
|
|
opener.$TST.previousLastRelatedTab;
|
|
if (lastRelatedTab) {
|
|
log(`handleTabsFromPinnedOpener: place after last related tab ${dumpTab(lastRelatedTab)}`);
|
|
tab.$TST.temporaryMetadata.set('alreadyMovedAsOpenedFromPinnedOpener', true);
|
|
return TabsMove.moveTabAfter(tab, lastRelatedTab.$TST.lastDescendant || lastRelatedTab, {
|
|
delayedMove: true,
|
|
broadcast: true
|
|
});
|
|
}
|
|
const lastPinnedTab = Tab.getLastPinnedTab(tab.windowId);
|
|
if (lastPinnedTab) {
|
|
log(`handleTabsFromPinnedOpener: place after last pinned tab ${dumpTab(lastPinnedTab)}`);
|
|
tab.$TST.temporaryMetadata.set('alreadyMovedAsOpenedFromPinnedOpener', true);
|
|
return TabsMove.moveTabAfter(tab, lastPinnedTab, {
|
|
delayedMove: true,
|
|
broadcast: true
|
|
});
|
|
}
|
|
const firstNormalTab = Tab.getFirstNormalTab(tab.windowId);
|
|
if (firstNormalTab) {
|
|
log(`handleTabsFromPinnedOpener: place before first unpinned tab ${dumpTab(firstNormalTab)}`);
|
|
tab.$TST.temporaryMetadata.set('alreadyMovedAsOpenedFromPinnedOpener', true);
|
|
return TabsMove.moveTabBefore(tab, firstNormalTab, {
|
|
delayedMove: true,
|
|
broadcast: true
|
|
});
|
|
}
|
|
};
|
|
case Constants.kINSERT_TOP: {
|
|
const lastPinnedTab = Tab.getLastPinnedTab(tab.windowId);
|
|
if (lastPinnedTab) {
|
|
log(`handleTabsFromPinnedOpener: opened from pinned opener: place after last pinned tab ${dumpTab(lastPinnedTab)}`);
|
|
tab.$TST.temporaryMetadata.set('alreadyMovedAsOpenedFromPinnedOpener', true);
|
|
return TabsMove.moveTabAfter(tab, lastPinnedTab, {
|
|
delayedMove: true,
|
|
broadcast: true
|
|
});
|
|
}
|
|
const firstNormalTab = Tab.getFirstNormalTab(tab.windowId);
|
|
if (firstNormalTab) {
|
|
log(`handleTabsFromPinnedOpener: opened from pinned opener: place before first pinned tab ${dumpTab(firstNormalTab)}`);
|
|
tab.$TST.temporaryMetadata.set('alreadyMovedAsOpenedFromPinnedOpener', true);
|
|
return TabsMove.moveTabBefore(tab, firstNormalTab, {
|
|
delayedMove: true,
|
|
broadcast: true
|
|
});
|
|
}
|
|
}; break;
|
|
|
|
case Constants.kINSERT_END: {
|
|
const lastTab = Tab.getLastTab(tab.windowId);
|
|
log('handleTabsFromPinnedOpener: opened from pinned opener: place after the last tab ', lastTab);
|
|
tab.$TST.temporaryMetadata.set('alreadyMovedAsOpenedFromPinnedOpener', true);
|
|
return TabsMove.moveTabAfter(tab, lastTab, {
|
|
delayedMove: true,
|
|
broadcast: true
|
|
});
|
|
};
|
|
}
|
|
|
|
return Promise.resolve(false);
|
|
}
|
|
|
|
Tab.onCreated.addListener((tab, info = {}) => {
|
|
if (!info.duplicated ||
|
|
info.bypassTabControl)
|
|
return;
|
|
const original = info.originalTab;
|
|
log('duplicated ', dumpTab(tab), dumpTab(original));
|
|
if (info.duplicatedInternally) {
|
|
log('duplicated by internal operation');
|
|
tab.$TST.addState(Constants.kTAB_STATE_DUPLICATING, { broadcast: true });
|
|
TabsStore.addDuplicatingTab(tab);
|
|
}
|
|
else {
|
|
// On old versions of Firefox, duplicated tabs had no openerTabId so they were
|
|
// not handled by Tab.onCreating listener. Today they are already handled before
|
|
// here, so this is just a failsafe (or for old versions of Firefox).
|
|
// See also: https://github.com/piroor/treestyletab/issues/2830#issuecomment-831414189
|
|
Tree.behaveAutoAttachedTab(tab, {
|
|
baseTab: original,
|
|
behavior: configs.autoAttachOnDuplicated,
|
|
dontMove: info.positionedBySelf || info.movedBySelfWhileCreation || info.mayBeReplacedWithContainer,
|
|
broadcast: true
|
|
});
|
|
}
|
|
});
|
|
|
|
Tab.onUpdated.addListener((tab, changeInfo) => {
|
|
if ('openerTabId' in changeInfo &&
|
|
configs.syncParentTabAndOpenerTab &&
|
|
!tab.$TST.updatingOpenerTabIds.includes(changeInfo.openerTabId) /* accept only changes from outside of TST */) {
|
|
Tab.waitUntilTrackedAll(tab.windowId).then(() => {
|
|
const parent = tab.$TST.openerTab;
|
|
if (!parent ||
|
|
parent.windowId != tab.windowId ||
|
|
parent == tab.$TST.parent)
|
|
return;
|
|
Tree.attachTabTo(tab, parent, {
|
|
insertAt: Constants.kINSERT_NEAREST,
|
|
forceExpand: tab.active,
|
|
broadcast: true
|
|
});
|
|
});
|
|
}
|
|
|
|
if (tab.$TST.temporaryMetadata.has('openedCompletely') &&
|
|
tab.windowId == tab.$windowIdOnCreated && // Don't treat tab as "opened from active tab" if it is moved across windows while loading
|
|
(changeInfo.url || changeInfo.status == 'complete') &&
|
|
(tab.$TST.temporaryMetadata.has('isNewTab') ||
|
|
tab.$TST.temporaryMetadata.has('fromExternal') ||
|
|
tab.$TST.temporaryMetadata.has('anyOtherTrigger'))) {
|
|
log('loaded tab ', dumpTab(tab), {
|
|
isNewTab: tab.$TST.temporaryMetadata.has('isNewTab'),
|
|
fromExternal: tab.$TST.temporaryMetadata.has('fromExternal'),
|
|
anyOtherTrigger: tab.$TST.temporaryMetadata.has('anyOtherTrigger'),
|
|
});
|
|
tab.$TST.temporaryMetadata.delete('isNewTab');
|
|
const possibleOpenerTab = Tab.get(tab.$TST.temporaryMetadata.get('possibleOpenerTab'));
|
|
tab.$TST.temporaryMetadata.delete('possibleOpenerTab');
|
|
log('possibleOpenerTab ', dumpTab(possibleOpenerTab));
|
|
|
|
if (tab.$TST.temporaryMetadata.has('fromExternal')) {
|
|
tab.$TST.temporaryMetadata.delete('fromExternal');
|
|
log('behave as a tab opened from external application (delayed)');
|
|
handleNewTabFromActiveTab(tab, {
|
|
url: tab.url,
|
|
activeTab: possibleOpenerTab,
|
|
autoAttachBehavior: configs.autoAttachOnOpenedFromExternal,
|
|
inheritContextualIdentityMode: configs.inheritContextualIdentityToTabsFromExternalMode,
|
|
context: TSTAPI.kNEWTAB_CONTEXT_FROM_EXTERNAL,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (tab.$TST.temporaryMetadata.has('anyOtherTrigger')) {
|
|
tab.$TST.temporaryMetadata.delete('anyOtherTrigger');
|
|
log('behave as a tab opened from any other trigger (delayed)');
|
|
handleNewTabFromActiveTab(tab, {
|
|
url: tab.url,
|
|
activeTab: possibleOpenerTab,
|
|
autoAttachBehavior: configs.autoAttachOnAnyOtherTrigger,
|
|
inheritContextualIdentityMode: configs.inheritContextualIdentityToTabsFromAnyOtherTriggerMode,
|
|
context: TSTAPI.kNEWTAB_CONTEXT_UNKNOWN,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const win = TabsStore.windows.get(tab.windowId);
|
|
log('win.openedNewTabs ', win.openedNewTabs);
|
|
if (tab.$TST.parent ||
|
|
!possibleOpenerTab ||
|
|
win.openedNewTabs.has(tab.id) ||
|
|
tab.$TST.temporaryMetadata.has('openedWithOthers') ||
|
|
tab.$TST.temporaryMetadata.has('positionedBySelf')) {
|
|
log(' => no need to control ', {
|
|
parent: tab.$TST.parent,
|
|
possibleOpenerTab,
|
|
openedNewTab: win.openedNewTabs.has(tab.id),
|
|
openedWithOthers: tab.$TST.temporaryMetadata.has('openedWithOthers'),
|
|
positionedBySelf: tab.$TST.temporaryMetadata.has('positionedBySelf')
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (tab.$TST.isNewTabCommandTab) {
|
|
log('behave as a tab opened by new tab command (delayed)');
|
|
tab.$TST.addState(Constants.kTAB_STATE_NEW_TAB_COMMAND_TAB);
|
|
handleNewTabFromActiveTab(tab, {
|
|
activeTab: possibleOpenerTab,
|
|
autoAttachBehavior: configs.autoAttachOnNewTabCommand,
|
|
inheritContextualIdentityMode: configs.inheritContextualIdentityToChildTabMode,
|
|
context: TSTAPI.kNEWTAB_CONTEXT_NEWTAB_COMMAND,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const siteMatcher = /^\w+:\/\/([^\/]+)(?:$|\/.*$)/;
|
|
const openerTabSite = possibleOpenerTab.url.match(siteMatcher);
|
|
const newTabSite = tab.url.match(siteMatcher);
|
|
if (openerTabSite &&
|
|
newTabSite &&
|
|
tab.url != possibleOpenerTab.url && // It may be opened by "Duplciate Tab" or "Open in New Container Tab" if the URL is completely same.
|
|
openerTabSite[1] == newTabSite[1]) {
|
|
log('behave as a tab opened from same site (delayed)');
|
|
tab.$TST.addState(Constants.kTAB_STATE_OPENED_FOR_SAME_WEBSITE);
|
|
handleNewTabFromActiveTab(tab, {
|
|
url: tab.url,
|
|
activeTab: possibleOpenerTab,
|
|
autoAttachBehavior: configs.autoAttachSameSiteOrphan,
|
|
inheritContextualIdentityMode: configs.inheritContextualIdentityToSameSiteOrphanMode,
|
|
context: TSTAPI.kNEWTAB_CONTEXT_WEBSITE_SAME_TO_ACTIVE_TAB,
|
|
});
|
|
return;
|
|
}
|
|
|
|
log('checking special openers (delayed)', { opener: possibleOpenerTab.url, child: tab.url });
|
|
for (const rule of Constants.kAGGRESSIVE_OPENER_TAB_DETECTION_RULES_WITH_URL) {
|
|
if (rule.opener.test(possibleOpenerTab.url) &&
|
|
rule.child.test(tab.url)) {
|
|
log('behave as a tab opened from special opener (delayed)', { rule });
|
|
handleNewTabFromActiveTab(tab, {
|
|
url: tab.url,
|
|
activeTab: possibleOpenerTab,
|
|
autoAttachBehavior: configs.autoAttachOnOpenedWithOwner,
|
|
context: TSTAPI.kNEWTAB_CONTEXT_FROM_ABOUT_ADDONS,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
Tab.onAttached.addListener(async (tab, attachInfo = {}) => {
|
|
if (!attachInfo.windowId)
|
|
return;
|
|
|
|
const parentTabOperationBehavior = TreeBehavior.getParentTabOperationBehavior(tab, {
|
|
context: Constants.kPARENT_TAB_OPERATION_CONTEXT_MOVE,
|
|
...attachInfo,
|
|
});
|
|
if (parentTabOperationBehavior != Constants.kPARENT_TAB_OPERATION_BEHAVIOR_ENTIRE_TREE)
|
|
return;
|
|
|
|
log('Tabs.onAttached ', dumpTab(tab), attachInfo);
|
|
|
|
log('descendants of attached tab: ', () => attachInfo.descendants.map(dumpTab));
|
|
const movedTabs = await Tree.moveTabs(attachInfo.descendants, {
|
|
destinationWindowId: tab.windowId,
|
|
insertAfter: tab
|
|
});
|
|
log('moved descendants: ', () => movedTabs.map(dumpTab));
|
|
if (attachInfo.descendants.length == movedTabs.length) {
|
|
await Tree.applyTreeStructureToTabs(
|
|
[tab, ...movedTabs],
|
|
attachInfo.structure
|
|
);
|
|
}
|
|
else {
|
|
for (const movedTab of movedTabs) {
|
|
Tree.attachTabTo(movedTab, tab, {
|
|
broadcast: true,
|
|
dontMove: true
|
|
});
|
|
}
|
|
}
|
|
});
|