Bug 1898408 - Make our editor disconnect `<br>` element temporarily when its type is changed from or to padding one r=m_kato

Currently, the `<br>` element type -- whether normal `<br>` element or padding
`<br>` element for empty editor or last line -- is managed by the flags of
`nsINode`.  Therefore, changing the flag does not cause mutation, so
`IMEContentObserver` cannot observe the type changes.  However,
`ContentEventHandler` treats the padding `<br>` elements as invisible.
Therefore, when a `<br>` element becomes a padding one, `IMEContentObserver`
needs to notify IME of atext removed notification, and also when a `<br>`
element becomes a normal one (i.e., visible), `IMEContentObserver` needs to
notify IME of a text added notification.

Therefore, this patch makes `EditorBase` disconnect the `<br>` element
temporarily to make `IMEContentObserver` observable the type change.

Depends on D211698

Differential Revision: https://phabricator.services.mozilla.com/D211699
This commit is contained in:
Masayuki Nakano 2024-05-31 00:42:13 +00:00
Родитель cb97c62342
Коммит ed99e91ef8
3 изменённых файлов: 109 добавлений и 12 удалений

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

@ -16,8 +16,9 @@
#include "DeleteNodeTransaction.h"
#include "DeleteRangeTransaction.h"
#include "DeleteTextTransaction.h"
#include "EditAction.h" // for EditSubAction
#include "EditorDOMPoint.h" // for EditorDOMPoint
#include "EditAction.h" // for EditSubAction
#include "EditorDOMPoint.h" // for EditorDOMPoint
#include "EditorForwards.h"
#include "EditorUtils.h" // for various helper classes.
#include "EditTransactionBase.h" // for EditTransactionBase
#include "EditorEventListener.h" // for EditorEventListener
@ -35,6 +36,7 @@
#include "gfxFontUtils.h" // for gfxFontUtils
#include "mozilla/Assertions.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/intl/BidiEmbeddingLevel.h"
#include "mozilla/BasePrincipal.h" // for BasePrincipal
#include "mozilla/CheckedInt.h" // for CheckedInt
@ -2414,11 +2416,17 @@ EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction(
pointToInsert = maybePointToInsert.unwrap();
}
RefPtr<Element> newBRElement = CreateHTMLContent(nsGkAtoms::br);
RefPtr<HTMLBRElement> newBRElement =
HTMLBRElement::FromNodeOrNull(RefPtr{CreateHTMLContent(nsGkAtoms::br)});
if (NS_WARN_IF(!newBRElement)) {
return Err(NS_ERROR_FAILURE);
}
newBRElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
nsresult rv = UpdateBRElementType(*newBRElement,
BRElementType::PaddingForEmptyLastLine);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::UpdateBRElementType() failed");
return Err(rv);
}
Result<CreateElementResult, nsresult> insertBRElementResult =
InsertNodeWithTransaction<Element>(*newBRElement, pointToInsert);
@ -2427,6 +2435,68 @@ EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction(
return insertBRElementResult;
}
nsresult EditorBase::UpdateBRElementType(HTMLBRElement& aBRElement,
BRElementType aNewType) {
const bool brElementIsHidden = aBRElement.IsPaddingForEmptyEditor() ||
aBRElement.IsPaddingForEmptyLastLine();
const bool brElementWillBeHidden = aNewType != BRElementType::Normal;
const auto SetBRElementFlags = [&]() {
switch (aNewType) {
case BRElementType::Normal:
if (brElementIsHidden) {
aBRElement.UnsetFlags(NS_PADDING_FOR_EMPTY_EDITOR |
NS_PADDING_FOR_EMPTY_LAST_LINE);
}
break;
case BRElementType::PaddingForEmptyEditor:
if (brElementIsHidden) {
aBRElement.UnsetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
}
aBRElement.SetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
break;
case BRElementType::PaddingForEmptyLastLine:
if (brElementIsHidden) {
aBRElement.UnsetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
}
aBRElement.SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
break;
}
};
// If the <br> element is in the composed doc, it must be observed by
// IMEContentObserver. However, IMEContentObserver cannot observe the state
// change, but changing the <br> type may make the <br> element visible or
// invisible for ContentEventHandler. Therefore, IMEContentObserver needs to
// notify IME of the state change as a text change notification of adding or
// removing a line break. Therefore, we need to reconnect the <br> element
// temporarily for making IMEContentObserver observable this change.
if (!aBRElement.IsInComposedDoc() ||
brElementIsHidden == brElementWillBeHidden) {
SetBRElementFlags();
return NS_OK;
}
EditorDOMPoint pointToInsert(&aBRElement);
{
AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
nsresult rv = DeleteNodeWithTransaction(aBRElement);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
return rv;
}
}
if (NS_WARN_IF(!pointToInsert.IsSetAndValid())) {
return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
SetBRElementFlags();
Result<CreateElementResult, nsresult> result =
InsertNodeWithTransaction<Element>(aBRElement, pointToInsert);
if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
return result.unwrapErr();
}
result.inspect().IgnoreCaretPointSuggestion();
return NS_OK;
}
NS_IMETHODIMP EditorBase::DeleteNode(nsINode* aNode, bool aPreserveSelection,
uint8_t aOptionalArgCount) {
MOZ_ASSERT_UNREACHABLE("Do not use this API with TextEditor");
@ -3654,8 +3724,12 @@ nsresult EditorBase::EnsurePaddingBRElementInMultilineEditor() {
}
// Morph it back to a padding <br> element for empty last line.
brElement->UnsetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
brElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
nsresult rv =
UpdateBRElementType(*brElement, BRElementType::PaddingForEmptyLastLine);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::UpdateBRElementType() failed");
return rv;
}
return NS_OK;
}

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

@ -1754,6 +1754,19 @@ class EditorBase : public nsIEditor,
InsertPaddingBRElementForEmptyLastLineWithTransaction(
const EditorDOMPoint& aPointToInsert);
enum class BRElementType {
Normal,
PaddingForEmptyEditor,
PaddingForEmptyLastLine
};
/**
* Updates the type of aBRElement. If it will be hidden or shown from
* IMEContentObserver and ContentEventHandler points of view, this temporarily
* removes the node and reconnect to the same position.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
UpdateBRElementType(dom::HTMLBRElement& aBRElement, BRElementType aNewType);
/**
* CloneAttributesWithTransaction() clones all attributes from
* aSourceElement to aDestElement after removing all attributes in

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

@ -974,7 +974,8 @@ nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
"HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
// Create a br.
RefPtr<Element> newBRElement = CreateHTMLContent(nsGkAtoms::br);
RefPtr<HTMLBRElement> newBRElement =
HTMLBRElement::FromNodeOrNull(RefPtr{CreateHTMLContent(nsGkAtoms::br)});
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
@ -982,11 +983,15 @@ nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
return NS_ERROR_FAILURE;
}
mPaddingBRElementForEmptyEditor =
static_cast<HTMLBRElement*>(newBRElement.get());
mPaddingBRElementForEmptyEditor = newBRElement;
// Give it a special attribute.
newBRElement->SetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
nsresult rv =
UpdateBRElementType(*newBRElement, BRElementType::PaddingForEmptyEditor);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::UpdateBRElementType() failed");
return rv;
}
// Put the node in the document.
Result<CreateElementResult, nsresult> insertBRElementResult =
@ -999,7 +1004,7 @@ nsresult HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() {
// Set selection.
insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING(
"EditorBase::CollapseSelectionToStartOf() caused destroying the "
@ -8698,7 +8703,12 @@ Result<SplitNodeResult, nsresult> HTMLEditor::SplitParagraphWithTransaction(
// <br>.
if (brElement &&
brElement->GetParentNode() == deepestInlineContainerElement) {
brElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
nsresult rv = UpdateBRElementType(
*brElement, BRElementType::PaddingForEmptyLastLine);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::UpdateBRElementType() failed");
return Err(rv);
}
return SplitNodeResult(std::move(unwrappedSplitDivOrPResult),
EditorDOMPoint(brElement));
}