Skip to content

Moving away from execCommand for pasting #239233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { EditContext } from './editContextFactory.js';
import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';
import { NativeEditContextRegistry } from './nativeEditContextRegistry.js';
import { IEditorAriaOptions } from '../../../editorBrowser.js';
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';

// Corresponds to classes in nativeEditContext.css
enum CompositionClassName {
Expand Down Expand Up @@ -68,7 +69,8 @@ export class NativeEditContext extends AbstractEditContext {
viewController: ViewController,
private readonly _visibleRangeProvider: IVisibleRangeProvider,
@IInstantiationService instantiationService: IInstantiationService,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
@IClipboardService private readonly _clipboardService: IClipboardService,
) {
super(context);

Expand Down Expand Up @@ -253,24 +255,26 @@ export class NativeEditContext extends AbstractEditContext {
return true;
}

public executePaste(): boolean {
public triggerPaste(): Promise | undefined {
this._onWillPaste();
try {
// pause focus tracking because we don't want to react to focus/blur
// events while pasting since we move the focus to the textarea
this._focusTracker.pause();

// Since we can not call execCommand('paste') on a dom node with edit context set
// we added a hidden text area that receives the paste execution
this._textArea.focus();
const result = this._textArea.domNode.ownerDocument.execCommand('paste');
this._textArea.domNode.textContent = '';
this.domNode.focus();

return result;
} finally {
// pause focus tracking because we don't want to react to focus/blur
// events while pasting since we move the focus to the textarea
this._focusTracker.pause();

// Since we can not call execCommand('paste') on a dom node with edit context set
// we added a hidden text area that receives the paste execution
this._textArea.focus();
const triggerPaste = this._clipboardService.triggerPaste();
if (!triggerPaste) {
this.domNode.domNode.focus();
this._focusTracker.resume(); // resume focus tracking
return undefined;
}
return triggerPaste.then(() => {
this._textArea.domNode.textContent = '';
this.domNode.domNode.focus();
this._focusTracker.resume(); // resume focus tracking
});
}

private _onWillPaste(): void {
Expand Down
25 changes: 15 additions & 10 deletions src/vs/editor/contrib/clipboard/browser/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,21 +234,26 @@ if (PasteAction) {
const focusedEditor = codeEditorService.getFocusedCodeEditor();
if (focusedEditor && focusedEditor.hasModel() && focusedEditor.hasTextFocus()) {
// execCommand(paste) does not work with edit context
let result: boolean;
const experimentalEditContextEnabled = focusedEditor.getOption(EditorOption.effectiveExperimentalEditContextEnabled);
if (experimentalEditContextEnabled) {
const nativeEditContext = NativeEditContextRegistry.get(focusedEditor.getId());
if (nativeEditContext) {
result = nativeEditContext.executePaste();
} else {
result = false;
const triggerPaste = nativeEditContext.triggerPaste();
if (triggerPaste) {
return triggerPaste.then(async () => {
return CopyPasteController.get(focusedEditor)?.finishedPaste() ?? Promise.resolve();
});
}
}
} else {
result = focusedEditor.getContainerDomNode().ownerDocument.execCommand('paste');
const triggerPaste = clipboardService.triggerPaste();
if (triggerPaste) {
return triggerPaste.then(async () => {
return CopyPasteController.get(focusedEditor)?.finishedPaste() ?? Promise.resolve();
});
}
}
if (result) {
return CopyPasteController.get(focusedEditor)?.finishedPaste() ?? Promise.resolve();
} else if (platform.isWeb) {
if (platform.isWeb) {
// Use the clipboard service if document.execCommand('paste') was not successful
return (async () => {
const clipboardText = await clipboardService.readText();
Expand Down Expand Up @@ -278,8 +283,8 @@ if (PasteAction) {

// 2. Paste: (default) handle case when focus is somewhere else.
PasteAction.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => {
getActiveDocument().execCommand('paste');
return true;
const triggerPaste = accessor.get(IClipboardService).triggerPaste();
return triggerPaste ?? false;
});
}

Expand Down
4 changes: 4 additions & 0 deletions src/vs/platform/clipboard/browser/clipboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer
}, { window: mainWindow, disposables: this._store }));
}

triggerPaste(): Promise | undefined {
return undefined;
}

async readImage(): Promise {
try {
const clipboardItems = await navigator.clipboard.read();
Expand Down
5 changes: 5 additions & 0 deletions src/vs/platform/clipboard/common/clipboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export interface IClipboardService {

readonly _serviceBrand: undefined;

/**
* Trigger the paste. Returns undefined if the paste was not triggered or a promise that resolves on paste end.
*/
triggerPaste(): Promise | undefined;

/**
* Writes text to the system clipboard.
*/
Expand Down
4 changes: 4 additions & 0 deletions src/vs/platform/clipboard/test/common/testClipboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class TestClipboardService implements IClipboardService {

private text: string | undefined = undefined;

triggerPaste(): Promise | undefined {
return Promise.resolve();
}

async writeText(text: string, type?: string): Promise {
this.text = text;
}
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/native/common/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export interface ICommonNativeHostService {
killProcess(pid: number, code: string): Promise;

// Clipboard
triggerPaste(): Promise;
readClipboardText(type?: 'selection' | 'clipboard'): Promise;
writeClipboardText(text: string, type?: 'selection' | 'clipboard'): Promise;
readClipboardFindText(): Promise;
Expand Down
5 changes: 5 additions & 0 deletions src/vs/platform/native/electron-main/nativeHostMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,11 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
return clipboard.readText(type);
}

async triggerPaste(windowId: number | undefined): Promise {
const window = this.windowById(windowId);
return window?.win?.webContents.paste() ?? Promise.resolve();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fyi, no need to use Promise.resolve()

}

async readImage(): Promise {
return clipboard.readImage().toPNG();
}
Expand Down
5 changes: 1 addition & 4 deletions src/vs/platform/windows/electron-main/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,7 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt
// Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624
// Refs https://github.com/microsoft/vscode/issues/140098
enableBlinkFeatures: 'HighlightAPI',
sandbox: true,
// TODO(deepak1556): Should be removed once migration is complete
// https://github.com/microsoft/vscode/issues/239228
enableDeprecatedPaste: true,
sandbox: true
},
experimentalDarkMode: true
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export class NativeClipboardService implements IClipboardService {
@INativeHostService private readonly nativeHostService: INativeHostService
) { }

async triggerPaste(): Promise {
return this.nativeHostService.triggerPaste();
}

async readImage(): Promise {
return this.nativeHostService.readImage();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export class TestNativeHostService implements INativeHostService {
async readClipboardFindText(): Promise { return ''; }
async writeClipboardFindText(text: string): Promise { }
async writeClipboardBuffer(format: string, buffer: VSBuffer, type?: 'selection' | 'clipboard' | undefined): Promise { }
async triggerPaste(): Promise { }
async readImage(): Promise { return Uint8Array.from([]); }
async readClipboardBuffer(format: string): Promise { return VSBuffer.wrap(Uint8Array.from([])); }
async hasClipboard(format: string, type?: 'selection' | 'clipboard' | undefined): Promise { return false; }
Expand Down
Loading