зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1920646 - part 2: Make `HTMLEditor` handle `insertHTML` as inserting plaintext converted from given source r=m_kato
Differential Revision: https://phabricator.services.mozilla.com/D223909
This commit is contained in:
Родитель
7e9bbe23c8
Коммит
dc004f95b6
|
@ -188,6 +188,19 @@ class TextComposition final {
|
|||
*/
|
||||
bool IsComposing() const { return mIsComposing; }
|
||||
|
||||
/**
|
||||
* If we're requesting IME to commit or cancel composition, or we've already
|
||||
* requested it, or we've already known this composition has been ended in
|
||||
* IME, we don't need to request commit nor cancel composition anymore and
|
||||
* shouldn't do so if we're in content process for not committing/canceling
|
||||
* "current" composition in native IME. So, when this returns true,
|
||||
* RequestIMEToCommit() does nothing.
|
||||
*/
|
||||
[[nodiscard]] bool CanRequsetIMEToCommitOrCancelComposition() const {
|
||||
return !mIsRequestingCommit && !mIsRequestingCancel &&
|
||||
!mRequestedToCommitOrCancel && !mHasReceivedCommitEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if editor has started or already ended handling an event which
|
||||
* is modifying the composition string and/or IME selections.
|
||||
|
@ -412,19 +425,6 @@ class TextComposition final {
|
|||
// when DispatchCompositionEvent() is called.
|
||||
bool mWasCompositionStringEmpty;
|
||||
|
||||
/**
|
||||
* If we're requesting IME to commit or cancel composition, or we've already
|
||||
* requested it, or we've already known this composition has been ended in
|
||||
* IME, we don't need to request commit nor cancel composition anymore and
|
||||
* shouldn't do so if we're in content process for not committing/canceling
|
||||
* "current" composition in native IME. So, when this returns true,
|
||||
* RequestIMEToCommit() does nothing.
|
||||
*/
|
||||
bool CanRequsetIMEToCommitOrCancelComposition() const {
|
||||
return !mIsRequestingCommit && !mIsRequestingCancel &&
|
||||
!mRequestedToCommitOrCancel && !mHasReceivedCommitEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetEditorBase() returns EditorBase pointer of mEditorBaseWeak.
|
||||
*/
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "mozilla/OwningNonNull.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/TextComposition.h"
|
||||
#include "nsAString.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCRTGlue.h" // for CRLF
|
||||
|
@ -290,6 +291,54 @@ nsresult HTMLEditor::InsertHTMLAsAction(const nsAString& aInString,
|
|||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
|
||||
const RefPtr<Element> editingHost =
|
||||
ComputeEditingHost(LimitInBodyElement::No);
|
||||
if (NS_WARN_IF(!editingHost)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (editingHost->IsContentEditablePlainTextOnly()) {
|
||||
nsAutoString plaintextString;
|
||||
nsresult rv = nsContentUtils::ConvertToPlainText(
|
||||
aInString, plaintextString, nsIDocumentEncoder::OutputLFLineBreak,
|
||||
0u /* never wrap lines*/);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("nsContentUtils::ConvertToPlainText() failed");
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
Maybe<AutoPlaceholderBatch> treatAsOneTransaction;
|
||||
const auto EnsureAutoPlaceholderBatch = [&]() {
|
||||
if (treatAsOneTransaction.isNothing()) {
|
||||
treatAsOneTransaction.emplace(*this, ScrollSelectionIntoView::Yes,
|
||||
__FUNCTION__);
|
||||
}
|
||||
};
|
||||
if (mComposition &&
|
||||
mComposition->CanRequsetIMEToCommitOrCancelComposition()) {
|
||||
EnsureAutoPlaceholderBatch();
|
||||
CommitComposition();
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
|
||||
}
|
||||
if (NS_WARN_IF(editingHost !=
|
||||
ComputeEditingHost(LimitInBodyElement::No))) {
|
||||
return EditorBase::ToGenericNSResult(
|
||||
NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
}
|
||||
if (MOZ_LIKELY(!plaintextString.IsEmpty())) {
|
||||
EnsureAutoPlaceholderBatch();
|
||||
rv = InsertTextAsSubAction(plaintextString, SelectionHandling::Delete);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"EditorBase::InsertTextAsSubAction() failed");
|
||||
} else if (!SelectionRef().IsCollapsed()) {
|
||||
EnsureAutoPlaceholderBatch();
|
||||
rv = DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"EditorBase::DeleteSelectionAsSubAction() failed");
|
||||
}
|
||||
return EditorBase::ToGenericNSResult(rv);
|
||||
}
|
||||
AutoPlaceholderBatch treatAsOneTransaction(
|
||||
*this, ScrollSelectionIntoView::Yes, __FUNCTION__);
|
||||
rv = InsertHTMLWithContextAsSubAction(aInString, u""_ns, u""_ns, u""_ns,
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="timeout" content="long">
|
||||
<meta name="variant" content="?white-space=normal">
|
||||
<meta name="variant" content="?white-space=pre">
|
||||
<meta name="variant" content="?white-space=pre-line">
|
||||
<meta name="variant" content="?white-space=pre-wrap">
|
||||
<title>Pasting rich text into contenteditable=plaintext-only</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="../include/editor-test-utils.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
const searchParams = new URLSearchParams(document.location.search);
|
||||
const whiteSpace = searchParams.get("white-space");
|
||||
const useBR = whiteSpace == "normal";
|
||||
const collapseWhiteSpaces = whiteSpace == "normal" || whiteSpace == "pre-line";
|
||||
|
||||
addEventListener("load", () => {
|
||||
const editingHost = document.createElement("div");
|
||||
editingHost.style.whiteSpace = whiteSpace;
|
||||
editingHost.contentEditable = "plaintext-only";
|
||||
document.body.appendChild(editingHost);
|
||||
editingHost.focus();
|
||||
editingHost.getBoundingClientRect();
|
||||
const utils = new EditorTestUtils(editingHost);
|
||||
|
||||
for (const data of [
|
||||
{
|
||||
insertHTML: "plaintext",
|
||||
expected: "plaintext",
|
||||
},
|
||||
{
|
||||
// line breaks should not be preformatted
|
||||
insertHTML: "1st line\n2nd line",
|
||||
expected: "1st line 2nd line",
|
||||
},
|
||||
{
|
||||
// preformatted line breaks should appear as-is
|
||||
insertHTML: "<pre>1st line\n2nd line</pre>",
|
||||
expected: useBR
|
||||
? "1st line<br>2nd line"
|
||||
: ["1st line<br>2nd line", "1st line\n2nd line"],
|
||||
},
|
||||
{
|
||||
// text should be inserted into the <b>
|
||||
initialInnerHTML: "<b>{}</b>",
|
||||
insertHTML: "plaintext",
|
||||
expected: "<b>plaintext</b>",
|
||||
},
|
||||
{
|
||||
// text should be inserted into the <b>
|
||||
initialInnerHTML: "<b>{}<br></b>",
|
||||
insertHTML: "plaintext",
|
||||
expected: ["<b>plaintext</b>", "<b>plaintext<br></b>"],
|
||||
},
|
||||
{
|
||||
// text should be inserted into the <b>
|
||||
initialInnerHTML: "<b>A[]B</b>",
|
||||
insertHTML: "plaintext",
|
||||
expected: "<b>AplaintextB</b>",
|
||||
},
|
||||
{
|
||||
// text should be inserted into the <span> even if it's meaningless
|
||||
initialInnerHTML: "<span>A[]B</span>",
|
||||
insertHTML: "plaintext",
|
||||
expected: "<span>AplaintextB</span>",
|
||||
},
|
||||
{
|
||||
// inserting one paragraph should cause inserting only its contents.
|
||||
// (but it's okay other serialized text.)
|
||||
insertHTML: "<div>abc</div>",
|
||||
expected: "abc",
|
||||
},
|
||||
{
|
||||
// inserting one paragraph should cause inserting only its contents.
|
||||
// (but it's okay other serialized text.)
|
||||
insertHTML: "<div>abc<br>def</div>",
|
||||
expected: useBR ? "abc<br>def" : ["abc<br>def", "abc\ndef"],
|
||||
},
|
||||
{
|
||||
// inserting 2 or more paragraphs should be handled as multiple lines
|
||||
insertHTML: "<div>abc</div><div>def</div>",
|
||||
expected: useBR ? "abc<br>def" : ["abc<br>def", "abc\ndef"],
|
||||
},
|
||||
{
|
||||
// inserting 2 or more paragraphs should be handled as multiple lines
|
||||
insertHTML: "<div>abc<br>def</div><div>ghi<br>jkl</div>",
|
||||
expected: useBR
|
||||
? "abc<br>def<br>ghi<br>jkl"
|
||||
: ["abc<br>def<br>ghi<br>jkl",
|
||||
"abc\ndef\nghi\njkl"],
|
||||
},
|
||||
{
|
||||
// <noscript> content should not be inserted
|
||||
insertHTML: "<noscript>no script</noscript>",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
// <noframes> content should not be inserted
|
||||
insertHTML: "<noframes>no frames</noframes>",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
// <script> content should not be inserted
|
||||
insertHTML: `<script>script</${"script"}>`,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
// <style> content should not be inserted
|
||||
insertHTML: "<style>style</style>",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
// <head> content should not be inserted
|
||||
insertHTML: "<html><head><title>title</title></head><body>body</body></html>",
|
||||
expected: "body",
|
||||
},
|
||||
{
|
||||
// white-spaces should be collapsed
|
||||
insertHTML: "plain text",
|
||||
expected: "plain text",
|
||||
},
|
||||
{
|
||||
// white-spaces should be collapsed
|
||||
insertHTML: "<span>plain text</span>",
|
||||
expected: "plain text",
|
||||
},
|
||||
{
|
||||
// preformatted white-spaces should not be collapsed
|
||||
insertHTML: "<pre>plain text</pre>",
|
||||
expected: !collapseWhiteSpaces
|
||||
? "plain text"
|
||||
: ["plain text", "plain text", "plain text",
|
||||
"plain \u00A0text", "plain\u00A0 text", "plain\u00A0\u00A0text"],
|
||||
},
|
||||
{
|
||||
// even if inserting HTML is empty, selected text should be deleted
|
||||
initialInnerHTML: "A[B]C",
|
||||
insertHTML: "",
|
||||
expected: "AC",
|
||||
},
|
||||
]) {
|
||||
test(() => {
|
||||
utils.setupEditingHost(data.initialInnerHTML ? data.initialInnerHTML : "");
|
||||
document.execCommand("insertHTML", false, data.insertHTML);
|
||||
if (Array.isArray(data.expected)) {
|
||||
assert_in_array(editingHost.innerHTML, data.expected);
|
||||
} else {
|
||||
assert_equals(editingHost.innerHTML, data.expected);
|
||||
}
|
||||
}, `execCommand("insertHTML", false, "${data.insertHTML}") when "${
|
||||
data.initialInnerHTML ? data.initialInnerHTML : ""
|
||||
}"`);
|
||||
}
|
||||
}, {once: true});
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче