Bug 1713334 - Part 3: Assume TextEditor always have a text node r=masayuki

Differential Revision: https://phabricator.services.mozilla.com/D117023
This commit is contained in:
Kagami Sascha Rosylight 2021-06-14 19:56:23 +00:00
Родитель 4a9087283b
Коммит d6d3699b31
6 изменённых файлов: 64 добавлений и 93 удалений

Просмотреть файл

@ -68,6 +68,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=328885
evt.originalTarget.setAttribute("foo", "bar");
ok(mutationCount == 0, "(10) Mutation listener shouldn't have been called! ["+ mutationCount + "]");
// We played with the internal div, now restore its initial state
evt.originalTarget.replaceChildren(new Text());
// Same tests with non-native-anononymous element.
// mutationCount should be increased by 2 each time, since there is
// first a mutation specific event and then DOMSubtreeModified.

Просмотреть файл

@ -5393,7 +5393,6 @@ nsresult EditorBase::ReplaceTextAsAction(
}
nsresult EditorBase::ReplaceSelectionAsSubAction(const nsAString& aString) {
// TODO: Move this method to `EditorBase`.
if (aString.IsEmpty()) {
nsresult rv = DeleteSelectionAsSubAction(
nsIEditor::eNone,

Просмотреть файл

@ -600,36 +600,22 @@ EditActionResult TextEditor::SetTextWithoutTransaction(
}
RefPtr<Element> anonymousDivElement = GetRoot();
nsIContent* firstChild = anonymousDivElement->GetFirstChild();
RefPtr<Text> textNode =
Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
MOZ_ASSERT(textNode);
// We can use this fast path only when:
// - we need to insert a text node.
// - we need to replace content of existing text node.
// Additionally, for avoiding odd result, we should check whether we're in
// usual condition.
if (IsSingleLineEditor()) {
// If we're a single line text editor, i.e., <input>, there is only padding
// <br> element. Otherwise, there should be only one text node. But note
// that even if there is a padding <br> element for empty editor, it's
// already been removed by `EnsureNoPaddingBRElementForEmptyEditor()`. So,
// at here, there should be only one text node or no children.
if (firstChild && (!firstChild->IsText() || firstChild->GetNextSibling())) {
return EditActionIgnored();
}
} else {
if (!IsSingleLineEditor()) {
// If we're a multiline text editor, i.e., <textarea>, there is a padding
// <br> element for empty last line followed by scrollbar/resizer elements.
// Otherwise, a text node is followed by them.
if (!firstChild) {
return EditActionIgnored();
}
if (firstChild->IsText()) {
if (!firstChild->GetNextSibling() ||
!EditorUtils::IsPaddingBRElementForEmptyLastLine(
*firstChild->GetNextSibling())) {
return EditActionIgnored();
}
} else if (!EditorUtils::IsPaddingBRElementForEmptyLastLine(*firstChild)) {
if (!textNode->GetNextSibling() ||
!EditorUtils::IsPaddingBRElementForEmptyLastLine(
*textNode->GetNextSibling())) {
return EditActionIgnored();
}
}
@ -641,36 +627,6 @@ EditActionResult TextEditor::SetTextWithoutTransaction(
HandleNewLinesInStringForSingleLineEditor(sanitizedValue);
}
if (!firstChild || !firstChild->IsText()) {
if (sanitizedValue.IsEmpty()) {
return EditActionHandled();
}
RefPtr<Document> document = GetDocument();
if (NS_WARN_IF(!document)) {
return EditActionIgnored();
}
RefPtr<nsTextNode> newTextNode = CreateTextNode(sanitizedValue);
if (!newTextNode) {
NS_WARNING("EditorBase::CreateTextNode() failed");
return EditActionIgnored();
}
nsresult rv = InsertNodeWithTransaction(
*newTextNode, EditorDOMPoint(anonymousDivElement, 0));
if (NS_WARN_IF(Destroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
return EditActionResult(rv);
}
return EditActionHandled();
}
RefPtr<Text> textNode = firstChild->GetAsText();
if (MOZ_UNLIKELY(!textNode)) {
NS_WARNING("The first child was not a text node");
return EditActionIgnored();
}
rv = SetTextNodeWithoutTransaction(sanitizedValue, *textNode);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::SetTextNodeWithoutTransaction() failed");
@ -691,9 +647,7 @@ EditActionResult TextEditor::HandleDeleteSelection(
CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
// if there is only padding <br> element for empty editor, cancel the
// operation.
if (mPaddingBRElementForEmptyEditor) {
if (IsEmpty()) {
return EditActionCanceled();
}
EditActionResult result =
@ -785,37 +739,15 @@ EditActionResult TextEditor::ComputeValueFromTextNodeAndBRElement(
return EditActionHandled();
}
nsIContent* textNodeOrPaddingBRElement = anonymousDivElement->GetFirstChild();
if (!textNodeOrPaddingBRElement ||
textNodeOrPaddingBRElement == mPaddingBRElementForEmptyEditor) {
Text* textNode = Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
MOZ_ASSERT(textNode);
if (!textNode->Length()) {
aValue.Truncate();
return EditActionHandled();
}
// If it's an <input type="text"> element, the DOM tree should be:
// <div (::-moz-text-control-editing-root)>
// #text
// </div>
//
// If it's a <textarea> element, the DOM tree should be:
// <div (::-moz-text-control-editing-root)>
// #text (if there is)
// <br type="_moz">
// <scrollbar orient="horizontal">
// ...
// </div>
Text* textNode = textNodeOrPaddingBRElement->GetAsText();
if (!textNode) {
// If there is no text node in the expected DOM tree, we can say that it's
// just empty.
aValue.Truncate();
return EditActionHandled();
}
nsIContent* firstChildExceptText =
textNode ? textNodeOrPaddingBRElement->GetNextSibling()
: textNodeOrPaddingBRElement;
nsIContent* firstChildExceptText = textNode->GetNextSibling();
// If the DOM tree is unexpected, fall back to the expensive path.
bool isInput = IsSingleLineEditor();
bool isTextarea = !isInput;

Просмотреть файл

@ -368,10 +368,11 @@ bool TextEditor::IsEmpty() const {
return true; // Don't warn it, this is possible, e.g., 997805.html
}
MOZ_ASSERT(anonymousDivElement->GetFirstChild() &&
anonymousDivElement->GetFirstChild()->IsText());
// Only when there is non-empty text node, we are not empty.
return !anonymousDivElement->GetFirstChild() ||
!anonymousDivElement->GetFirstChild()->IsText() ||
!anonymousDivElement->GetFirstChild()->Length();
return !anonymousDivElement->GetFirstChild()->Length();
}
NS_IMETHODIMP TextEditor::GetTextLength(int32_t* aCount) {
@ -596,13 +597,7 @@ nsresult TextEditor::SelectEntireDocument() {
RefPtr<Text> text =
Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
if (!text) {
ErrorResult error;
SelectionRef().CollapseInLimiter(*anonymousDivElement, 0, error);
NS_WARNING_ASSERTION(!error.Failed(),
"Selection::SetStartAndEndInLimiter() failed");
return error.StealNSResult();
}
MOZ_ASSERT(text);
MOZ_TRY(SelectionRef().SetStartAndEndInLimiter(
*text, 0, *text, text->TextDataLength(), eDirNext,

Просмотреть файл

@ -224,7 +224,7 @@ skip-if = headless
[test_bug1399722.html]
[test_bug1406726.html]
[test_bug1409520.html]
[test_bug1425997.html]
[test_bug1425997.html]
[test_bug1497480.html]
skip-if = toolkit == 'android'
[test_bug1543312.html]
@ -311,6 +311,7 @@ skip-if = (verify && debug && os == 'win') # bug 1485293
skip-if = toolkit == 'android'
[test_state_change_on_reframe.html]
[test_textarea_value_not_include_cr.html]
[test_texteditor_textnode.html]
[test_typing_at_edge_of_anchor.html]
[test_undo_after_spellchecker_replaces_word.html]
skip-if = toolkit == 'android'

Просмотреть файл

@ -0,0 +1,41 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test for Bug 1713334</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<input id="input">
<textarea id="textarea"></textarea>
<script>
"use strict";
function assertChild(div, content) {
const name = div.parentElement.localName;
is(div.firstChild.nodeType, Node.TEXT_NODE, `<${name}>: The first node of the root element must be a text node`);
is(div.firstChild.textContent, content, `<${name}>: The content of the text node is wrong`);
}
function test(element) {
element.focus();
const { rootElement } = SpecialPowers.wrap(element).editor;
assertChild(rootElement, "");
element.value = "";
assertChild(rootElement, "");
element.value = "foo"
assertChild(rootElement, "foo");
element.value = "";
assertChild(rootElement, "");
}
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(() => {
test(document.all.input);
test(document.all.textarea);
SimpleTest.finish();
});
</script>