Skip to content

Commit

Permalink
Update indent for collapsed/expanded tree with less delay #3383
Browse files Browse the repository at this point in the history
  • Loading branch information
piroor committed Nov 1, 2023
1 parent a888d9e commit 06b6b0f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 24 deletions.
49 changes: 35 additions & 14 deletions webextensions/background/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -979,15 +979,16 @@ function updateTabIndentNow(tab, level = undefined, options = {}) {

// collapse/expand tabs

// returns an array of tab ids which are changed their visibility
export async function collapseExpandSubtree(tab, params = {}) {
params.collapsed = !!params.collapsed;
if (!tab || !TabsStore.ensureLivingTab(tab))
return;
return [];
if (!TabsStore.ensureLivingTab(tab)) // it was removed while waiting
return;
return [];
params.stack = `${configs.debug && new Error().stack}\n${params.stack || ''}`;
logCollapseExpand('collapseExpandSubtree: ', dumpTab(tab), tab.$TST.subtreeCollapsed, params);
await collapseExpandSubtreeInternal(tab, params);
const visibilityChangedTabIds = await collapseExpandSubtreeInternal(tab, params);
onSubtreeCollapsedStateChanged.dispatch(tab, { collapsed: !!params.collapsed });
if (TSTAPI.hasListenerForMessageType(TSTAPI.kNOTIFY_TREE_COLLAPSED_STATE_CHANGED)) {
const treeItem = new TSTAPI.TreeItem(tab);
Expand All @@ -998,11 +999,12 @@ export async function collapseExpandSubtree(tab, params = {}) {
}, { tabProperties: ['tab'] }).catch(_error => {});
treeItem.clearCache();
}
return visibilityChangedTabIds;
}
async function collapseExpandSubtreeInternal(tab, params = {}) {
if (!params.force &&
tab.$TST.subtreeCollapsed == params.collapsed)
return;
return [];

if (params.collapsed) {
tab.$TST.addState(Constants.kTAB_STATE_SUBTREE_COLLAPSED);
Expand All @@ -1015,26 +1017,28 @@ async function collapseExpandSubtreeInternal(tab, params = {}) {

const childTabs = tab.$TST.children;
const lastExpandedTabIndex = childTabs.length - 1;
const allVisibilityChangedTabIds = [];
for (let i = 0, maxi = childTabs.length; i < maxi; i++) {
const childTab = childTabs[i];
if (i == lastExpandedTabIndex &&
!params.collapsed) {
await collapseExpandTabAndSubtree(childTab, {
allVisibilityChangedTabIds.push(...(await collapseExpandTabAndSubtree(childTab, {
collapsed: params.collapsed,
justNow: params.justNow,
anchor: tab,
last: true,
broadcast: false
});
})));
}
else {
await collapseExpandTabAndSubtree(childTab, {
allVisibilityChangedTabIds.push(...(await collapseExpandTabAndSubtree(childTab, {
collapsed: params.collapsed,
justNow: params.justNow,
broadcast: false
});
})));
}
}
const visibilityChangedTabIds = [...new Set(allVisibilityChangedTabIds)];

onSubtreeCollapsedStateChanging.dispatch(tab, { collapsed: params.collapsed });
SidebarConnection.sendMessage({
Expand All @@ -1044,29 +1048,38 @@ async function collapseExpandSubtreeInternal(tab, params = {}) {
collapsed: !!params.collapsed,
justNow: params.justNow,
anchorId: tab.id,
visibilityChangedTabIds,
last: true
});

return visibilityChangedTabIds;
}

// returns an array of tab ids which are changed their visibility
export function manualCollapseExpandSubtree(tab, params = {}) {
params.manualOperation = true;
collapseExpandSubtree(tab, params);
const visibilityChangedTabIds = collapseExpandSubtree(tab, params);
if (!params.collapsed) {
tab.$TST.addState(Constants.kTAB_STATE_SUBTREE_EXPANDED_MANUALLY);
//setTabValue(tab, Constants.kTAB_STATE_SUBTREE_EXPANDED_MANUALLY, true);
}
return visibilityChangedTabIds;
}

// returns an array of tab ids which are changed their visibility
export async function collapseExpandTabAndSubtree(tab, params = {}) {
const visibilityChangedTabIds = [];

if (!tab)
return;
return visibilityChangedTabIds;

// allow to expand root collapsed tab
if (!tab.$TST.collapsed &&
!tab.$TST.parent)
return;
return visibilityChangedTabIds;

collapseExpandTab(tab, params);
if (collapseExpandTab(tab, params))
visibilityChangedTabIds.push(tab.id);

if (params.collapsed &&
tab.active &&
Expand All @@ -1092,7 +1105,7 @@ export async function collapseExpandTabAndSubtree(tab, params = {}) {

if (!tab.$TST.subtreeCollapsed) {
const children = tab.$TST.children;
await Promise.all(children.map((child, index) => {
const allVisibilityChangedTabs = await Promise.all(children.map((child, index) => {
const last = params.last &&
(index == children.length - 1);
return collapseExpandTabAndSubtree(child, {
Expand All @@ -1104,9 +1117,13 @@ export async function collapseExpandTabAndSubtree(tab, params = {}) {
broadcast: params.broadcast
});
}));
visibilityChangedTabIds.push(...allVisibilityChangedTabs.flat());
}

return [...new Set(visibilityChangedTabIds)];
}

// returns true if the tab's visibility is changed
export async function collapseExpandTab(tab, params = {}) {
if (tab.pinned && params.collapsed) {
log('CAUTION: a pinned tab is going to be collapsed, but canceled.',
Expand All @@ -1122,9 +1139,11 @@ export async function collapseExpandTab(tab, params = {}) {
tab.$TST.ancestors.some(ancestor => ancestor.$TST.subtreeCollapsed)) {
log('collapseExpandTab: canceled to avoid expansion under collapsed tree ',
tab.$TST.ancestors.find(ancestor => ancestor.$TST.subtreeCollapsed));
return;
return false;
}

const visibilityChanged = tab.$TST.collapsed != params.collapsed;

const stack = `${configs.debug && new Error().stack}\n${params.stack || ''}`;
logCollapseExpand(`collapseExpandTab ${tab.id} `, params, { stack })
const last = params.last &&
Expand Down Expand Up @@ -1170,6 +1189,8 @@ export async function collapseExpandTab(tab, params = {}) {
});
}, shouldApplyAnimation() ? 100 : 0);
collapseExpandTab.delayedNotify.set(tab.id, timer);

return visibilityChanged;
}
collapseExpandTab.delayedNotify = new Map();

Expand Down
2 changes: 1 addition & 1 deletion webextensions/common/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export const configs = new Configs({
// animation
animation: true,
animationForce: false,
maxAllowedImmediateRefreshCount: 10,
maxAllowedImmediateRefreshCount: 1,
smoothScrollEnabled: true,
smoothScrollDuration: 150,
burstDuration: 375,
Expand Down
42 changes: 33 additions & 9 deletions webextensions/sidebar/indent.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,12 @@ export async function reserveToUpdateVisualMaxTreeLevel() {

const animation = shouldApplyAnimation();

// Immediate update may cause a performance issue when this function
// is called too many times.
// On the other hand, delayed update exposes "not updated yet" visual
// to people unexpectedly and it may stress some people sensitive to
// flickers.
// The threshold is a workaround to run immediate update while the
// slowing down is acceptable.
// On no-animation mode, we should update max indent level immediately
// as possible as we can without delay, to reduce visual flicking which
// can trigger an epileptic seizure.
// But we also have to reduce needless function calls for better performance.
// This threshold is a safe guard for uncared cases with too many call
// of updateVisualMaxTreeLevel().
// See also: https://github.com/piroor/treestyletab/issues/3383
if (reserveToUpdateVisualMaxTreeLevel.calledCount <= configs.maxAllowedImmediateRefreshCount &&
!animation) {
Expand Down Expand Up @@ -214,10 +213,28 @@ async function reserveToUpdateIndent() {
}


CollapseExpand.onUpdated.addListener((_tab, _options) => {
const restVisibilityChangedTabIds = new Set();

CollapseExpand.onUpdated.addListener((tab, _options) => {
const isFinishBatch = restVisibilityChangedTabIds.has(tab.id);
restVisibilityChangedTabIds.delete(tab.id);

if (configs.indentAutoShrink &&
configs.indentAutoShrinkOnlyForVisible)
reserveToUpdateVisualMaxTreeLevel();

// On no-animation mode, we should update max indent level immediately
// as possible as we can without delay, to reduce visual flicking which
// can trigger an epileptic seizure.
// But we also have to reduce needless function calls for better performance.
// So we throttle the function call of updateVisualMaxTreeLevel() until
// collapsed state of all tabs notified with "kCOMMAND_NOTIFY_SUBTREE_COLLAPSED_STATE_CHANGED"
// are completely updated.
// See also: https://github.com/piroor/treestyletab/issues/3383
if (isFinishBatch &&
restVisibilityChangedTabIds.size == 0 &&
!shouldApplyAnimation())
updateVisualMaxTreeLevel();
});

const BUFFER_KEY_PREFIX = 'indent-';
Expand Down Expand Up @@ -253,8 +270,15 @@ BackgroundConnection.onMessage.addListener(async message => {
reserveToUpdateIndent();
}; break;

case Constants.kCOMMAND_NOTIFY_SUBTREE_COLLAPSED_STATE_CHANGED:
for (const id of message.visibilityChangedTabIds) {
restVisibilityChangedTabIds.add(id);
}
break;

case Constants.kCOMMAND_NOTIFY_TAB_COLLAPSED_STATE_CHANGED:
if (!shouldApplyAnimation())
if (!restVisibilityChangedTabIds.has(message.tabId) &&
!shouldApplyAnimation())
updateVisualMaxTreeLevel();
break;
}
Expand Down

0 comments on commit 06b6b0f

Please sign in to comment.