From d06f28e39a7f47ac43fcbd823a2058b88b529b05 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 7 Jan 2026 14:13:27 +0100 Subject: [PATCH 1/2] moving back to execCommand('paste') --- .../editContext/native/nativeEditContext.css | 3 +- .../editContext/native/nativeEditContext.ts | 30 +++++++++++- .../native/screenReaderContentRich.ts | 4 ++ .../native/screenReaderContentSimple.ts | 4 ++ .../editContext/native/screenReaderSupport.ts | 4 ++ .../editContext/native/screenReaderUtils.ts | 5 ++ .../contrib/clipboard/browser/clipboard.ts | 48 ++++++------------- 7 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css index 00170ceefc686..c2bc46f0d31d1 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css @@ -13,7 +13,8 @@ white-space: pre-wrap; } -.monaco-editor .ime-text-area { +.monaco-editor .ime-text-area, +.monaco-editor .native-edit-context-textarea { min-width: 0; min-height: 0; margin: 0; diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 369f42f3eb7f7..5f5d782c14af4 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -53,6 +53,7 @@ export class NativeEditContext extends AbstractEditContext { // Text area used to handle paste events public readonly domNode: FastDomNode; + private readonly _pasteTextArea: FastDomNode; private readonly _imeTextArea: FastDomNode; private readonly _editContext: EditContext; private readonly _screenReaderSupport: ScreenReaderSupport; @@ -85,6 +86,10 @@ export class NativeEditContext extends AbstractEditContext { this.domNode = new FastDomNode(document.createElement('div')); this.domNode.setClassName(`native-edit-context`); this._imeTextArea = new FastDomNode(document.createElement('textarea')); + this._pasteTextArea = new FastDomNode(document.createElement('textarea')); + this._pasteTextArea.setClassName('native-edit-context-textarea'); + this._pasteTextArea.setAttribute('tabindex', '-1'); + this._pasteTextArea.setAttribute('aria-hidden', 'true'); this._imeTextArea.setClassName(`ime-text-area`); this._imeTextArea.setAttribute('readonly', 'true'); this._imeTextArea.setAttribute('tabindex', '-1'); @@ -97,6 +102,7 @@ export class NativeEditContext extends AbstractEditContext { this._updateDomAttributes(); overflowGuardContainer.appendChild(this.domNode); + overflowGuardContainer.appendChild(this._pasteTextArea); overflowGuardContainer.appendChild(this._imeTextArea); this._parent = overflowGuardContainer.domNode; @@ -139,8 +145,9 @@ export class NativeEditContext extends AbstractEditContext { this._onType(this._viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); } })); - this._register(addDisposableListener(this.domNode.domNode, 'paste', (e) => { + this._register(addDisposableListener(this._pasteTextArea.domNode, 'paste', (e) => { this.logService.trace('NativeEditContext#paste'); + this._screenReaderSupport.onPaste(); const pasteData = computePasteData(e, this._context, this.logService); if (!pasteData) { return; @@ -217,9 +224,30 @@ export class NativeEditContext extends AbstractEditContext { this.domNode.domNode.blur(); this.domNode.domNode.remove(); this._imeTextArea.domNode.remove(); + this._pasteTextArea.domNode.remove(); super.dispose(); } + public executePaste(): boolean { + 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._pasteTextArea.focus(); + const result = this._pasteTextArea.domNode.ownerDocument.execCommand('paste'); + this._pasteTextArea.domNode.textContent = ''; + this.domNode.focus(); + return result; + } finally { + // resume focus tracking + this._focusTracker.resume(); + } + } + public setAriaOptions(options: IEditorAriaOptions): void { this._screenReaderSupport.setAriaOptions(options); } diff --git a/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts b/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts index 7a274e27f3beb..a135eab812e64 100644 --- a/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts +++ b/src/vs/editor/browser/controller/editContext/native/screenReaderContentRich.ts @@ -96,6 +96,10 @@ export class RichScreenReaderContent extends Disposable implements IScreenReader this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); } + public onPaste(): void { + this._setIgnoreSelectionChangeTime('onPaste'); + } + public onWillCut(): void { this._setIgnoreSelectionChangeTime('onCut'); } diff --git a/src/vs/editor/browser/controller/editContext/native/screenReaderContentSimple.ts b/src/vs/editor/browser/controller/editContext/native/screenReaderContentSimple.ts index 308d252fcf725..0ccf18a183c71 100644 --- a/src/vs/editor/browser/controller/editContext/native/screenReaderContentSimple.ts +++ b/src/vs/editor/browser/controller/editContext/native/screenReaderContentSimple.ts @@ -95,6 +95,10 @@ export class SimpleScreenReaderContent extends Disposable implements IScreenRead this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); } + public onPaste(): void { + this._setIgnoreSelectionChangeTime('onPaste'); + } + public onWillCut(): void { this._setIgnoreSelectionChangeTime('onCut'); } diff --git a/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts b/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts index 86e6faece5a60..c6f65076207e9 100644 --- a/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts +++ b/src/vs/editor/browser/controller/editContext/native/screenReaderSupport.ts @@ -50,6 +50,10 @@ export class ScreenReaderSupport extends Disposable { this._updateDomAttributes(); } + public onPaste(): void { + this._state.value?.onPaste(); + } + public onWillPaste(): void { this._state.value?.onWillPaste(); } diff --git a/src/vs/editor/browser/controller/editContext/native/screenReaderUtils.ts b/src/vs/editor/browser/controller/editContext/native/screenReaderUtils.ts index 882990b6eacbd..fefe41400ef7b 100644 --- a/src/vs/editor/browser/controller/editContext/native/screenReaderUtils.ts +++ b/src/vs/editor/browser/controller/editContext/native/screenReaderUtils.ts @@ -10,6 +10,11 @@ export interface IScreenReaderContent { dispose(): void; + /** + * Handle screen reader content on pasting the content + */ + onPaste(): void; + /** * Handle screen reader content before cutting the content */ diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 751ef28109f30..fe8686c81d0c5 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from '../../../../base/browser/browser.js'; -import { getActiveDocument, getActiveWindow } from '../../../../base/browser/dom.js'; +import { getActiveDocument } from '../../../../base/browser/dom.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import * as platform from '../../../../base/common/platform.js'; import * as nls from '../../../../nls.js'; @@ -14,7 +14,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { CopyOptions, generateDataToCopyAndStoreInMemory, InMemoryClipboardMetadataManager, PasteOptions } from '../../../browser/controller/editContext/clipboardUtils.js'; +import { CopyOptions, generateDataToCopyAndStoreInMemory, InMemoryClipboardMetadataManager } from '../../../browser/controller/editContext/clipboardUtils.js'; import { NativeEditContextRegistry } from '../../../browser/controller/editContext/native/nativeEditContextRegistry.js'; import { IActiveCodeEditor, ICodeEditor } from '../../../browser/editorBrowser.js'; import { Command, EditorAction, MultiCommand, registerEditorAction } from '../../../browser/editorExtensions.js'; @@ -308,41 +308,21 @@ if (PasteAction) { const focusedEditor = codeEditorService.getFocusedCodeEditor(); if (focusedEditor && focusedEditor.hasModel() && focusedEditor.hasTextFocus()) { // execCommand(paste) does not work with edit context - const editContextEnabled = focusedEditor.getOption(EditorOption.effectiveEditContext); - if (editContextEnabled) { + let result: boolean; + const editContext = focusedEditor.getOption(EditorOption.editContext); + if (editContext) { const nativeEditContext = NativeEditContextRegistry.get(focusedEditor.getId()); if (nativeEditContext) { - nativeEditContext.onWillPaste(); + result = nativeEditContext.executePaste(); + } else { + result = false; } - } - - logService.trace('registerExecCommandImpl (before triggerPaste)'); - PasteOptions.electronBugWorkaroundPasteEventHasFired = false; - logService.trace('(before triggerPaste) PasteOptions.electronBugWorkaroundPasteEventHasFired : ', PasteOptions.electronBugWorkaroundPasteEventHasFired); - const triggerPaste = clipboardService.triggerPaste(getActiveWindow().vscodeWindowId); - if (triggerPaste) { - logService.trace('registerExecCommandImpl (triggerPaste defined)'); - PasteOptions.electronBugWorkaroundPasteEventLock = false; - return triggerPaste.then(async () => { - logService.trace('(triggerPaste) PasteOptions.electronBugWorkaroundPasteEventHasFired : ', PasteOptions.electronBugWorkaroundPasteEventHasFired); - if (PasteOptions.electronBugWorkaroundPasteEventHasFired === false) { - // Ensure this doesn't run twice, what appears to be happening is - // triggerPasteis called once but it's handler is called multiple times - // when it reproduces - logService.trace('(triggerPaste) PasteOptions.electronBugWorkaroundPasteEventLock : ', PasteOptions.electronBugWorkaroundPasteEventLock); - if (PasteOptions.electronBugWorkaroundPasteEventLock === true) { - return; - } - PasteOptions.electronBugWorkaroundPasteEventLock = true; - return pasteWithNavigatorAPI(focusedEditor, clipboardService, logService); - } - logService.trace('registerExecCommandImpl (after triggerPaste)'); - return CopyPasteController.get(focusedEditor)?.finishedPaste() ?? Promise.resolve(); - }); } else { - logService.trace('registerExecCommandImpl (triggerPaste undefined)'); + result = focusedEditor.getContainerDomNode().ownerDocument.execCommand('paste'); } - if (platform.isWeb) { + if (result) { + return CopyPasteController.get(focusedEditor)?.finishedPaste() ?? Promise.resolve(); + } else if (platform.isWeb) { logService.trace('registerExecCommandImpl (Paste handling on web)'); // Use the clipboard service if document.execCommand('paste') was not successful return pasteWithNavigatorAPI(focusedEditor, clipboardService, logService); @@ -356,8 +336,8 @@ if (PasteAction) { PasteAction.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: unknown) => { const logService = accessor.get(ILogService); logService.trace('registerExecCommandImpl (addImplementation generic-dom for : paste)'); - const triggerPaste = accessor.get(IClipboardService).triggerPaste(getActiveWindow().vscodeWindowId); - return triggerPaste ?? false; + getActiveDocument().execCommand('paste'); + return true; }); } From 8ddbe619242f7e193c3406fb2c86306253e29622 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 7 Jan 2026 14:58:52 +0100 Subject: [PATCH 2/2] removing global booleans, unless going to apply them on edit context --- .../editor/browser/controller/editContext/clipboardUtils.ts | 1 - src/vs/editor/contrib/clipboard/browser/clipboard.ts | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts index edfb62977d35f..28b91e56c9fa0 100644 --- a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts +++ b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts @@ -170,7 +170,6 @@ export const CopyOptions = { export const PasteOptions = { electronBugWorkaroundPasteEventHasFired: false, - electronBugWorkaroundPasteEventLock: false }; interface InMemoryClipboardMetadata { diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index fe8686c81d0c5..c7614da38cf7f 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -14,7 +14,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { CopyOptions, generateDataToCopyAndStoreInMemory, InMemoryClipboardMetadataManager } from '../../../browser/controller/editContext/clipboardUtils.js'; +import { CopyOptions, generateDataToCopyAndStoreInMemory, InMemoryClipboardMetadataManager, PasteOptions } from '../../../browser/controller/editContext/clipboardUtils.js'; import { NativeEditContextRegistry } from '../../../browser/controller/editContext/native/nativeEditContextRegistry.js'; import { IActiveCodeEditor, ICodeEditor } from '../../../browser/editorBrowser.js'; import { Command, EditorAction, MultiCommand, registerEditorAction } from '../../../browser/editorExtensions.js'; @@ -313,7 +313,11 @@ if (PasteAction) { if (editContext) { const nativeEditContext = NativeEditContextRegistry.get(focusedEditor.getId()); if (nativeEditContext) { + PasteOptions.electronBugWorkaroundPasteEventHasFired = false; result = nativeEditContext.executePaste(); + if (PasteOptions.electronBugWorkaroundPasteEventHasFired === false) { + return pasteWithNavigatorAPI(focusedEditor, clipboardService, logService); + } } else { result = false; }