Skip to content

SCM inline toolbar actions leak between providers on template reuse #311990

@derammo

Description

@derammo

Bug

When multiple SCM providers are registered, inline toolbar actions from one provider's scm/resourceState/context menu may appear on another provider's resource items. For example, git's Stage Changes / Discard Changes / Open File buttons appear on a third-party extension's SCM resources, if those resources don't contribute any actions themselves. This happens even though git's contributions are properly guarded with scmProvider == git.

Reproduction

  1. Install an extension that registers a custom SCM provider with resource groups and resources
  2. Open the Source Control view so both the custom provider and git are visible
  3. Expand git's working tree to render its resources (inline actions appear correctly)
  4. Switch to the custom provider's resources
  5. Hover over a resource item -- git's inline actions appear despite the scmProvider == git when-clause guard

The bug requires that the custom provider's resource items contribute zero inline actions to scm/resourceState/context.

Root cause

connectPrimaryMenu in src/vs/workbench/contrib/scm/browser/util.ts initializes its action cache as empty arrays:

let cachedPrimary: IAction[] = [];
let cachedSecondary: IAction[] = [];

When a ResourceRenderer template is recycled from a git resource to another provider's resource, _renderActionBar correctly detects the menu change and creates a new connectPrimaryMenu subscription. The new menu is correctly evaluated through the OverlayContextKeyService chain, and all git when-clauses correctly return false, producing an empty action set.

However, equals([], []) returns true, so the callback is never invoked:

if (equals(cachedPrimary, primary, compareActions) && equals(cachedSecondary, secondary, compareActions)) {
    return; // skipped -- both cached and new are empty
}

Since setActions is never called, the toolbar retains the stale inline actions from the previous template occupant.

Fix

Initialize the cache to undefined instead of []. The equals function in base/common/arrays.ts already handles undefined safely (line 33: if (!one || !other) return false), so the first invocation always falls through to the callback:

let cachedPrimary: IAction[] = undefined!;
let cachedSecondary: IAction[] = undefined!;

Verification

Confirmed by patching the bundled workbench.desktop.main.js with this change. After the fix, switching between providers correctly clears the inline toolbar when the new provider has no matching scm/resourceState/context contributions.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions