Bug 968104, paste unformatted shortcut (shift+ctrl/cmd+v) should work in plain text contexts, such as input and textarea, r=masayuki

Differential Revision: https://phabricator.services.mozilla.com/D150806
This commit is contained in:
Neil Deakin 2022-07-11 08:58:35 +00:00
parent 65711751b2
commit 00acc7206b
4 changed files with 147 additions and 5 deletions

View File

@ -178,6 +178,12 @@ ShortcutKeyData ShortcutKeys::sInputHandlers[] = {
{u"keypress", nullptr, u"z", u"accel", u"cmd_undo"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"z", u"accel,shift", u"cmd_redo"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"v", u"accel,shift", u"cmd_paste"}, // Win, macOS, Linux, Android, Emacs
// Mac uses Option+Shift+Command+V for Paste and Match Style
#if defined(MOZ_WIDGET_COCOA)
{u"keypress", nullptr, u"v", u"accel,alt,shift", u"cmd_paste"}, // macOS
#endif // MOZ_WIDGET_COCOA
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) ||\
defined(USE_EMACS_KEY_BINDINGS)
{u"keypress", nullptr, u"y", u"accel", u"cmd_redo"}, // Win, Linux, Emacs
@ -315,6 +321,12 @@ ShortcutKeyData ShortcutKeys::sTextAreaHandlers[] = {
{u"keypress", u"VK_INSERT", nullptr, u"shift", u"cmd_paste"}, // Win, Emacs
#endif // XP_WIN || USE_EMACS_KEY_BINDINGS
{u"keypress", nullptr, u"v", u"accel,shift", u"cmd_paste"}, // Win, macOS, Linux, Android, Emacs
// Mac uses Option+Shift+Command+V for Paste and Match Style
#if defined(MOZ_WIDGET_COCOA)
{u"keypress", nullptr, u"v", u"accel,alt,shift", u"cmd_paste"}, // macOS
#endif // MOZ_WIDGET_COCOA
/**************************************************************************
* Delete key in <textarea>.
**************************************************************************/
@ -544,11 +556,17 @@ ShortcutKeyData ShortcutKeys::sBrowserHandlers[] = {
/**************************************************************************
* Common editor commands in non-editable element.
**************************************************************************/
{u"keypress", nullptr, u"c", u"accel", u"cmd_copy"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"x", u"accel", u"cmd_cut"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"v", u"accel", u"cmd_paste"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"z", u"accel", u"cmd_undo"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"z", u"accel,shift", u"cmd_redo"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"c", u"accel", u"cmd_copy"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"x", u"accel", u"cmd_cut"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"v", u"accel", u"cmd_paste"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"v", u"accel,shift", u"cmd_pasteNoFormatting"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"z", u"accel", u"cmd_undo"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"z", u"accel,shift", u"cmd_redo"}, // Win, macOS, Linux, Android, Emacs
// Mac uses Option+Shift+Command+V for Paste and Match Style
#if defined(MOZ_WIDGET_COCOA)
{u"keypress", nullptr, u"v", u"accel,alt,shift", u"cmd_pasteNoFormatting"}, // macOS
#endif // MOZ_WIDGET_COCOA
#if defined(XP_WIN)
{u"keypress", nullptr, u"y", u"accel", u"cmd_redo"}, // Win
@ -714,6 +732,7 @@ ShortcutKeyData ShortcutKeys::sEditorHandlers[] = {
{u"keypress", nullptr, u"z", u"accel", u"cmd_undo"}, // Win, macOS, Linux, Android, Emacs
{u"keypress", nullptr, u"z", u"accel,shift", u"cmd_redo"}, // Win, macOS, Linux, Android, Emacs
// Mac uses Option+Shift+Command+V for Paste and Match Style
#if defined(MOZ_WIDGET_COCOA)
{u"keypress", nullptr, u"v", u"accel,alt,shift", u"cmd_pasteNoFormatting"}, // macOS
#endif // MOZ_WIDGET_COCOA

View File

@ -298,6 +298,10 @@ TEST(ShortcutKeyDefinitions, HTMLInputElement)
{0, 'y', MODIFIER_ACCEL, u"cmd_redo", nullptr, u"cmd_redo", nullptr, u"cmd_redo"},
{0, 'z', MODIFIER_ACCEL, u"cmd_undo", u"cmd_undo", u"cmd_undo", u"cmd_undo", u"cmd_undo"},
// charCode Modifiers, Windows macOS Linux Android Emacs
{0, 'v', MODIFIER_SHIFT | MODIFIER_ACCEL, u"cmd_paste", u"cmd_paste", u"cmd_paste", u"cmd_paste", u"cmd_paste"},
{0, 'v', MODIFIER_SHIFT | MODIFIER_ALT | MODIFIER_ACCEL, nullptr, u"cmd_paste", nullptr, nullptr, nullptr},
// charCode Modifiers Windows macOS Linux Android Emacs
{0, 'z', MODIFIER_SHIFT | MODIFIER_ACCEL, u"cmd_redo", u"cmd_redo", u"cmd_redo", u"cmd_redo", u"cmd_redo"},
@ -396,6 +400,10 @@ TEST(ShortcutKeyDefinitions, HTMLTextAreaElement)
{0, 'y', MODIFIER_ACCEL, u"cmd_redo", nullptr, u"cmd_redo", nullptr, u"cmd_redo"},
{0, 'z', MODIFIER_ACCEL, u"cmd_undo", u"cmd_undo", u"cmd_undo", u"cmd_undo", u"cmd_undo"},
// charCode Modifiers, Windows macOS Linux Android Emacs
{0, 'v', MODIFIER_SHIFT | MODIFIER_ACCEL, u"cmd_paste", u"cmd_paste", u"cmd_paste", u"cmd_paste", u"cmd_paste"},
{0, 'v', MODIFIER_SHIFT | MODIFIER_ALT | MODIFIER_ACCEL, nullptr, u"cmd_paste", nullptr, nullptr, nullptr},
// charCode Modifiers Windows macOS Linux Android Emacs
{0, 'z', MODIFIER_SHIFT | MODIFIER_ACCEL, u"cmd_redo", u"cmd_redo", u"cmd_redo", u"cmd_redo", u"cmd_redo"},
@ -497,6 +505,12 @@ TEST(ShortcutKeyDefinitions, Browser)
{0, 'y', MODIFIER_ACCEL, u"cmd_redo", nullptr, nullptr, nullptr, nullptr},
{0, 'z', MODIFIER_ACCEL, u"cmd_undo", u"cmd_undo", u"cmd_undo", u"cmd_undo", u"cmd_undo"},
// charCode Modifiers, Windows macOS Linux Android Emacs
{0, 'v', MODIFIER_SHIFT | MODIFIER_ACCEL, u"cmd_pasteNoFormatting", u"cmd_pasteNoFormatting", u"cmd_pasteNoFormatting", u"cmd_pasteNoFormatting", u"cmd_pasteNoFormatting"},
// charCode Modifiers, Windows macOS Linux Android Emacs
{0, 'v', MODIFIER_SHIFT | MODIFIER_ALT | MODIFIER_ACCEL, nullptr, u"cmd_pasteNoFormatting", nullptr, nullptr, nullptr},
// charCode Modifiers Windows macOS Linux Android Emacs
{0, 'z', MODIFIER_SHIFT | MODIFIER_ACCEL, u"cmd_redo", u"cmd_redo", u"cmd_redo", u"cmd_redo", u"cmd_redo"},
};

View File

@ -288,6 +288,7 @@ skip-if = headless
[test_password_paste.html]
[test_password_per_word_operation.html]
[test_password_unmask_API.html]
[test_paste_no_formatting.html]
[test_pasting_in_root_element.xhtml]
[test_pasting_in_temporarily_created_div_outside_body.html]
[test_pasting_text_longer_than_maxlength.html]

View File

@ -0,0 +1,108 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test pasting formatted test into various fields</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<input id="input">
<textarea id="textarea"></textarea>
<div id="editable" contenteditable="true"></div>
<div id="noneditable">Text</div>
<div id="source">Some <b>Bold</b> Text</div>
<script>
const expectedText = "Some Bold Text";
const expectedHTML = "<div id=\"source\">Some <b>Bold</b> Text</div>";
const htmlPrefix = navigator.platform.includes("Win")
? "<html><body>\n<!--StartFragment-->"
: "";
const htmlPostfix = navigator.platform.includes("Win")
? "<!--EndFragment-->\n</body>\n</html>"
: "";
add_task(async function test_paste_formatted() {
window.getSelection().selectAllChildren(document.getElementById("source"));
synthesizeKey("c", { accelKey: true });
function doKey(element, withShiftKey)
{
let inputEventPromise = new Promise(resolve => {
element.addEventListener("input", event => {
is(event.inputType, "insertFromPaste", "correct inputType");
resolve();
}, { once: true });
});
synthesizeKey("v", { accelKey: true, shiftKey: withShiftKey });
return inputEventPromise;
}
// Paste into input and textarea
for (let fieldid of ["input", "textarea"]) {
let field = document.getElementById(fieldid);
field.focus();
doKey(field, false);
is(field.value, expectedText, "paste into " + fieldid);
doKey(field, true);
is(field.value, expectedText + expectedText, "paste unformatted into " + field);
}
const selection = window.getSelection();
// Paste into editable area
let editable = document.getElementById("editable");
selection.selectAllChildren(editable);
selection.collapseToStart();
doKey(editable, false);
is(editable.innerHTML, expectedHTML, "paste into contenteditable");
// Unformatted paste into editable area
selection.selectAllChildren(editable);
selection.collapseToEnd();
doKey(editable, true);
is(editable.innerHTML, expectedHTML + expectedText, "paste unformatted into contenteditable");
let noneditable = document.getElementById("noneditable");
selection.selectAllChildren(noneditable);
selection.collapseToStart();
function getPasteResult() {
return new Promise(resolve => {
noneditable.addEventListener("paste", event => {
resolve({
text: event.clipboardData.getData("text/plain"),
html: event.clipboardData.getData("text/html"),
});
}, { once: true});
});
}
// Normal paste into non-editable area
let pastePromise = getPasteResult();
doKey(noneditable, false);
is(noneditable.innerHTML, "Text", "paste into non-editable");
let result = await pastePromise;
is(result.text, expectedText, "paste text into non-editable");
is(result.html, htmlPrefix + expectedHTML + htmlPostfix, "paste html into non-editable");
// Unformatted paste into non-editable area
pastePromise = getPasteResult();
doKey(noneditable, true);
is(noneditable.innerHTML, "Text", "paste unformatted into non-editable");
result = await pastePromise;
is(result.text, expectedText, "paste unformatted text into non-editable");
// Formatted HTML text should not exist when pasting unformatted.
is(result.html, "", "paste unformatted html into non-editable");
});
</script>
</body>