Bug 1574852 - part 89: Move `HTMLEditRules::AdjustSelection()` to `HTMLEditor` r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 10 Sep 2019 03:38:04 +0000
changeset 492427 735f520eadfacb348147b81fc8933b1aff31eca0
parent 492426 f02aa8a0f9e6da1b28b3732600c4de2693c674fe
child 492428 99edaf54b89f1408916c78e9b3a78101e0fb1efe
push id36555
push usermalexandru@mozilla.com
push dateTue, 10 Sep 2019 09:53:18 +0000
treeherdermozilla-central@07f1d247491f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1574852
milestone71.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 1574852 - part 89: Move `HTMLEditRules::AdjustSelection()` to `HTMLEditor` r=m_kato Differential Revision: https://phabricator.services.mozilla.com/D44793
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
editor/libeditor/HTMLEditor.h
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -613,27 +613,33 @@ nsresult HTMLEditRules::AfterEditInner()
     }
 
     // Adjust selection for insert text, html paste, and delete actions if
     // we haven't removed new empty blocks.  Note that if empty block parents
     // are removed, Selection should've been adjusted by the method which
     // did it.
     if (!HTMLEditorRef()
              .TopLevelEditSubActionDataRef()
-             .mDidDeleteEmptyParentBlocks) {
+             .mDidDeleteEmptyParentBlocks &&
+        SelectionRefPtr()->IsCollapsed()) {
       switch (HTMLEditorRef().GetTopLevelEditSubAction()) {
         case EditSubAction::eInsertText:
         case EditSubAction::eInsertTextComingFromIME:
         case EditSubAction::eDeleteSelectedContent:
         case EditSubAction::eInsertLineBreak:
         case EditSubAction::eInsertParagraphSeparator:
         case EditSubAction::ePasteHTMLContent:
         case EditSubAction::eInsertHTMLSource:
-          rv = AdjustSelection(
-              HTMLEditorRef().GetDirectionOfTopLevelEditSubAction());
+          // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
+          //     does not create padding `<br>` element for empty editor.
+          //     Investigate which is better that whether this should does it
+          //     or wait MaybeCreatePaddingBRElementForEmptyEditor().
+          rv = MOZ_KnownLive(HTMLEditorRef())
+                   .AdjustCaretPositionAndEnsurePaddingBRElement(
+                       HTMLEditorRef().GetDirectionOfTopLevelEditSubAction());
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
           break;
         default:
           break;
       }
     }
@@ -9703,178 +9709,181 @@ void HTMLEditRules::CheckInterlinePositi
   if (node && HTMLEditor::NodeIsBlockStatic(*node)) {
     IgnoredErrorResult ignoredError;
     SelectionRefPtr()->SetInterlinePosition(false, ignoredError);
     NS_WARNING_ASSERTION(!ignoredError.Failed(),
                          "Failed to unset interline position");
   }
 }
 
-nsresult HTMLEditRules::AdjustSelection(nsIEditor::EDirection aAction) {
-  MOZ_ASSERT(IsEditorDataAvailable());
-
-  // if the selection isn't collapsed, do nothing.
-  // moose: one thing to do instead is check for the case of
-  // only a single break selected, and collapse it.  Good thing?  Beats me.
-  if (!SelectionRefPtr()->IsCollapsed()) {
-    return NS_OK;
-  }
-
-  // get the (collapsed) selection location
+nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
+    nsIEditor::EDirection aDirectionAndAmount) {
+  MOZ_ASSERT(IsEditActionDataAvailable());
+  MOZ_ASSERT(SelectionRefPtr()->IsCollapsed());
+
   EditorDOMPoint point(EditorBase::GetStartPoint(*SelectionRefPtr()));
   if (NS_WARN_IF(!point.IsSet())) {
     return NS_ERROR_FAILURE;
   }
 
-  // are we in an editable node?
-  while (!HTMLEditorRef().IsEditable(point.GetContainer())) {
-    // scan up the tree until we find an editable place to be
+  // If selection start is not editable, climb up the tree until editable one.
+  while (!IsEditable(point.GetContainer())) {
     point.Set(point.GetContainer());
     if (NS_WARN_IF(!point.IsSet())) {
       return NS_ERROR_FAILURE;
     }
   }
 
-  // make sure we aren't in an empty block - user will see no cursor.  If this
-  // is happening, put a <br> in the block if allowed.
-  RefPtr<Element> theblock = HTMLEditorRef().GetBlock(*point.GetContainer());
-
-  if (theblock && HTMLEditorRef().IsEditable(theblock)) {
-    bool isEmptyNode;
-    nsresult rv =
-        HTMLEditorRef().IsEmptyNode(theblock, &isEmptyNode, false, false);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-    // check if br can go into the destination node
-    if (isEmptyNode &&
-        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
-        // TextEditor::MaybeCreatePaddingBRElementForEmptyEditor()!
+  // If caret is in empty block element, we need to insert a `<br>` element
+  // because the block should have one-line height.
+  if (RefPtr<Element> blockElement = GetBlock(*point.GetContainer())) {
+    if (IsEditable(blockElement)) {
+      bool isEmptyNode;
+      nsresult rv = IsEmptyNode(blockElement, &isEmptyNode, false, false);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      if (isEmptyNode && CanContainTag(*point.GetContainer(), *nsGkAtoms::br)) {
+        Element* bodyOrDocumentElement = GetRoot();
+        if (NS_WARN_IF(!bodyOrDocumentElement)) {
+          return NS_ERROR_FAILURE;
+        }
+        if (point.GetContainer() == bodyOrDocumentElement) {
+          // Our root node is completely empty. Don't add a <br> here.
+          // AfterEditInner() will add one for us when it calls
+          // TextEditor::MaybeCreatePaddingBRElementForEmptyEditor().
+          // XXX This kind of dependency between methods makes us spaghetti.
+          //     Let's handle it here later.
+          // XXX This looks odd check.  If active editing host is not a
+          //     `<body>`, what are we doing?
+          return NS_OK;
+        }
+        CreateElementResult createPaddingBRResult =
+            InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
+        if (NS_WARN_IF(createPaddingBRResult.Failed())) {
+          return createPaddingBRResult.Rv();
+        }
         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())) {
-        return createPaddingBRResult.Rv();
-      }
-      return NS_OK;
-    }
-  }
-
-  // are we in a text node?
+    }
+  }
+
+  // XXX Perhaps, we should do something if we're in a data node but not
+  //     a text node.
   if (point.IsInTextNode()) {
-    return NS_OK;  // we LIKE it when we are in a text node.  that RULZ
+    return NS_OK;
   }
 
   // Do we need to insert a padding <br> element for empty last line?  We do
   // if we are:
   // 1) prior node is in same block where selection is AND
   // 2) prior node is a br AND
   // 3) that br is not visible
 
-  nsCOMPtr<nsIContent> nearNode =
-      HTMLEditorRef().GetPreviousEditableHTMLNode(point);
-  if (nearNode) {
-    // is nearNode also a descendant of same block?
-    RefPtr<Element> block = HTMLEditorRef().GetBlock(*point.GetContainer());
-    RefPtr<Element> nearBlock = HTMLEditorRef().GetBlockNodeParent(nearNode);
-    if (block && block == nearBlock) {
-      if (nearNode && TextEditUtils::IsBreak(nearNode)) {
-        if (!HTMLEditorRef().IsVisibleBRElement(nearNode)) {
-          // need to insert special moz BR. Why?  Because if we don't
-          // the user will see no new line for the break.  Also, things
-          // like table cells won't grow in height.
-          CreateElementResult createPaddingBRResult =
-              MOZ_KnownLive(HTMLEditorRef())
-                  .InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
-          if (NS_WARN_IF(createPaddingBRResult.Failed())) {
-            return createPaddingBRResult.Rv();
-          }
-          point.Set(createPaddingBRResult.GetNewNode());
-          // Selection stays *before* padding <br> element for empty last
-          // line, sticking to it.
-          ErrorResult error;
-          SelectionRefPtr()->SetInterlinePosition(true, error);
-          if (NS_WARN_IF(!CanHandleEditAction())) {
-            error.SuppressException();
-            return NS_ERROR_EDITOR_DESTROYED;
-          }
-          NS_WARNING_ASSERTION(!error.Failed(),
+  if (nsCOMPtr<nsIContent> previousEditableContent =
+          GetPreviousEditableHTMLNode(point)) {
+    RefPtr<Element> blockElementAtCaret = GetBlock(*point.GetContainer());
+    RefPtr<Element> blockElementParentAtPreviousEditableContent =
+        GetBlockNodeParent(previousEditableContent);
+    // If previous editable content of caret is in same block and a `<br>`
+    // element, we need to adjust interline position.
+    if (blockElementAtCaret &&
+        blockElementAtCaret == blockElementParentAtPreviousEditableContent &&
+        previousEditableContent &&
+        TextEditUtils::IsBreak(previousEditableContent)) {
+      // If it's an invisible `<br>` element, we need to insert a padding
+      // `<br>` element for making empty line have one-line height.
+      if (!IsVisibleBRElement(previousEditableContent)) {
+        CreateElementResult createPaddingBRResult =
+            InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
+        if (NS_WARN_IF(createPaddingBRResult.Failed())) {
+          return createPaddingBRResult.Rv();
+        }
+        point.Set(createPaddingBRResult.GetNewNode());
+        // Selection stays *before* padding `<br>` element for empty last
+        // line, sticking to it.
+        IgnoredErrorResult ignoredError;
+        SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+        if (NS_WARN_IF(Destroyed())) {
+          return NS_ERROR_EDITOR_DESTROYED;
+        }
+        NS_WARNING_ASSERTION(!ignoredError.Failed(),
+                             "Failed to set interline position");
+        ErrorResult error;
+        SelectionRefPtr()->Collapse(point, error);
+        if (NS_WARN_IF(Destroyed())) {
+          error.SuppressException();
+          return NS_ERROR_EDITOR_DESTROYED;
+        }
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
+        }
+      }
+      // If it's a visible `<br>` element and next editable content is a
+      // padding `<br>` element, we need to set interline position.
+      else if (nsIContent* nextEditableContentInBlock =
+                   GetNextEditableHTMLNodeInBlock(*previousEditableContent)) {
+        if (EditorBase::IsPaddingBRElementForEmptyLastLine(
+                *nextEditableContentInBlock)) {
+          // Make it stick to the padding `<br>` element so that it will be
+          // on blank line.
+          IgnoredErrorResult ignoredError;
+          SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+          NS_WARNING_ASSERTION(!ignoredError.Failed(),
                                "Failed to set interline position");
-          error = NS_OK;
-          SelectionRefPtr()->Collapse(point, error);
-          if (NS_WARN_IF(!CanHandleEditAction())) {
-            error.SuppressException();
-            return NS_ERROR_EDITOR_DESTROYED;
-          }
-          if (NS_WARN_IF(error.Failed())) {
-            return error.StealNSResult();
-          }
-        } else {
-          nsCOMPtr<nsIContent> nextNode =
-              HTMLEditorRef().GetNextEditableHTMLNodeInBlock(*nearNode);
-          if (nextNode &&
-              EditorBase::IsPaddingBRElementForEmptyLastLine(*nextNode)) {
-            // Selection between a <br> element and a padding <br> element for
-            // empty last line.  Make it stick to the padding <br> element so
-            // that it will be on blank line.
-            IgnoredErrorResult ignoredError;
-            SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
-            NS_WARNING_ASSERTION(!ignoredError.Failed(),
-                                 "Failed to set interline position");
-          }
-        }
-      }
-    }
-  }
-
-  // we aren't in a textnode: are we adjacent to text or a break or an image?
-  nearNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
-  if (nearNode &&
-      (TextEditUtils::IsBreak(nearNode) || EditorBase::IsTextNode(nearNode) ||
-       HTMLEditUtils::IsImage(nearNode) ||
-       nearNode->IsHTMLElement(nsGkAtoms::hr))) {
-    // this is a good place for the caret to be
+        }
+      }
+    }
+  }
+
+  // If previous editable content in same block is `<br>`, text node, `<img>`
+  //  or `<hr>`, current caret position is fine.
+  if (nsIContent* previousEditableContentInBlock =
+          GetPreviousEditableHTMLNodeInBlock(point)) {
+    if (TextEditUtils::IsBreak(previousEditableContentInBlock) ||
+        EditorBase::IsTextNode(previousEditableContentInBlock) ||
+        HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
+        previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
+      return NS_OK;
+    }
+  }
+  // If next editable content in same block is `<br>`, text node, `<img>` or
+  // `<hr>`, current caret position is fine.
+  if (nsIContent* nextEditableContentInBlock =
+          GetNextEditableHTMLNodeInBlock(point)) {
+    if (TextEditUtils::IsBreak(nextEditableContentInBlock) ||
+        EditorBase::IsTextNode(nextEditableContentInBlock) ||
+        nextEditableContentInBlock->IsAnyOfHTMLElements(nsGkAtoms::img,
+                                                        nsGkAtoms::hr)) {
+      return NS_OK;
+    }
+  }
+
+  // Otherwise, look for a near editable content towards edit action direction.
+
+  // If there is no editable content, keep current caret position.
+  nsIContent* nearEditableContent =
+      FindNearEditableContent(point, aDirectionAndAmount);
+  if (!nearEditableContent) {
     return NS_OK;
   }
-  nearNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
-  if (nearNode &&
-      (TextEditUtils::IsBreak(nearNode) || EditorBase::IsTextNode(nearNode) ||
-       nearNode->IsAnyOfHTMLElements(nsGkAtoms::img, nsGkAtoms::hr))) {
-    return NS_OK;  // this is a good place for the caret to be
-  }
-
-  // look for a nearby text node.
-  // prefer the correct direction.
-  nearNode = HTMLEditorRef().FindNearEditableContent(point, aAction);
-  if (!nearNode) {
-    return NS_OK;
-  }
-
-  EditorDOMPoint pt = HTMLEditorRef().GetGoodCaretPointFor(*nearNode, aAction);
+
+  EditorDOMPoint pointToPutCaret =
+      GetGoodCaretPointFor(*nearEditableContent, aDirectionAndAmount);
+  if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
+    return NS_ERROR_FAILURE;
+  }
   ErrorResult error;
-  SelectionRefPtr()->Collapse(pt, error);
-  if (NS_WARN_IF(!CanHandleEditAction())) {
+  SelectionRefPtr()->Collapse(pointToPutCaret, error);
+  if (NS_WARN_IF(Destroyed())) {
     error.SuppressException();
     return NS_ERROR_EDITOR_DESTROYED;
   }
-  if (NS_WARN_IF(error.Failed())) {
-    return error.StealNSResult();
-  }
-  return NS_OK;
+  NS_WARNING_ASSERTION(!error.Failed(), "Selection::Collapse() failed");
+  return error.StealNSResult();
 }
 
 template <typename PT, typename CT>
 nsIContent* HTMLEditor::FindNearEditableContent(
     const EditorDOMPointBase<PT, CT>& aPoint,
     nsIEditor::EDirection aDirection) {
   MOZ_ASSERT(IsEditActionDataAvailable());
   MOZ_ASSERT(aPoint.IsSetAndValid());
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -205,26 +205,16 @@ class HTMLEditRules : public TextEditRul
    * PinSelectionToNewBlock() may collapse Selection around mNewNode if it's
    * necessary,
    */
   MOZ_MUST_USE nsresult PinSelectionToNewBlock();
 
   void CheckInterlinePosition();
 
   /**
-   * AdjustSelection() may adjust Selection range to nearest editable content.
-   * Despite of the name, this may change the DOM tree.  If it needs to create
-   * a <br> to put caret, this tries to create a <br> element.
-   *
-   * @param aAction     Maybe used to look for a good point to put caret.
-   */
-  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
-  AdjustSelection(nsIEditor::EDirection aAction);
-
-  /**
    * RemoveEmptyNodesInChangedRange() removes all empty nodes in
    * TopLevelEditSubActionData::mChangedRange.  However, if mail-cite node has
    * only a <br> element, the node will be removed but <br> element is moved
    * to where the mail-cite node was.
    * XXX This method is expensive if TopLevelEditSubActionData::mChangedRange
    *     is too wide and may remove unexpected empty element, e.g., it was
    *     created by JS, but we haven't touched it.  Cannot we remove this
    *     method and make guarantee that empty nodes won't be created?
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -2587,16 +2587,27 @@ class HTMLEditor final : public TextEdit
    *                    this returns nullptr.
    *                    And also if aDirection is not nsIEditor::ePrevious,
    *                    the result may be the node pointed by aPoint.
    */
   template <typename PT, typename CT>
   nsIContent* FindNearEditableContent(const EditorDOMPointBase<PT, CT>& aPoint,
                                       nsIEditor::EDirection aDirection);
 
+  /**
+   * AdjustCaretPositionAndEnsurePaddingBRElement() may adjust caret
+   * position to nearest editable content and if padding `<br>` element is
+   * necessary at caret position, this creates it.
+   *
+   * @param aDirectionAndAmount Direction of the edit action.
+   */
+  MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
+  AdjustCaretPositionAndEnsurePaddingBRElement(
+      nsIEditor::EDirection aDirectionAndAmount);
+
  protected:  // Called by helper classes.
   virtual void OnStartToHandleTopLevelEditSubAction(
       EditSubAction aEditSubAction, nsIEditor::EDirection aDirection) override;
   MOZ_CAN_RUN_SCRIPT
   virtual void OnEndHandlingTopLevelEditSubAction() override;
 
  protected:  // Shouldn't be used by friend classes
   virtual ~HTMLEditor();