[Bug] Firefox 131: Selection issues when Monaco is in shadow dom
Reproducible in vscode.dev or in VS Code Desktop?
- [X] Not reproducible in vscode.dev or VS Code Desktop
Reproducible in the monaco editor playground?
- [ ] Not reproducible in the monaco editor playground
Monaco Editor Playground Link
https://microsoft.github.io/monaco-editor/playground.html?source=v0.51.0#example-creating-the-editor-web-component
Monaco Editor Playground Code
customElements.define(
"code-view-monaco",
class CodeViewMonaco extends HTMLElement {
_monacoEditor;
/** @type HTMLElement */
_editor;
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
// Copy over editor styles
const styles = document.querySelectorAll(
"link[rel='stylesheet'][data-name^='vs/']"
);
for (const style of styles) {
shadowRoot.appendChild(style.cloneNode(true));
}
const template = /** @type HTMLTemplateElement */ (
document.getElementById("editor-template")
);
shadowRoot.appendChild(template.content.cloneNode(true));
this._editor = shadowRoot.querySelector("#container");
this._monacoEditor = monaco.editor.create(this._editor, {
automaticLayout: true,
language: "html",
value: `<div>Hello World</div>`,
});
}
}
);
<template id="editor-template">
<div
id="container"
style="overflow: hidden; width: 100%; height: 100%; position: absolute"
></div>
</template>
<code-view-monaco></code-view-monaco>
Reproduction Steps
- Monaco is mounted in the shadow dom of a custom element.
- Browser is Firefox 131 (I used
brew install firefox@developer-editionto install)
Actual (Problematic) Behavior
Text selection via mouse does not work correctly, and editing with an active selection has no effect.
https://github.com/user-attachments/assets/026ff3b6-2385-4a94-b7cc-e074b66ebf84
Expected Behavior
Text selection should work normally.
Additional Context
No response
I did a bit of digging here and it seems related to Firefox updating its implementation of document.caretPositionFromPoint with respect to shadow dom. (Github, Bugzilla)
The updated implementation seems to respect the options parameter (CSSOM).
I made a codepen to demonstrate. If you open this in Firefox 131 you'll notice that clicking on the textarea (which is in the shadow-dom) produces different results depending on whether you include options["shadowRoots"]. Most notably:
If you don't include a reference to the shadow-root in the third parameter, the function will just return the shadow-root.
If you open the codepen in a lower version of Firefox, there's no difference in behavior, and it always returns the textarea even if the shadow-root parameter is omitted. I also noticed that Chrome seems to match FF 131. However, in Monaco Chrome doesn't use the caretPositionFromPoint, it uses caretRangeFromPoint which is not supported by FF.
The caretPositionFromPoint function is used in mouseTarget._doHitTestWithCaretPositionFromPoint here - seems that to continue using this, we'll need to pass in a third parameter with a reference to the shadow-root. It also seems adding it won't have any effect on lower versions of FF.
I have the same problem, but after the Firefox update, I can't select anything. Not even incorrectly.
Same for my project :(
Hoping to make a PR to vscode fixing this issue, as we were able to resolve it by patching Monaco for our project. Here's the fix in case anyone has some free time - it's super simple it's just I've never made contributions to vscode so I'm putting it off until I have some free time to setup:
This line here:
const hitResult = document.caretPositionFromPoint(coords.clientX, coords.clientY);
Should instead be:
const shadowRoot = dom.getShadowRoot(ctx.viewDomNode);
const hitResult = shadowRoot ?
document.caretPositionFromPoint(coords.clientX, coords.clientY, { shadowRoots: [ shadowRoot ] }) :
document.caretPositionFromPoint(coords.clientX, coords.clientY);
Having the same issue in the CrowdStrike Advanced Search interface. It is utilizing Monaco, and I cannot select any text. It is completely broken, but just in Firefox (running version 132.0.1)
For anyone that wants to patch the minified version on the fly during a CI step, here is a code snipped that is based on this code (https://github.com/microsoft/monaco-editor/issues/3409#issuecomment-1862083015), which already patches another bug, that probably will never get fixed. All credit goes to @nate-bow-db for posting/finding the solution 😄
const fs = require('fs');
console.log("fix ./node_modules/monaco-editor/min/vs/editor/editor.main.js");
fs.readFile("./node_modules/monaco-editor/min/vs/editor/editor.main.js", function (err, buf) {
let code = buf.toString();
//fix for monaco editor (issue https://github.com/microsoft/monaco-editor/issues/3409)
let rgx = /(.)=>{this\.viewHelper\.viewDomNode\.contains\((.)\.target\)/;
let newcode = code.replace(rgx, '$1=>{this.viewHelper.viewDomNode.contains($1.composedPath()[0])');
//fix for firefox 131+ issue (issue: https://github.com/microsoft/monaco-editor/issues/4679#issuecomment-2406284453)
rgx = /=(.)\.getShadowRoot\((.)\.viewDomNode\)/;
let matches = newcode.match(rgx);
rgx = /static _doHitTestWithCaretPositionFromPoint\((.),(.)\)\{const (.)=document\.caretPositionFromPoint\((.).clientX,(.).clientY\);/;
newcode = newcode.replace(rgx, "static _doHitTestWithCaretPositionFromPoint($1,$2){const shadowRoot=" + matches[1] + ".getShadowRoot($1.viewDomNode);const $3=shadowRoot?document.caretPositionFromPoint($2.clientX,$2.clientY,{shadowRoots:[shadowRoot]}):document.caretPositionFromPoint($2.clientX,$2.clientY);");
if (code != newcode) {
console.log("patched monaco editor");
fs.writeFile("./node_modules/monaco-editor/min/vs/editor/editor.main.js", newcode, (err) => {
if (err) console.log(err);
});
}
});
@paresy
For anyone that wants to patch the minified version on the fly during a CI step, here is a code snipped that is based on this code (#3409 (comment)), which already patches another bug, that probably will never get fixed.
yeah, seems to be badly supported
see also: https://bugzilla.mozilla.org/show_bug.cgi?id=1927838
A quick fix for some cases can be:
class MonacoEditor extends HTMLElement {
constructor() { super(); } get shadowRoot() { return this.d; }
async connectedCallback() { if (this.connectedCallback.called) return; this.connectedCallback.called = true;
this.style.display = 'contents';
this.innerHTML = `<iframe style=width:100%;height:100%;border:0></iframe>`;
this.f = this.querySelector('iframe');
this.f.srcdoc = `
<link rel=stylesheet href=monaco.css>
<style>body { margin: 0; overflow: hidden; }</style>
<main id=m style="width:100dvw;height:100dvh"></main>`.trim();
await new Promise(r => this.f.onload = r);
const base = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/';
const doc = this.d = this.f.contentDocument;
const script = doc.createElement('script');
/**/ script.src = `${base}${this.version}/min/vs/loader.min.js`;
/**/ doc.body.append(script);
await new Promise(r => script.onload = r);
const win = this.w = this.f.contentWindow; this.d.m = win.m;
win.require.config({ paths: { vs: `${base}${this.version}/min/vs` }});
win.require(['vs/editor/editor.main'], () => {
Object.assign(window, { monaco: win.monaco });
this.editor = this.m = win.monaco.editor.create(win.m, this.config ?? {
value: 'function hello() {\n console.log("Hello, world!"); \n retur "Hello"; \n}',
language: 'javascript', theme: 'vs-dark',
placeholder: 'Start coding!',
automaticLayout: true,
});
['keydown', 'keyup', 'keypress'].forEach(type =>
win.addEventListener(type, e => ((e.ctrlKey || e.metaKey) && e.key === 'o' ? (e.preventDefault(), this) : this).dispatchEvent(new e.constructor(e.type, e)))
);
this.editorReady?.(); // subclass entry point
});
}
get version() { return this.getAttribute('version') ?? '0.52.0'; }
get value() { return this.editor?.getValue(); }
set value(v) { this.editor?.setValue(v); }
}
customElements.define('monaco-editor', MonacoEditor);
I also noticed that text selection causes a JS error can't access property "offsetNode", hitResult is null to be thrown in Firefox (currently on v145), and this issue seems to be the root cause. I can reproduce on https://microsoft.github.io/monaco-editor/playground.html?source=v0.55.1#example-creating-the-editor-hello-world by just selecting all text in Monaco:
I have also noticed the same error on both playground and bundled editor, which happens when selecting text with mouse in the editor and the mouse moves out of the <div class="monaco-scrollable-element editor-scrollable vs" ...>
Uncaught Error: can't access property "offsetNode", i is null