Browser: context menus (#299013)

* Browser: context menus

* feedback

* feedback

* auxiliary fix
This commit is contained in:
Kyle Cutler
2026-03-06 07:29:58 -08:00
committed by GitHub
parent 81f2b5cd2f
commit dc94486ab9
6 changed files with 306 additions and 82 deletions

View File

@@ -7,6 +7,29 @@ import { Event } from '../../../base/common/event.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { URI } from '../../../base/common/uri.js';
const commandPrefix = 'workbench.action.browser';
export enum BrowserViewCommandId {
Open = `${commandPrefix}.open`,
NewTab = `${commandPrefix}.newTab`,
GoBack = `${commandPrefix}.goBack`,
GoForward = `${commandPrefix}.goForward`,
Reload = `${commandPrefix}.reload`,
HardReload = `${commandPrefix}.hardReload`,
FocusUrlInput = `${commandPrefix}.focusUrlInput`,
AddElementToChat = `${commandPrefix}.addElementToChat`,
AddConsoleLogsToChat = `${commandPrefix}.addConsoleLogsToChat`,
ToggleDevTools = `${commandPrefix}.toggleDevTools`,
OpenExternal = `${commandPrefix}.openExternal`,
ClearGlobalStorage = `${commandPrefix}.clearGlobalStorage`,
ClearWorkspaceStorage = `${commandPrefix}.clearWorkspaceStorage`,
ClearEphemeralStorage = `${commandPrefix}.clearEphemeralStorage`,
OpenSettings = `${commandPrefix}.openSettings`,
ShowFind = `${commandPrefix}.showFind`,
HideFind = `${commandPrefix}.hideFind`,
FindNext = `${commandPrefix}.findNext`,
FindPrevious = `${commandPrefix}.findPrevious`,
}
export interface IBrowserViewBounds {
windowId: number;
x: number;
@@ -287,4 +310,10 @@ export interface IBrowserViewService {
* @param id The browser view identifier
*/
clearStorage(id: string): Promise<void>;
/**
* Update the keybinding accelerators used in browser view context menus.
* @param keybindings A map of command ID to accelerator label
*/
updateKeybindings(keybindings: { [commandId: string]: string }): Promise<void>;
}

View File

@@ -11,15 +11,16 @@ import { VSBuffer } from '../../../base/common/buffer.js';
import { IBrowserViewBounds, IBrowserViewDevToolsStateEvent, IBrowserViewFocusEvent, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewNavigationEvent, IBrowserViewLoadingEvent, IBrowserViewLoadError, IBrowserViewTitleChangeEvent, IBrowserViewFaviconChangeEvent, IBrowserViewNewPageRequest, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, IBrowserViewFindInPageResult, IBrowserViewVisibilityEvent, BrowserNewPageLocation, browserViewIsolatedWorldId } from '../common/browserView.js';
import { EVENT_KEY_CODE_MAP, KeyCode, KeyMod, SCAN_CODE_STR_TO_EVENT_KEY_CODE } from '../../../base/common/keyCodes.js';
import { IWindowsMainService } from '../../windows/electron-main/windows.js';
import { IBaseWindow, ICodeWindow } from '../../window/electron-main/window.js';
import { ICodeWindow } from '../../window/electron-main/window.js';
import { IAuxiliaryWindowsMainService } from '../../auxiliaryWindow/electron-main/auxiliaryWindows.js';
import { IAuxiliaryWindow } from '../../auxiliaryWindow/electron-main/auxiliaryWindow.js';
import { isMacintosh } from '../../../base/common/platform.js';
import { BrowserViewUri } from '../common/browserViewUri.js';
import { BrowserViewDebugger } from './browserViewDebugger.js';
import { ILogService } from '../../log/common/log.js';
import { ICDPTarget, ICDPConnection, CDPTargetInfo } from '../common/cdp/types.js';
import { BrowserSession } from './browserSession.js';
import { IAuxiliaryWindow } from '../../auxiliaryWindow/electron-main/auxiliaryWindow.js';
import { hasKey } from '../../../base/common/types.js';
/** Key combinations that are used in system-level shortcuts. */
const nativeShortcuts = new Set([
@@ -47,7 +48,7 @@ export class BrowserView extends Disposable implements ICDPTarget {
private _lastUserGestureTimestamp: number = -Infinity;
private _debugger: BrowserViewDebugger;
private _window: IBaseWindow | undefined;
private _window: ICodeWindow | IAuxiliaryWindow | undefined;
private _isSendingKeyEvent = false;
private _isDisposed = false;
@@ -88,6 +89,7 @@ export class BrowserView extends Disposable implements ICDPTarget {
public readonly id: string,
public readonly session: BrowserSession,
createChildView: (options?: Electron.WebContentsViewConstructorOptions) => BrowserView,
openContextMenu: (view: BrowserView, params: Electron.ContextMenuParams) => void,
options: Electron.WebContentsViewConstructorOptions | undefined,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService,
@@ -150,6 +152,10 @@ export class BrowserView extends Disposable implements ICDPTarget {
};
});
this._view.webContents.on('context-menu', (_event, params) => {
openContextMenu(this, params);
});
this._view.webContents.on('destroyed', () => {
this.dispose();
});
@@ -376,7 +382,7 @@ export class BrowserView extends Disposable implements ICDPTarget {
*/
layout(bounds: IBrowserViewBounds): void {
if (this._window?.win?.id !== bounds.windowId) {
const newWindow = this.windowById(bounds.windowId);
const newWindow = this._windowById(bounds.windowId);
if (newWindow) {
this._window?.win?.contentView.removeChildView(this._view);
this._window = newWindow;
@@ -575,6 +581,22 @@ export class BrowserView extends Disposable implements ICDPTarget {
return this._view;
}
/**
* Get the hosting Electron window for this view, if any.
* This can be an auxiliary window, depending on where the view is currently hosted.
*/
getElectronWindow(): Electron.BrowserWindow | undefined {
return this._window?.win ?? undefined;
}
/**
* Get the main code window hosting this browser view, if any. This is used for routing commands from the browser view to the correct window.
* If the browser view is hosted in an auxiliary window, this will return the parent code window of that auxiliary window.
*/
getTopCodeWindow(): ICodeWindow | undefined {
return this._window && hasKey(this._window, { parentId: true }) ? this._codeWindowById(this._window.parentId) : undefined;
}
// ============ ICDPTarget implementation ============
/**
@@ -662,11 +684,11 @@ export class BrowserView extends Disposable implements ICDPTarget {
return true;
}
private windowById(windowId: number | undefined): ICodeWindow | IAuxiliaryWindow | undefined {
return this.codeWindowById(windowId) ?? this.auxiliaryWindowById(windowId);
private _windowById(windowId: number | undefined): ICodeWindow | IAuxiliaryWindow | undefined {
return this._codeWindowById(windowId) ?? this._auxiliaryWindowById(windowId);
}
private codeWindowById(windowId: number | undefined): ICodeWindow | undefined {
private _codeWindowById(windowId: number | undefined): ICodeWindow | undefined {
if (typeof windowId !== 'number') {
return undefined;
}
@@ -674,7 +696,7 @@ export class BrowserView extends Disposable implements ICDPTarget {
return this.windowsMainService.getWindowById(windowId);
}
private auxiliaryWindowById(windowId: number | undefined): IAuxiliaryWindow | undefined {
private _auxiliaryWindowById(windowId: number | undefined): IAuxiliaryWindow | undefined {
if (typeof windowId !== 'number') {
return undefined;
}

View File

@@ -6,7 +6,8 @@
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { IBrowserViewBounds, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewService, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions } from '../common/browserView.js';
import { IBrowserViewBounds, IBrowserViewKeyDownEvent, IBrowserViewState, IBrowserViewService, BrowserViewStorageScope, IBrowserViewCaptureScreenshotOptions, IBrowserViewFindInPageOptions, BrowserViewCommandId } from '../common/browserView.js';
import { clipboard, Menu, MenuItem } from 'electron';
import { ICDPTarget, CDPBrowserVersion, CDPWindowBounds, CDPTargetInfo, ICDPConnection, ICDPBrowserTarget } from '../common/cdp/types.js';
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.js';
@@ -17,9 +18,13 @@ import { IWindowsMainService } from '../../windows/electron-main/windows.js';
import { BrowserSession } from './browserSession.js';
import { IProductService } from '../../product/common/productService.js';
import { CDPBrowserProxy } from '../common/cdp/proxy.js';
import { logBrowserOpen } from '../common/browserViewTelemetry.js';
import { IntegratedBrowserOpenSource, logBrowserOpen } from '../common/browserViewTelemetry.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { localize } from '../../../nls.js';
import { INativeHostMainService } from '../../native/electron-main/nativeHostMainService.js';
import { ITextEditorOptions } from '../../editor/common/editor.js';
import { htmlAttributeEncodeValue } from '../../../base/common/strings.js';
export const IBrowserViewMainService = createDecorator<IBrowserViewMainService>('browserViewMainService');
@@ -41,6 +46,7 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
}
private readonly browserViews = this._register(new DisposableMap<string, BrowserView>());
private _keybindings: { [commandId: string]: string } = Object.create(null);
// ICDPBrowserTarget events
private readonly _onTargetCreated = this._register(new Emitter<BrowserView>());
@@ -54,38 +60,12 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IProductService private readonly productService: IProductService,
@ITelemetryService private readonly telemetryService: ITelemetryService
@ITelemetryService private readonly telemetryService: ITelemetryService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService
) {
super();
}
/**
* Create a browser view backed by the given {@link BrowserSession}.
*/
private createBrowserView(id: string, browserSession: BrowserSession, options?: Electron.WebContentsViewConstructorOptions): BrowserView {
if (this.browserViews.has(id)) {
throw new Error(`Browser view with id ${id} already exists`);
}
const view = this.instantiationService.createInstance(
BrowserView,
id,
browserSession,
// Recursive factory for nested windows (child views share the same session)
(childOptions) => this.createBrowserView(generateUuid(), browserSession, childOptions),
options
);
this.browserViews.set(id, view);
this._onTargetCreated.fire(view);
Event.once(view.onDidClose)(() => {
this._onTargetDestroyed.fire(view);
this.browserViews.deleteAndDispose(id);
});
return view;
}
async getOrCreateBrowserView(id: string, scope: BrowserViewStorageScope, workspaceId?: string): Promise<IBrowserViewState> {
if (this.browserViews.has(id)) {
// Note: scope will be ignored if the view already exists.
@@ -160,26 +140,14 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
}
async createTarget(url: string, browserContextId?: string, windowId?: number): Promise<ICDPTarget> {
const targetId = generateUuid();
const browserSession = browserContextId && BrowserSession.get(browserContextId) || BrowserSession.getOrCreateEphemeral(targetId);
const browserSession = browserContextId ? BrowserSession.get(browserContextId) : undefined;
// Create the browser view (fires onTargetCreated)
const view = this.createBrowserView(targetId, browserSession);
logBrowserOpen(this.telemetryService, 'cdpCreated');
const window = windowId !== undefined ? this.windowsMainService.getWindowById(windowId) : this.windowsMainService.getFocusedWindow();
if (!window) {
throw new Error(`Window ${windowId} not found`);
}
// Request the workbench to open the editor
window.sendWhenReady('vscode:runAction', CancellationToken.None, {
id: '_workbench.open',
args: [BrowserViewUri.forUrl(url, targetId), [undefined, { preserveFocus: true }], undefined]
return this.openNew(url, {
session: browserSession,
windowId,
editorOptions: { preserveFocus: true },
source: 'cdpCreated'
});
return view;
}
async activateTarget(target: ICDPTarget): Promise<void> {
@@ -372,4 +340,182 @@ export class BrowserViewMainService extends Disposable implements IBrowserViewMa
);
await browserSession.electronSession.clearData();
}
async updateKeybindings(keybindings: { [commandId: string]: string }): Promise<void> {
this._keybindings = keybindings;
}
/**
* Create a browser view backed by the given {@link BrowserSession}.
*/
private createBrowserView(id: string, browserSession: BrowserSession, options?: Electron.WebContentsViewConstructorOptions): BrowserView {
if (this.browserViews.has(id)) {
throw new Error(`Browser view with id ${id} already exists`);
}
const view = this.instantiationService.createInstance(
BrowserView,
id,
browserSession,
// Recursive factory for nested windows (child views share the same session)
(childOptions) => this.createBrowserView(generateUuid(), browserSession, childOptions),
(v, params) => this.showContextMenu(v, params),
options
);
this.browserViews.set(id, view);
this._onTargetCreated.fire(view);
Event.once(view.onDidClose)(() => {
this._onTargetDestroyed.fire(view);
this.browserViews.deleteAndDispose(id);
});
return view;
}
private async openNew(
url: string,
{
session,
windowId,
editorOptions,
source
}: {
session: BrowserSession | undefined;
windowId: number | undefined;
editorOptions: ITextEditorOptions;
source: IntegratedBrowserOpenSource;
}
): Promise<ICDPTarget> {
const targetId = generateUuid();
const view = this.createBrowserView(targetId, session || BrowserSession.getOrCreateEphemeral(targetId));
const window = windowId !== undefined ? this.windowsMainService.getWindowById(windowId) : this.windowsMainService.getFocusedWindow();
if (!window) {
throw new Error(`Window ${windowId} not found`);
}
logBrowserOpen(this.telemetryService, source);
// Request the workbench to open the editor
window.sendWhenReady('vscode:runAction', CancellationToken.None, {
id: '_workbench.open',
args: [BrowserViewUri.forUrl(url, targetId), [undefined, editorOptions], undefined]
});
return view;
}
private showContextMenu(view: BrowserView, params: Electron.ContextMenuParams): void {
const win = view.getElectronWindow();
if (!win) {
return;
}
const webContents = view.webContents;
if (webContents.isDestroyed()) {
return;
}
const menu = new Menu();
if (params.linkURL) {
menu.append(new MenuItem({
label: localize('browser.contextMenu.openLinkInNewTab', 'Open Link in New Tab'),
click: () => {
void this.openNew(params.linkURL, {
session: view.session,
windowId: view.getTopCodeWindow()?.id,
editorOptions: { preserveFocus: true, inactive: true },
source: 'browserLinkBackground'
});
}
}));
menu.append(new MenuItem({
label: localize('browser.contextMenu.openLinkInExternalBrowser', 'Open Link in External Browser'),
click: () => { void this.nativeHostMainService.openExternal(undefined, params.linkURL); }
}));
menu.append(new MenuItem({ type: 'separator' }));
menu.append(new MenuItem({
label: localize('browser.contextMenu.copyLink', 'Copy Link'),
click: () => {
clipboard.write({
text: params.linkURL,
html: `<a href="${encodeURI(params.linkURL)}">${htmlAttributeEncodeValue(params.linkText || params.linkURL)}</a>`
});
}
}));
}
if (params.hasImageContents && params.srcURL) {
if (menu.items.length > 0) {
menu.append(new MenuItem({ type: 'separator' }));
}
menu.append(new MenuItem({
label: localize('browser.contextMenu.openImageInNewTab', 'Open Image in New Tab'),
click: () => {
void this.openNew(params.srcURL!, {
session: view.session,
windowId: view.getTopCodeWindow()?.id,
editorOptions: { preserveFocus: true, inactive: true },
source: 'browserLinkBackground'
});
}
}));
menu.append(new MenuItem({
label: localize('browser.contextMenu.copyImage', 'Copy Image'),
click: () => { view.webContents.copyImageAt(params.x, params.y); }
}));
menu.append(new MenuItem({
label: localize('browser.contextMenu.copyImageUrl', 'Copy Image URL'),
click: () => { clipboard.writeText(params.srcURL!); }
}));
}
if (params.isEditable) {
menu.append(new MenuItem({ role: 'cut', enabled: params.editFlags.canCut }));
menu.append(new MenuItem({ role: 'copy', enabled: params.editFlags.canCopy }));
menu.append(new MenuItem({ role: 'paste', enabled: params.editFlags.canPaste }));
menu.append(new MenuItem({ role: 'pasteAndMatchStyle', enabled: params.editFlags.canPaste }));
menu.append(new MenuItem({ role: 'selectAll', enabled: params.editFlags.canSelectAll }));
} else if (params.selectionText) {
menu.append(new MenuItem({ role: 'copy' }));
}
// Add navigation items as defaults
if (menu.items.length === 0) {
if (webContents.navigationHistory.canGoBack()) {
menu.append(new MenuItem({
label: localize('browser.contextMenu.back', 'Back'),
accelerator: this._keybindings[BrowserViewCommandId.GoBack],
click: () => webContents.navigationHistory.goBack()
}));
}
if (webContents.navigationHistory.canGoForward()) {
menu.append(new MenuItem({
label: localize('browser.contextMenu.forward', 'Forward'),
accelerator: this._keybindings[BrowserViewCommandId.GoForward],
click: () => webContents.navigationHistory.goForward()
}));
}
menu.append(new MenuItem({
label: localize('browser.contextMenu.reload', 'Reload'),
accelerator: this._keybindings[BrowserViewCommandId.Reload],
click: () => webContents.reload()
}));
}
menu.append(new MenuItem({ type: 'separator' }));
menu.append(new MenuItem({
label: localize('browser.contextMenu.inspect', 'Inspect'),
click: () => webContents.inspectElement(params.x, params.y)
}));
const viewBounds = view.getWebContentsView().getBounds();
menu.popup({
window: win,
x: viewBounds.x + params.x,
y: viewBounds.y + params.y,
sourceType: params.menuSourceType
});
}
}

View File

@@ -11,6 +11,7 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
import { IBrowserViewModel } from '../common/browserView.js';
import { BrowserViewCommandId } from '../../../../platform/browserView/common/browserView.js';
import { localize } from '../../../../nls.js';
import { DisposableStore } from '../../../../base/common/lifecycle.js';
@@ -45,9 +46,9 @@ export class BrowserFindWidget extends SimpleFindWidget {
showResultCount: true,
enableSash: true,
initialWidth: 350,
previousMatchActionId: 'workbench.action.browser.findPrevious',
nextMatchActionId: 'workbench.action.browser.findNext',
closeWidgetActionId: 'workbench.action.browser.hideFind'
previousMatchActionId: BrowserViewCommandId.FindPrevious,
nextMatchActionId: BrowserViewCommandId.FindNext,
closeWidgetActionId: BrowserViewCommandId.HideFind
}, contextViewService, contextKeyService, hoverService, keybindingService, configurationService, accessibilityService);
this._findWidgetVisible = CONTEXT_BROWSER_FIND_WIDGET_VISIBLE.bindTo(contextKeyService);

View File

@@ -14,7 +14,7 @@ import { Codicon } from '../../../../base/common/codicons.js';
import { BrowserEditor, CONTEXT_BROWSER_CAN_GO_BACK, CONTEXT_BROWSER_CAN_GO_FORWARD, CONTEXT_BROWSER_DEVTOOLS_OPEN, CONTEXT_BROWSER_FOCUSED, CONTEXT_BROWSER_HAS_ERROR, CONTEXT_BROWSER_HAS_URL, CONTEXT_BROWSER_STORAGE_SCOPE, CONTEXT_BROWSER_ELEMENT_SELECTION_ACTIVE, CONTEXT_BROWSER_FIND_WIDGET_FOCUSED, CONTEXT_BROWSER_FIND_WIDGET_VISIBLE } from './browserEditor.js';
import { BrowserViewUri } from '../../../../platform/browserView/common/browserViewUri.js';
import { IBrowserViewWorkbenchService } from '../common/browserView.js';
import { BrowserViewStorageScope } from '../../../../platform/browserView/common/browserView.js';
import { BrowserViewCommandId, BrowserViewStorageScope } from '../../../../platform/browserView/common/browserView.js';
import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
@@ -37,7 +37,7 @@ interface IOpenBrowserOptions {
class OpenIntegratedBrowserAction extends Action2 {
constructor() {
super({
id: 'workbench.action.browser.open',
id: BrowserViewCommandId.Open,
title: localize2('browser.openAction', "Open Integrated Browser"),
category: BrowserCategory,
f1: true
@@ -67,7 +67,7 @@ class OpenIntegratedBrowserAction extends Action2 {
class NewTabAction extends Action2 {
constructor() {
super({
id: 'workbench.action.browser.newTab',
id: BrowserViewCommandId.NewTab,
title: localize2('browser.newTabAction', "New Tab"),
category: BrowserCategory,
f1: true,
@@ -97,7 +97,7 @@ class NewTabAction extends Action2 {
}
class GoBackAction extends Action2 {
static readonly ID = 'workbench.action.browser.goBack';
static readonly ID = BrowserViewCommandId.GoBack;
constructor() {
super({
@@ -129,7 +129,7 @@ class GoBackAction extends Action2 {
}
class GoForwardAction extends Action2 {
static readonly ID = 'workbench.action.browser.goForward';
static readonly ID = BrowserViewCommandId.GoForward;
constructor() {
super({
@@ -161,7 +161,7 @@ class GoForwardAction extends Action2 {
}
class ReloadAction extends Action2 {
static readonly ID = 'workbench.action.browser.reload';
static readonly ID = BrowserViewCommandId.Reload;
constructor() {
super({
@@ -199,7 +199,7 @@ class ReloadAction extends Action2 {
}
class HardReloadAction extends Action2 {
static readonly ID = 'workbench.action.browser.hardReload';
static readonly ID = BrowserViewCommandId.HardReload;
constructor() {
super({
@@ -227,7 +227,7 @@ class HardReloadAction extends Action2 {
}
class FocusUrlInputAction extends Action2 {
static readonly ID = 'workbench.action.browser.focusUrlInput';
static readonly ID = BrowserViewCommandId.FocusUrlInput;
constructor() {
super({
@@ -251,7 +251,7 @@ class FocusUrlInputAction extends Action2 {
}
class AddElementToChatAction extends Action2 {
static readonly ID = 'workbench.action.browser.addElementToChat';
static readonly ID = BrowserViewCommandId.AddElementToChat;
constructor() {
const enabled = ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('config.chat.sendElementsToChat.enabled', true));
@@ -288,7 +288,7 @@ class AddElementToChatAction extends Action2 {
}
class AddConsoleLogsToChatAction extends Action2 {
static readonly ID = 'workbench.action.browser.addConsoleLogsToChat';
static readonly ID = BrowserViewCommandId.AddConsoleLogsToChat;
constructor() {
const enabled = ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('config.chat.sendElementsToChat.enabled', true));
@@ -316,7 +316,7 @@ class AddConsoleLogsToChatAction extends Action2 {
}
class ToggleDevToolsAction extends Action2 {
static readonly ID = 'workbench.action.browser.toggleDevTools';
static readonly ID = BrowserViewCommandId.ToggleDevTools;
constructor() {
super({
@@ -347,7 +347,7 @@ class ToggleDevToolsAction extends Action2 {
}
class OpenInExternalBrowserAction extends Action2 {
static readonly ID = 'workbench.action.browser.openExternal';
static readonly ID = BrowserViewCommandId.OpenExternal;
constructor() {
super({
@@ -383,7 +383,7 @@ class OpenInExternalBrowserAction extends Action2 {
}
class ClearGlobalBrowserStorageAction extends Action2 {
static readonly ID = 'workbench.action.browser.clearGlobalStorage';
static readonly ID = BrowserViewCommandId.ClearGlobalStorage;
constructor() {
super({
@@ -408,7 +408,7 @@ class ClearGlobalBrowserStorageAction extends Action2 {
}
class ClearWorkspaceBrowserStorageAction extends Action2 {
static readonly ID = 'workbench.action.browser.clearWorkspaceStorage';
static readonly ID = BrowserViewCommandId.ClearWorkspaceStorage;
constructor() {
super({
@@ -433,7 +433,7 @@ class ClearWorkspaceBrowserStorageAction extends Action2 {
}
class ClearEphemeralBrowserStorageAction extends Action2 {
static readonly ID = 'workbench.action.browser.clearEphemeralStorage';
static readonly ID = BrowserViewCommandId.ClearEphemeralStorage;
constructor() {
super({
@@ -460,7 +460,7 @@ class ClearEphemeralBrowserStorageAction extends Action2 {
}
class OpenBrowserSettingsAction extends Action2 {
static readonly ID = 'workbench.action.browser.openSettings';
static readonly ID = BrowserViewCommandId.OpenSettings;
constructor() {
super({
@@ -486,7 +486,7 @@ class OpenBrowserSettingsAction extends Action2 {
// Find actions
class ShowBrowserFindAction extends Action2 {
static readonly ID = 'workbench.action.browser.showFind';
static readonly ID = BrowserViewCommandId.ShowFind;
constructor() {
super({
@@ -515,7 +515,7 @@ class ShowBrowserFindAction extends Action2 {
}
class HideBrowserFindAction extends Action2 {
static readonly ID = 'workbench.action.browser.hideFind';
static readonly ID = BrowserViewCommandId.HideFind;
constructor() {
super({
@@ -540,7 +540,7 @@ class HideBrowserFindAction extends Action2 {
}
class BrowserFindNextAction extends Action2 {
static readonly ID = 'workbench.action.browser.findNext';
static readonly ID = BrowserViewCommandId.FindNext;
constructor() {
super({
@@ -571,7 +571,7 @@ class BrowserFindNextAction extends Action2 {
}
class BrowserFindPreviousAction extends Action2 {
static readonly ID = 'workbench.action.browser.findPrevious';
static readonly ID = BrowserViewCommandId.FindPrevious;
constructor() {
super({

View File

@@ -3,15 +3,24 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IBrowserViewService, ipcBrowserViewChannelName } from '../../../../platform/browserView/common/browserView.js';
import { BrowserViewCommandId, IBrowserViewService, ipcBrowserViewChannelName } from '../../../../platform/browserView/common/browserView.js';
import { IBrowserViewWorkbenchService, IBrowserViewModel, BrowserViewModel } from '../common/browserView.js';
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { Event } from '../../../../base/common/event.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
export class BrowserViewWorkbenchService implements IBrowserViewWorkbenchService {
/** Command IDs whose accelerators are shown in browser view context menus. */
const browserViewContextMenuCommands = [
BrowserViewCommandId.GoBack,
BrowserViewCommandId.GoForward,
BrowserViewCommandId.Reload,
];
export class BrowserViewWorkbenchService extends Disposable implements IBrowserViewWorkbenchService {
declare readonly _serviceBrand: undefined;
private readonly _browserViewService: IBrowserViewService;
@@ -20,10 +29,15 @@ export class BrowserViewWorkbenchService implements IBrowserViewWorkbenchService
constructor(
@IMainProcessService mainProcessService: IMainProcessService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IKeybindingService private readonly keybindingService: IKeybindingService
) {
super();
const channel = mainProcessService.getChannel(ipcBrowserViewChannelName);
this._browserViewService = ProxyChannel.toService<IBrowserViewService>(channel);
this.sendKeybindings();
this._register(this.keybindingService.onDidUpdateKeybindings(() => this.sendKeybindings()));
}
async getOrCreateBrowserViewModel(id: string): Promise<IBrowserViewModel> {
@@ -67,4 +81,16 @@ export class BrowserViewWorkbenchService implements IBrowserViewWorkbenchService
return model;
}
private sendKeybindings(): void {
const keybindings: { [commandId: string]: string } = Object.create(null);
for (const commandId of browserViewContextMenuCommands) {
const binding = this.keybindingService.lookupKeybinding(commandId);
const accelerator = binding?.getElectronAccelerator();
if (accelerator) {
keybindings[commandId] = accelerator;
}
}
void this._browserViewService.updateKeybindings(keybindings);
}
}