From 8f2392cf9fc7ad33bfef53bf4c6d51539402c7ca Mon Sep 17 00:00:00 2001 From: OS-josesoares Date: Fri, 23 Oct 2026 15:33:42 +0100 Subject: [PATCH] RDEV-9911 - shadow dom mouse leave --- build/monaco/package.json | 2 +- .../editor/browser/controller/mouseHandler.ts | 2 +- .../browser/controller/mouseHandler.test.ts | 71 +++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/vs/editor/test/browser/controller/mouseHandler.test.ts diff --git a/build/monaco/package.json b/build/monaco/package.json index 91ed0c1c9a1b06..7eefe26b92dab9 100644 --- a/build/monaco/package.json +++ b/build/monaco/package.json @@ -1,7 +1,7 @@ { "name": "monaco-editor-core", "private": true, - "version": "0.55.1-os1", + "version": "0.55.1-os2", "description": "A browser based code editor", "author": "Microsoft Corporation", "license": "MIT", diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 4ad4ca9d817e57..6c5f6de87c174c 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -103,7 +103,7 @@ export class MouseHandler extends ViewEventHandler { if (!this._mouseLeaveMonitor) { this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => { - if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) { + if (!this.viewHelper.viewDomNode.contains(e.composedPath()[0] as Node | null)) { // went outside the editor! this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode)); } diff --git a/src/vs/editor/test/browser/controller/mouseHandler.test.ts b/src/vs/editor/test/browser/controller/mouseHandler.test.ts new file mode 100644 index 00000000000000..71b33c345294f7 --- /dev/null +++ b/src/vs/editor/test/browser/controller/mouseHandler.test.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { mainWindow } from '../../../../base/browser/window.js'; +import { Color } from '../../../../base/common/color.js'; +import { toDisposable } from '../../../../base/common/lifecycle.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +import { CodeEditorWidget } from '../../../browser/widget/codeEditor/codeEditorWidget.js'; +import { TokenizationRegistry } from '../../../common/languages.js'; +import { instantiateTextModel } from '../../common/testTextModel.js'; +import { createCodeEditorServices } from '../testCodeEditor.js'; + +suite('MouseHandler', () => { + + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let previousColorMap: Color[] | null; + + suiteSetup(() => { + // Without a token color map, the minimap of a real editor view reports unexpected errors + previousColorMap = TokenizationRegistry.getColorMap(); + TokenizationRegistry.setColorMap([null!, Color.black, Color.white]); + }); + + suiteTeardown(() => { + TokenizationRegistry.setColorMap(previousColorMap!); + }); + + function dispatchMouseMove(target: EventTarget, clientX: number, clientY: number): void { + target.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, composed: true, clientX, clientY })); + } + + test('in an open shadow root, moving inside the editor does not fire onMouseLeave and moving outside does', () => { + // Arrange + const shadowHost = mainWindow.document.createElement('div'); + mainWindow.document.body.appendChild(shadowHost); + disposables.add(toDisposable(() => shadowHost.remove())); + const container = mainWindow.document.createElement('div'); + shadowHost.attachShadow({ mode: 'open' }).appendChild(container); + + const instantiationService = createCodeEditorServices(disposables); + const model = disposables.add(instantiateTextModel(instantiationService, 'hello world')); + const editor = disposables.add(instantiationService.createInstance(CodeEditorWidget, container, {}, { contributions: [] })); + editor.setModel(model); + editor.layout({ width: 400, height: 200 }); + + const editorDomNode = editor.getDomNode()!; + const editorRect = editorDomNode.getBoundingClientRect(); + const log: string[] = []; + disposables.add(editor.onMouseMove(() => log.push('onMouseMove'))); + disposables.add(editor.onMouseLeave(() => log.push('onMouseLeave'))); + + // Act + log.push('move inside'); + dispatchMouseMove(editorDomNode, editorRect.left + 10, editorRect.top + 10); + log.push('move inside'); + dispatchMouseMove(editorDomNode, editorRect.left + 20, editorRect.top + 10); + log.push('move outside'); + dispatchMouseMove(mainWindow.document.body, editorRect.left + 10, editorRect.bottom + 10); + + // Assert + assert.deepStrictEqual(log, [ + 'move inside', 'onMouseMove', + 'move inside', 'onMouseMove', + 'move outside', 'onMouseLeave', + ]); + }); +});