Skip to content

Commit 8010df1

Browse files
committed
Allow to toggle native tab group's collapsed state by clicking
1 parent 73cfd83 commit 8010df1

5 files changed

Lines changed: 70 additions & 45 deletions

File tree

webextensions/common/Tab.js

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export default class Tab {
219219

220220
bindElement(element) {
221221
element.$TST = this;
222-
element.apiTab = this.tab;
222+
element.apiRaw = this.raw;
223223
this.element = element;
224224
this.classList = element.classList;
225225
// wait until initialization processes are completed
@@ -249,7 +249,7 @@ export default class Tab {
249249
this.element.removeAttribute(name);
250250
}
251251
this.element.$TST = null;
252-
this.element.apiTab = null;
252+
this.element.apiRaw = null;
253253
}
254254
this.element = null;
255255
this.classList = null;
@@ -299,7 +299,7 @@ export default class Tab {
299299
}
300300

301301
get type() {
302-
return 'color' in this.raw ? 'group' : 'tab';
302+
return Tab.isNativeTabGroup(this.raw) ? 'group' : 'tab';
303303
}
304304

305305
get renderingId() {
@@ -939,7 +939,7 @@ export default class Tab {
939939
this.tab.groupId == -1) {
940940
return null;
941941
}
942-
return TabsStore.windows.get(this.tab.windowId).tabGroups.get(this.tab.groupId);
942+
return Tab.getNativeTabGroup(this.tab);
943943
}
944944

945945
set parent(tab) {
@@ -2026,7 +2026,7 @@ export default class Tab {
20262026
continue;
20272027
}
20282028
const windowId = this.tab.windowId;
2029-
const group = TabsStore.windows.get(this.tab.windowId).tabGroups.get(groupId);
2029+
const group = Tab.getNativeTabGroup({ windowId: this.tab.windowId, groupId });
20302030
if (!group) {
20312031
continue;
20322032
}
@@ -2183,40 +2183,41 @@ export default class Tab {
21832183

21842184
let exportedTab = configs.cacheAPITreeItems && light ? this.$exportedForAPI : null;
21852185
let favIconUrl;
2186+
const tab = this.tab;
21862187
if (!exportedTab) {
21872188
const [effectiveFavIconUrl, children] = await Promise.all([
21882189
(light ||
21892190
(!permissions ||
21902191
(!permissions.has(kPERMISSION_TABS) &&
21912192
(!permissions.has(kPERMISSION_ACTIVE_TAB) ||
2192-
!this.tab.active)))) ?
2193+
!tab?.active)))) ?
21932194
null :
2194-
(this.tab.id in cache.effectiveFavIconUrls) ?
2195-
cache.effectiveFavIconUrls[this.tab.id] :
2196-
this.tab.favIconUrl?.startsWith('data:') ?
2197-
this.tab.favIconUrl :
2198-
TabFavIconHelper.getLastEffectiveFavIconURL(this.tab).catch(ApiTabs.handleMissingTabError),
2195+
(tab?.id in cache.effectiveFavIconUrls) ?
2196+
cache.effectiveFavIconUrls[tab?.id] :
2197+
tab?.favIconUrl?.startsWith('data:') ?
2198+
tab?.favIconUrl :
2199+
TabFavIconHelper.getLastEffectiveFavIconURL(tab).catch(ApiTabs.handleMissingTabError),
21992200
doProgressively(
2200-
this.tab.$TST.children,
2201+
this.raw.$TST.children,
22012202
child => child.$TST.exportForAPI({ addonId, light, isContextTab, interval, permissions, cache, cacheKey }),
22022203
interval
22032204
),
22042205
]);
22052206
favIconUrl = effectiveFavIconUrl;
22062207

2207-
if (!(this.tab.id in cache.effectiveFavIconUrls))
2208-
cache.effectiveFavIconUrls[this.tab.id] = effectiveFavIconUrl;
2208+
if (!(tab?.id in cache.effectiveFavIconUrls))
2209+
cache.effectiveFavIconUrls[tab?.id] = effectiveFavIconUrl;
22092210

2210-
const tabStates = this.tab.$TST.states;
2211+
const tabStates = tab?.$TST.states;
22112212
exportedTab = {
2212-
id: this.tab.id,
2213-
windowId: this.tab.windowId,
2213+
id: this.raw.id,
2214+
windowId: this.raw.windowId,
22142215
type: this.type,
2215-
states: this.type == 'group' ? [] : Constants.kTAB_SAFE_STATES_ARRAY.filter(state => tabStates.has(state)),
2216-
indent: parseInt(this.tab.$TST.getAttribute(Constants.kLEVEL) || 0),
2216+
states: tabStates && Constants.kTAB_SAFE_STATES_ARRAY.filter(state => tabStates.has(state)) || [],
2217+
indent: parseInt(this.raw.$TST.getAttribute(Constants.kLEVEL) || 0),
22172218
children,
2218-
ancestorTabIds: this.tab.$TST.ancestorIds,
2219-
bundledTabId: this.tab.$TST.bundledTabId,
2219+
ancestorTabIds: tab?.$TST.ancestorIds || [],
2220+
bundledTabId: tab?.$TST.bundledTabId,
22202221
};
22212222
if (this.stuck)
22222223
exportedTab.states.push(Constants.kTAB_STATE_STUCK);
@@ -2259,8 +2260,8 @@ export default class Tab {
22592260

22602261
if (permissions.has(kPERMISSION_TABS) ||
22612262
(permissions.has(kPERMISSION_ACTIVE_TAB) &&
2262-
(this.tab.active ||
2263-
(this.tab == this.tab && this.isContextTab)))) {
2263+
(tab?.active ||
2264+
this.isContextTab))) {
22642265
// specially allowed with "tabs" or "activeTab" permission
22652266
allowedProperties.add('favIconUrl');
22662267
allowedProperties.add('title');
@@ -2269,12 +2270,12 @@ export default class Tab {
22692270
}
22702271
if (permissions.has(kPERMISSION_COOKIES)) {
22712272
allowedProperties.add('cookieStoreId');
2272-
fullExportedTab.cookieStoreName = this.tab.$TST.cookieStoreName;
2273+
fullExportedTab.cookieStoreName = tab?.$TST.cookieStoreName;
22732274
}
22742275

22752276
for (const property of allowedProperties) {
2276-
if (property in this.tab)
2277-
fullExportedTab[property] = this.tab[property];
2277+
if (property in this.raw)
2278+
fullExportedTab[property] = this.raw[property];
22782279
}
22792280

22802281
if (configs.cacheAPITreeItems)
@@ -2384,10 +2385,17 @@ Tab.isTracked = tabId => {
23842385
return TabsStore.tabs.has(tabId);
23852386
};
23862387

2388+
Tab.isNativeTabGroup = tabOrGroup => typeof tabOrGroup?.color !== 'undefined';
2389+
23872390
Tab.get = tabId => {
2391+
if (Tab.isNativeTabGroup(tabId)) {
2392+
return Tab.getNativeTabGroup({ windowId: tabId.windowId, groupId: tabId.id });
2393+
}
23882394
return TabsStore.tabs.get(typeof tabId == 'number' ? tabId : tabId?.id);
23892395
};
23902396

2397+
Tab.getNativeTabGroup = ({ windowId, groupId }) => TabsStore.windows.get(windowId).tabGroups.get(groupId);
2398+
23912399
Tab.getByUniqueId = id => {
23922400
if (!id)
23932401
return null;

webextensions/sidebar/event-utils.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,9 @@ export function getEventDetail(event) {
240240
export function getTabEventDetail(event, tab) {
241241
return {
242242
...getEventDetail(event),
243-
tab: tab && tab.id,
244-
tabId: tab && tab.id,
243+
tab: tab?.id,
244+
tabId: tab?.id,
245+
tabType: tab?.$TST.type,
245246
};
246247
}
247248

webextensions/sidebar/mouse-event-listener.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* The Original Code is the Tree Style Tab.
1515
*
1616
* The Initial Developer of the Original Code is YUKI "Piro" Hiroshi.
17-
* Portions created by the Initial Developer are Copyright (C) 2011-2024
17+
* Portions created by the Initial Developer are Copyright (C) 2011-2025
1818
* the Initial Developer. All Rights Reserved.
1919
*
2020
* Contributor(s): YUKI "Piro" Hiroshi <piro.outsider.reflex@gmail.com>
@@ -381,7 +381,7 @@ function onMouseDown(event) {
381381
if (mousedown.detail.button == 0 &&
382382
onRegularArea &&
383383
!wasMultiselectionAction &&
384-
tab) {
384+
tab?.$TST.type == 'tab') {
385385
BackgroundConnection.sendMessage({
386386
type: Constants.kCOMMAND_ACTIVATE_TAB,
387387
tabId: tab.id,
@@ -490,9 +490,12 @@ async function onMouseUp(event) {
490490
if (lastMousedown.tab && lastMousedown.detail.targetType == 'tab')
491491
promisedCanceled = lastMousedown.promisedMousedownNotified;
492492

493+
const lastMousedownTab = lastMousedown.detail.tabType == 'group' ?
494+
Tab.getNativeTabGroup({ windowId: lastMousedown.detail.windowId, groupId: lastMousedown.detail.tabId }) :
495+
Tab.get(lastMousedown.detail.tabId)
493496
if (lastMousedown.expired ||
494497
lastMousedown.detail.targetType != EventUtils.getEventTargetType(event) || // when the cursor was moved before mouseup
495-
(tab && tab != Tab.get(lastMousedown.detail.tab))) { // when the tab was already removed
498+
(tab && tab != lastMousedownTab)) { // when the tab was already removed
496499
log(' => expired, different type, or different tab');
497500
return;
498501
}
@@ -698,13 +701,22 @@ async function handleDefaultMouseUpOnTab({ lastMousedown, tab, event } = {}) {
698701
// we simulate the behavior.
699702
if (lastMousedown.detail.button == 0 &&
700703
onRegularArea &&
701-
!wasMultiselectionAction)
702-
BackgroundConnection.sendMessage({
703-
type: Constants.kCOMMAND_ACTIVATE_TAB,
704-
tabId: tab.id,
705-
byMouseOperation: true,
706-
keepMultiselection: false // tab.highlighted
707-
});
704+
!wasMultiselectionAction) {
705+
switch (tab.$TST.type) {
706+
case 'tab':
707+
BackgroundConnection.sendMessage({
708+
type: Constants.kCOMMAND_ACTIVATE_TAB,
709+
tabId: tab.id,
710+
byMouseOperation: true,
711+
keepMultiselection: false // tab.highlighted
712+
});
713+
break;
714+
715+
case 'group':
716+
await browser.tabGroups.update(tab.id, { collapsed: !tab.collapsed });
717+
break;
718+
}
719+
}
708720

709721
if (lastMousedown.detail.isMiddleClick) { // Ctrl-click doesn't close tab on Firefox's tab bar!
710722
log(`onMouseUp: middle click on the tab ${tab.id}: `, lastMousedown.detail.targetType);

webextensions/sidebar/scroll.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,10 @@ function getRenderableItemById(id) {
430430
const [type, rawId] = id.split(':');
431431
switch (type) {
432432
case 'group':
433-
return TabsStore.windows.get(TabsStore.getCurrentWindowId()).tabGroups.get(parseInt(rawId));
433+
return Tab.getNativeTabGroup({
434+
windowId: TabsStore.getCurrentWindowId(),
435+
groupId: parseInt(rawId),
436+
});
434437

435438
case 'tab':
436439
default:

webextensions/sidebar/sidebar-tabs.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ export function getTabFromDOMNode(node, options = {}) {
7777
node = node.parentNode;
7878
const tabSubstance = node && node.closest(kTAB_SUBSTANCE_ELEMENT_NAME);
7979
const tab = tabSubstance && tabSubstance.closest(kTAB_ELEMENT_NAME);
80-
if (options.force)
81-
return tab && tab.apiTab;
82-
return TabsStore.ensureLivingTab(tab && tab.apiTab);
80+
if (options.force ||
81+
tab?.apiRaw?.$TST.type == 'group') {
82+
return tab.apiRaw;
83+
}
84+
return TabsStore.ensureLivingTab(tab?.apiRaw);
8385
}
8486

8587

@@ -673,7 +675,7 @@ function reserveToRefreshNativeTabGroup(id) {
673675
reserveToRefreshNativeTabGroup.invoked.delete(id);
674676

675677
const windowId = TabsStore.getCurrentWindowId();
676-
const group = TabsStore.windows.get(windowId).tabGroups.get(id);
678+
const group = Tab.getNativeTabGroup({ windowId, groupId: id });
677679
if (!group) {
678680
return;
679681
}
@@ -1376,8 +1378,7 @@ BackgroundConnection.onMessage.addListener(async message => {
13761378
}; break;
13771379

13781380
case Constants.kCOMMAND_NOTIFY_TAB_GROUP_UPDATED: {
1379-
const win = TabsStore.windows.get(message.windowId);
1380-
const group = win.tabGroups.get(message.group.id);
1381+
const group = Tab.getNativeTabGroup({ windowId: message.windowId, groupId: message.group.id });
13811382
group.$TST.apply(message.group);
13821383
reserveToRefreshNativeTabGroup(message.group.id);
13831384
}; break;

0 commit comments

Comments
 (0)