Bug 1572375 - part 5: Move `TextEditRules::CreatePaddingBRElementForEmptyEditorIfNeeded()` into `EditorBase` r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 09 Aug 2019 10:19:11 +0000
changeset 487550 8c7339d7cd3b9679b17c02b8f6d2d5d12cd1b949
parent 487549 8059efaac8ca4a45dd3d4161aa3e09e644e3c5ca
child 487551 e14e02842d3eb93f5a83d5deb808cd6e653b8faf
push id36425
push userbtara@mozilla.com
push dateTue, 13 Aug 2019 09:54:32 +0000
treeherdermozilla-central@e29ba984dad2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1572375
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1572375 - part 5: Move `TextEditRules::CreatePaddingBRElementForEmptyEditorIfNeeded()` into `EditorBase` r=m_kato `TextEditRules::CreatePaddingBRElementForEmptyEditorIfNeeded()` is used by both `TextEditor` and `HTMLEditor` so that this moves it into `EditorBase`. Additionally, making `TextEditor::MaybeChangePaddingBRElementForEmptyEditor()` call it if there is no content. Then, we can avoid the dependency of them. Differential Revision: https://phabricator.services.mozilla.com/D41159
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -4047,16 +4047,87 @@ nsresult EditorBase::EnsureNoPaddingBREl
       std::move(mPaddingBRElementForEmptyEditor));
   nsresult rv = DeleteNodeWithTransaction(*paddingBRElement);
   if (NS_WARN_IF(Destroyed())) {
     return NS_ERROR_EDITOR_DESTROYED;
   }
   return rv;
 }
 
+nsresult EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+
+  if (mPaddingBRElementForEmptyEditor) {
+    return NS_OK;
+  }
+
+  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
+      *this, EditSubAction::eCreatePaddingBRElementForEmptyEditor,
+      nsIEditor::eNone);
+
+  RefPtr<Element> rootElement = GetRoot();
+  if (!rootElement) {
+    return NS_OK;
+  }
+
+  // Now we've got the body element. Iterate over the body element's children,
+  // looking for editable content. If no editable content is found, insert the
+  // padding <br> element.
+  bool isRootEditable = IsEditable(rootElement);
+  for (nsIContent* rootChild = rootElement->GetFirstChild(); rootChild;
+       rootChild = rootChild->GetNextSibling()) {
+    if (EditorBase::IsPaddingBRElementForEmptyEditor(*rootChild) ||
+        !isRootEditable || IsEditable(rootChild) || IsBlockNode(rootChild)) {
+      return NS_OK;
+    }
+  }
+
+  // Skip adding the padding <br> element for empty editor if body
+  // is read-only.
+  if (!IsModifiableNode(*rootElement)) {
+    return NS_OK;
+  }
+
+  // Create a br.
+  RefPtr<Element> newBrElement = CreateHTMLContent(nsGkAtoms::br);
+  if (NS_WARN_IF(Destroyed())) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  if (NS_WARN_IF(!newBrElement)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mPaddingBRElementForEmptyEditor =
+      static_cast<HTMLBRElement*>(newBrElement.get());
+
+  // Give it a special attribute.
+  newBrElement->SetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
+
+  // Put the node in the document.
+  nsresult rv =
+      InsertNodeWithTransaction(*newBrElement, EditorDOMPoint(rootElement, 0));
+  if (NS_WARN_IF(Destroyed())) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Set selection.
+  IgnoredErrorResult error;
+  SelectionRefPtr()->Collapse(EditorRawDOMPoint(rootElement, 0), error);
+  if (NS_WARN_IF(Destroyed())) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  NS_WARNING_ASSERTION(
+      !error.Failed(),
+      "Failed to collapse selection at start of the root element");
+  return NS_OK;
+}
+
 void EditorBase::BeginUpdateViewBatch() {
   MOZ_ASSERT(IsEditActionDataAvailable());
   MOZ_ASSERT(mUpdateCount >= 0, "bad state");
 
   if (!mUpdateCount) {
     // Turn off selection updates and notifications.
     SelectionRefPtr()->StartBatchChanges();
   }
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -1469,16 +1469,23 @@ class EditorBase : public nsIEditor,
 
   /**
    * EnsureNoPaddingBRElementForEmptyEditor() removes padding <br> element
    * for empty editor if there is.
    */
   MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
   EnsureNoPaddingBRElementForEmptyEditor();
 
+  /**
+   * MaybeCreatePaddingBRElementForEmptyEditor() creates padding <br> element
+   * for empty editor if there is no children.
+   */
+  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
+  MaybeCreatePaddingBRElementForEmptyEditor();
+
   MOZ_CAN_RUN_SCRIPT nsresult DoTransactionInternal(nsITransaction* aTxn);
 
   virtual bool IsBlockNode(nsINode* aNode) const;
 
   /**
    * Set outOffset to the offset of aChild in the parent.
    * Returns the parent of aChild.
    */
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -633,17 +633,18 @@ nsresult HTMLEditRules::AfterEditInner(E
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // detect empty doc
   // XXX Need to investigate when the padding <br> element is removed because
   //     I don't see the <br> element with testing manually.  If it won't be
   //     used, we can get rid of this cost.
-  rv = CreatePaddingBRElementForEmptyEditorIfNeeded();
+  rv = MOZ_KnownLive(HTMLEditorRef())
+           .MaybeCreatePaddingBRElementForEmptyEditor();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // adjust selection HINT if needed
   if (!mDidExplicitlySetInterline) {
     CheckInterlinePosition();
   }
@@ -9488,17 +9489,17 @@ nsresult HTMLEditRules::AdjustSelection(
         HTMLEditorRef().CanContainTag(*point.GetContainer(), *nsGkAtoms::br)) {
       Element* rootElement = HTMLEditorRef().GetRoot();
       if (NS_WARN_IF(!rootElement)) {
         return NS_ERROR_FAILURE;
       }
       if (point.GetContainer() == rootElement) {
         // Our root node is completely empty. Don't add a <br> here.
         // AfterEditInner() will add one for us when it calls
-        // CreatePaddingBRElementForEmptyEditorIfNeeded()!
+        // TextEditor::MaybeCreatePaddingBRElementForEmptyEditor()!
         return NS_OK;
       }
 
       // we know we can skip the rest of this routine given the cirumstance
       CreateElementResult createPaddingBRResult =
           MOZ_KnownLive(HTMLEditorRef())
               .InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
       if (NS_WARN_IF(createPaddingBRResult.Failed())) {
@@ -11162,15 +11163,17 @@ void HTMLEditRules::OnModifyDocument() {
         std::move(HTMLEditorRef().mPaddingBRElementForEmptyEditor));
     DebugOnly<nsresult> rv = MOZ_KnownLive(HTMLEditorRef())
                                  .DeleteNodeWithTransaction(*paddingBRElement);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                          "Failed to remove the padding <br> element");
   }
 
   // Try to recreate the padding <br> element for empty editor if needed.
-  DebugOnly<nsresult> rv = CreatePaddingBRElementForEmptyEditorIfNeeded();
+  nsresult rv = MOZ_KnownLive(HTMLEditorRef())
+                    .MaybeCreatePaddingBRElementForEmptyEditor();
   NS_WARNING_ASSERTION(
-      rv.value != NS_ERROR_EDITOR_DESTROYED,
+      rv != NS_ERROR_EDITOR_DESTROYED,
       "The editor has been destroyed during creating a padding <br> element");
+  Unused << rv;
 }
 
 }  // namespace mozilla
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -130,19 +130,18 @@ nsresult TextEditRules::Init(TextEditor*
   }
 
   InitFields();
 
   // We hold a non-refcounted reference back to our editor.
   mTextEditor = aTextEditor;
   AutoSafeEditorData setData(*this, *mTextEditor);
 
-  // Put in a magic <br> if needed. This method handles null selection,
-  // which should never happen anyway
-  nsresult rv = CreatePaddingBRElementForEmptyEditorIfNeeded();
+  nsresult rv = MOZ_KnownLive(TextEditorRef())
+                    .MaybeCreatePaddingBRElementForEmptyEditor();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // If the selection hasn't been set up yet, set it up collapsed to the end of
   // our editable content.
   if (!SelectionRefPtr()->RangeCount()) {
     rv = TextEditorRef().CollapseSelectionToEnd();
@@ -245,27 +244,21 @@ nsresult TextEditRules::AfterEdit(EditSu
         0, nullptr, 0);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     // no longer uses mCachedSelectionNode, so release it.
     mCachedSelectionNode = nullptr;
 
-    rv = TextEditorRef().MaybeChangePaddingBRElementForEmptyEditor();
+    rv = MOZ_KnownLive(TextEditorRef()).EnsurePaddingBRElementForEmptyEditor();
     if (NS_FAILED(rv)) {
       return rv;
     }
 
-    // detect empty doc
-    rv = CreatePaddingBRElementForEmptyEditorIfNeeded();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
     // ensure trailing br node
     rv = CreateTrailingBRIfNeeded();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     // Collapse the selection to the trailing padding <br> element for empty
     // last line if it's at the end of our text node.
@@ -1230,18 +1223,21 @@ nsresult TextEditRules::CreateTrailingBR
     return NS_OK;
   }
 
   Element* rootElement = TextEditorRef().GetRoot();
   if (NS_WARN_IF(!rootElement)) {
     return NS_ERROR_FAILURE;
   }
 
-  // Assuming CreatePaddingBRElementForEmptyEditorIfNeeded() has been
+  // Assuming EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() has been
   // called first.
+  // XXX This assumption is wrong.  This method may be called alone.  Actually,
+  //     we see this warning in mochitest log.  So, we should fix this bug
+  //     later.
   if (NS_WARN_IF(!rootElement->GetLastChild())) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<HTMLBRElement> brElement =
       HTMLBRElement::FromNode(rootElement->GetLastChild());
   if (!brElement) {
     AutoTransactionsConserveSelection dontChangeMySelection(TextEditorRef());
@@ -1265,94 +1261,16 @@ nsresult TextEditRules::CreateTrailingBR
 
   // 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);
 
   return NS_OK;
 }
 
-nsresult TextEditRules::CreatePaddingBRElementForEmptyEditorIfNeeded() {
-  MOZ_ASSERT(IsEditorDataAvailable());
-
-  if (TextEditorRef().mPaddingBRElementForEmptyEditor) {
-    // Let's not create more than one, ok?
-    return NS_OK;
-  }
-
-  // tell rules system to not do any post-processing
-  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
-      TextEditorRef(), EditSubAction::eCreatePaddingBRElementForEmptyEditor,
-      nsIEditor::eNone);
-
-  RefPtr<Element> rootElement = TextEditorRef().GetRoot();
-  if (!rootElement) {
-    // We don't even have a body yet, don't insert any padding <br> elements at
-    // this point.
-    return NS_OK;
-  }
-
-  // Now we've got the body element. Iterate over the body element's children,
-  // looking for editable content. If no editable content is found, insert the
-  // padding <br> element.
-  bool isRootEditable = TextEditorRef().IsEditable(rootElement);
-  for (nsIContent* rootChild = rootElement->GetFirstChild(); rootChild;
-       rootChild = rootChild->GetNextSibling()) {
-    if (EditorBase::IsPaddingBRElementForEmptyEditor(*rootChild) ||
-        !isRootEditable || TextEditorRef().IsEditable(rootChild) ||
-        TextEditorRef().IsBlockNode(rootChild)) {
-      return NS_OK;
-    }
-  }
-
-  // Skip adding the padding <br> element for empty editor if body
-  // is read-only.
-  if (!TextEditorRef().IsModifiableNode(*rootElement)) {
-    return NS_OK;
-  }
-
-  // Create a br.
-  RefPtr<Element> newBrElement =
-      TextEditorRef().CreateHTMLContent(nsGkAtoms::br);
-  if (NS_WARN_IF(!CanHandleEditAction())) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  if (NS_WARN_IF(!newBrElement)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  TextEditorRef().mPaddingBRElementForEmptyEditor =
-      static_cast<HTMLBRElement*>(newBrElement.get());
-
-  // Give it a special attribute.
-  newBrElement->SetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
-
-  // Put the node in the document.
-  nsresult rv = MOZ_KnownLive(TextEditorRef())
-                    .InsertNodeWithTransaction(*newBrElement,
-                                               EditorDOMPoint(rootElement, 0));
-  if (NS_WARN_IF(!CanHandleEditAction())) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Set selection.
-  IgnoredErrorResult error;
-  SelectionRefPtr()->Collapse(EditorRawDOMPoint(rootElement, 0), error);
-  if (NS_WARN_IF(!CanHandleEditAction())) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  NS_WARNING_ASSERTION(
-      !error.Failed(),
-      "Failed to collapse selection at start of the root element");
-  return NS_OK;
-}
-
 nsresult TextEditRules::TruncateInsertionIfNeeded(const nsAString* aInString,
                                                   nsAString* aOutString,
                                                   int32_t aMaxLength,
                                                   bool* aTruncated) {
   MOZ_ASSERT(IsEditorDataAvailable());
 
   if (NS_WARN_IF(!aInString) || NS_WARN_IF(!aOutString)) {
     return NS_ERROR_INVALID_ARG;
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -2160,47 +2160,57 @@ nsresult TextEditor::RemoveAttributeOrEq
 
   nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return EditorBase::ToGenericNSResult(rv);
   }
   return NS_OK;
 }
 
-nsresult TextEditor::MaybeChangePaddingBRElementForEmptyEditor() {
+nsresult TextEditor::EnsurePaddingBRElementForEmptyEditor() {
   MOZ_ASSERT(IsEditActionDataAvailable());
   MOZ_ASSERT(!AsHTMLEditor());
 
   // If there is padding <br> element for empty editor, we have no work to do.
   if (mPaddingBRElementForEmptyEditor) {
     return NS_OK;
   }
 
   // Likewise, nothing to be done if we could never have inserted a trailing
   // <br> element.
   // XXX Why don't we use same path for <textarea> and <input>?
   if (IsSingleLineEditor()) {
-    return NS_OK;
+    nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor();
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rv),
+        "Failed to create padding <br> element for empty editor");
+    return rv;
   }
 
   if (NS_WARN_IF(!mRootElement)) {
     return NS_ERROR_FAILURE;
   }
 
-  if (mRootElement->GetChildCount() > 1) {
-    // The trailing br is redundant if it is the only remaining child node
+  uint32_t childCount = mRootElement->GetChildCount();
+  if (childCount == 0) {
+    nsresult rv = MaybeCreatePaddingBRElementForEmptyEditor();
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rv),
+        "Failed to create padding <br> element for empty editor");
+    return rv;
+  }
+
+  if (childCount > 1) {
     return NS_OK;
   }
 
   RefPtr<HTMLBRElement> brElement =
       HTMLBRElement::FromNodeOrNull(mRootElement->GetFirstChild());
   if (!brElement ||
       !EditorBase::IsPaddingBRElementForEmptyLastLine(*brElement)) {
-    // XXX Why don't we create new padding <br> element when there is no
-    //     children?
     return NS_OK;
   }
 
   // Rather than deleting this node from the DOM tree we should instead
   // morph this <br> element into the padding <br> element for editor.
   mPaddingBRElementForEmptyEditor = std::move(brElement);
   mPaddingBRElementForEmptyEditor->UnsetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
   mPaddingBRElementForEmptyEditor->SetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -580,21 +580,21 @@ class TextEditor : public EditorBase,
   MOZ_CAN_RUN_SCRIPT
   virtual void OnEndHandlingTopLevelEditSubAction() override;
 
   void BeginEditorInit();
   MOZ_CAN_RUN_SCRIPT
   nsresult EndEditorInit();
 
   /**
-   * If editor has a padding <br> element for empty last line but the editor
-   * is now empty, this method changes it to padding <br> element for empty
-   * editor.
+   * EnsurePaddingBRElementForEmptyEditor() creates padding <br> element for
+   * empty editor or changes padding <br> element for empty last line to for
+   * empty editor when we're empty.
    */
-  nsresult MaybeChangePaddingBRElementForEmptyEditor();
+  MOZ_CAN_RUN_SCRIPT nsresult EnsurePaddingBRElementForEmptyEditor();
 
  protected:  // Shouldn't be used by friend classes
   virtual ~TextEditor();
 
   int32_t WrapWidth() const { return mWrapColumn; }
 
   /**
    * Make the given selection span the entire document.