Commit ed99e91e authored by Masayuki Nakano's avatar Masayuki Nakano
Browse files

Bug 1898408 - Make our editor disconnect `<br>` element temporarily when its...

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
parent cb97c623
Loading
Loading
Loading
Loading
+80 −6
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include "DeleteTextTransaction.h"
#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;
}
+13 −0
Original line number Diff line number Diff line
@@ -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
+16 −6
Original line number Diff line number Diff line
@@ -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));
      }