commit 116db2ec1d74093d7c036d053a846f56bd20b93d
parent 1d6c9204d4514de994d558529db4e5617ba114ff
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date: Fri, 19 Dec 2025 14:03:48 +0000
Bug 2005582 - [devtools] Fix copying from markup view HTML editor. r=devtools-reviewers,ochameau.
With the migration to CodeMirror 6, the copy event listener callback that
is set in the Markup view would not see the element as being an editor (it
used to have a textarea for the CodeMirror editor, but not anymore), and
so proceeds to copy the outerHTML of the selected node instead.
This is fixed by checking if the event target is in a CodeMirror instance,
on top of checking if it's an input or a textarea.
A test case is added to cover the fix.
Differential Revision: https://phabricator.services.mozilla.com/D277026
Diffstat:
2 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/devtools/client/inspector/markup/markup.js b/devtools/client/inspector/markup/markup.js
@@ -1311,7 +1311,7 @@ class MarkupView extends EventEmitter {
_onCopy(evt) {
// Ignore copy events from editors
- if (this._isInputOrTextarea(evt.target)) {
+ if (this._isInputOrTextareaOrInCodeMirrorEditor(evt.target)) {
return;
}
@@ -1453,7 +1453,7 @@ class MarkupView extends EventEmitter {
* Key shortcut listener.
*/
_onShortcut(name, event) {
- if (this._isInputOrTextarea(event.target)) {
+ if (this._isInputOrTextareaOrInCodeMirrorEditor(event.target)) {
return;
}
@@ -1477,11 +1477,19 @@ class MarkupView extends EventEmitter {
}
/**
- * Check if a node is an input or textarea
+ * Check if a node is used to type text (i.e. an input or textarea, or in a CodeMirror editor)
*/
- _isInputOrTextarea(element) {
+ _isInputOrTextareaOrInCodeMirrorEditor(element) {
const name = element.tagName.toLowerCase();
- return name === "input" || name === "textarea";
+ if (name === "input" || name === "textarea") {
+ return true;
+ }
+
+ if (element.closest(".cm-editor")) {
+ return true;
+ }
+
+ return false;
}
/**
diff --git a/devtools/client/inspector/markup/test/browser_markup_html_edit_03.js b/devtools/client/inspector/markup/test/browser_markup_html_edit_03.js
@@ -28,6 +28,9 @@ add_task(async function () {
info("Check that pressing escape cancels edits");
await testEscapeCancels(inspector);
+ info("Check that copying seletected text in editor works as expected");
+ await testCopyTextSelection(inspector);
+
info("Check that pressing F2 commits edits");
await testF2Commits(inspector);
@@ -58,6 +61,7 @@ async function testEscapeCancels(inspector) {
"The node is starting with old HTML."
);
+ info("Check that copying from the editor does work as expected");
inspector.markup.htmlEditor.editor.setText(NEW_HTML);
const onEditorHiddem = once(inspector.markup.htmlEditor, "popuphidden");
@@ -72,6 +76,32 @@ async function testEscapeCancels(inspector) {
);
}
+async function testCopyTextSelection(inspector) {
+ await selectNode(SELECTOR, inspector);
+
+ const onHtmlEditorCreated = once(inspector.markup, "begin-editing");
+ EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
+ await onHtmlEditorCreated;
+ ok(inspector.markup.htmlEditor.isVisible, "HTML Editor is visible");
+
+ info("Check that copying from the editor does work as expected");
+ inspector.markup.htmlEditor.editor.setText(NEW_HTML);
+ // Select the "div" word in the editor
+ inspector.markup.htmlEditor.editor.setSelectionAt(
+ { line: 1, column: 1 },
+ { line: 1, column: 4 }
+ );
+ await waitForClipboardPromise(() => {
+ EventUtils.synthesizeKey("c", { accelKey: true });
+ }, `div`);
+ ok(true, "Expected text was copied to clipboard");
+
+ // Close the editor
+ const onEditorHiddem = once(inspector.markup.htmlEditor, "popuphidden");
+ EventUtils.sendKey("ESCAPE", inspector.markup.htmlEditor.doc.defaultView);
+ await onEditorHiddem;
+}
+
async function testF2Commits(inspector) {
const onEditorShown = once(inspector.markup.htmlEditor, "popupshown");
inspector.markup._frame.contentDocument.documentElement.focus();