Bug 1460509 - part 48: Make HTMLEditRules::CheckForEmptyBlock() return NS_ERROR_EDITOR_DESTROYED if it causes destroying the editor r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 16 May 2018 00:10:11 +0900
changeset 798766 cbe591df0fc84b8ea8024f018da458a55008bd6b
parent 798765 fbc062e17ec7663cf7ec23aa7c610af427b4d955
child 798767 635141382655a9adfa90161da1c6ce32b428d582
push id110840
push usermasayuki@d-toybox.com
push dateWed, 23 May 2018 13:41:58 +0000
reviewersm_kato
bugs1460509
milestone62.0a1
Bug 1460509 - part 48: Make HTMLEditRules::CheckForEmptyBlock() return NS_ERROR_EDITOR_DESTROYED if it causes destroying the editor r?m_kato Additionally, this patch renames it to MaybeDeleteTopMostEmptyAncestor() for making its name explain what it does. MozReview-Commit-ID: 1i7zeq9In2T
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -2390,17 +2390,17 @@ HTMLEditRules::WillDeleteSelection(nsIEd
   int32_t startOffset = firstRange->StartOffset();
 
   if (bCollapsed) {
     // If we are inside an empty block, delete it.
     RefPtr<Element> host = HTMLEditorRef().GetActiveEditingHost();
     if (NS_WARN_IF(!host)) {
       return NS_ERROR_FAILURE;
     }
-    rv = CheckForEmptyBlock(startNode, host, aAction, aHandled);
+    rv = MaybeDeleteTopMostEmptyAncestor(*startNode, *host, aAction, aHandled);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     if (*aHandled) {
       return NS_OK;
     }
 
     // Test for distance between caret and text that will be deleted
@@ -5893,151 +5893,186 @@ HTMLEditRules::AlignBlockContents(nsINod
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     lastChild = HTMLEditorRef().GetLastEditableChild(aNode);
   }
   return NS_OK;
 }
 
-/**
- * CheckForEmptyBlock() is called by WillDeleteSelection() to detect and handle
- * case of deleting from inside an empty block.
- */
 nsresult
-HTMLEditRules::CheckForEmptyBlock(nsINode* aStartNode,
-                                  Element* aBodyNode,
-                                  nsIEditor::EDirection aAction,
-                                  bool* aHandled)
+HTMLEditRules::MaybeDeleteTopMostEmptyAncestor(nsINode& aStartNode,
+                                               Element& aEditingHostElement,
+                                               nsIEditor::EDirection aAction,
+                                               bool* aHandled)
 {
   MOZ_ASSERT(IsEditorDataAvailable());
 
   // If the editing host is an inline element, bail out early.
-  if (aBodyNode && IsInlineNode(*aBodyNode)) {
+  if (IsInlineNode(aEditingHostElement)) {
     return NS_OK;
   }
 
   // If we are inside an empty block, delete it.  Note: do NOT delete table
   // elements this way.
-  RefPtr<Element> block = HTMLEditorRef().GetBlock(*aStartNode);
-  bool bIsEmptyNode;
+  RefPtr<Element> block = HTMLEditorRef().GetBlock(aStartNode);
   RefPtr<Element> emptyBlock;
-  if (block && block != aBodyNode) {
+  if (block && block != &aEditingHostElement) {
     // Efficiency hack, avoiding IsEmptyNode() call when in body
+    bool isEmptyNode = false;
     nsresult rv =
-      HTMLEditorRef().IsEmptyNode(block, &bIsEmptyNode, true, false);
+      HTMLEditorRef().IsEmptyNode(block, &isEmptyNode, true, false);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-    while (block && bIsEmptyNode && !HTMLEditUtils::IsTableElement(block) &&
-           block != aBodyNode) {
+    while (block && isEmptyNode && !HTMLEditUtils::IsTableElement(block) &&
+           block != &aEditingHostElement) {
       emptyBlock = block;
       block = HTMLEditorRef().GetBlockNodeParent(emptyBlock);
       if (block) {
-        rv = HTMLEditorRef().IsEmptyNode(block, &bIsEmptyNode, true, false);
+        rv = HTMLEditorRef().IsEmptyNode(block, &isEmptyNode, true, false);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
       }
     }
   }
 
-  if (emptyBlock && emptyBlock->IsEditable()) {
-    nsCOMPtr<nsINode> blockParent = emptyBlock->GetParentNode();
-    NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
-
-    if (HTMLEditUtils::IsListItem(emptyBlock)) {
-      // Are we the first list item in the list?
-      if (HTMLEditorRef().IsFirstEditableChild(emptyBlock)) {
-        EditorDOMPoint atBlockParent(blockParent);
-        if (NS_WARN_IF(!atBlockParent.IsSet())) {
+  if (!emptyBlock || !emptyBlock->IsEditable()) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsINode> blockParent = emptyBlock->GetParentNode();
+  if (NS_WARN_IF(!blockParent)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (HTMLEditUtils::IsListItem(emptyBlock)) {
+    // If the found empty block is a list item element and its grand parent
+    // (i.e., parent of list element) is NOT a list element, insert <br>
+    // element before the list element which has the empty list item.
+    // XXX Typically, list element shouldn't have another list element.
+    //     So, what's the purpose of this block?
+    if (HTMLEditorRef().IsFirstEditableChild(emptyBlock)) {
+      EditorDOMPoint atBlockParent(blockParent);
+      if (NS_WARN_IF(!atBlockParent.IsSet())) {
+        return NS_ERROR_FAILURE;
+      }
+      // If the grand parent IS a list element, we'll adjust Selection in
+      // AfterEdit().
+      if (!HTMLEditUtils::IsList(atBlockParent.GetContainer())) {
+        RefPtr<Element> brElement =
+          HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(),
+                                                         atBlockParent);
+        if (NS_WARN_IF(!CanHandleEditAction())) {
+          return NS_ERROR_EDITOR_DESTROYED;
+        }
+        if (NS_WARN_IF(!brElement)) {
           return NS_ERROR_FAILURE;
         }
-        // If we are a sublist, skip the br creation
-        if (!HTMLEditUtils::IsList(atBlockParent.GetContainer())) {
-          // Create a br before list
-          RefPtr<Element> brElement =
-            HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(),
-                                                           atBlockParent);
-          if (NS_WARN_IF(!brElement)) {
-            return NS_ERROR_FAILURE;
-          }
-          // Adjust selection to be right before it
-          ErrorResult error;
-          SelectionRef().Collapse(EditorRawDOMPoint(brElement), error);
-          if (NS_WARN_IF(error.Failed())) {
-            return error.StealNSResult();
-          }
-        }
-        // Else just let selection percolate up.  We'll adjust it in
-        // AfterEdit()
-      }
-    } else {
-      if (aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
-          aAction == nsIEditor::eToEndOfLine) {
-        // Move to the start of the next node, if any
+        ErrorResult error;
+        SelectionRef().Collapse(EditorRawDOMPoint(brElement), error);
+        if (NS_WARN_IF(!CanHandleEditAction())) {
+          error.SuppressException();
+          return NS_ERROR_EDITOR_DESTROYED;
+        }
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
+        }
+      }
+    }
+  } else {
+    switch (aAction) {
+      case nsIEditor::eNext:
+      case nsIEditor::eNextWord:
+      case nsIEditor::eToEndOfLine: {
+        // Collapse Selection to next node of after empty block element
+        // if there is.  Otherwise, to just after the empty block.
         EditorRawDOMPoint afterEmptyBlock(emptyBlock);
-        DebugOnly<bool> advanced = afterEmptyBlock.AdvanceOffset();
-        NS_WARNING_ASSERTION(advanced,
+        bool advancedFromEmptyBlock = afterEmptyBlock.AdvanceOffset();
+        NS_WARNING_ASSERTION(advancedFromEmptyBlock,
           "Failed to set selection to the after the empty block");
         nsCOMPtr<nsIContent> nextNode =
           HTMLEditorRef().GetNextNode(afterEmptyBlock);
         if (nextNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction);
           ErrorResult error;
           SelectionRef().Collapse(pt, error);
+          if (NS_WARN_IF(!CanHandleEditAction())) {
+            error.SuppressException();
+            return NS_ERROR_EDITOR_DESTROYED;
+          }
           if (NS_WARN_IF(error.Failed())) {
             return error.StealNSResult();
           }
-        } else {
-          // Adjust selection to be right after it.
-          EditorRawDOMPoint afterEmptyBlock(emptyBlock);
-          if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
-            return NS_ERROR_FAILURE;
-          }
-          ErrorResult error;
-          SelectionRef().Collapse(afterEmptyBlock, error);
-          if (NS_WARN_IF(error.Failed())) {
-            return error.StealNSResult();
-          }
-        }
-      } else if (aAction == nsIEditor::ePrevious ||
-                 aAction == nsIEditor::ePreviousWord ||
-                 aAction == nsIEditor::eToBeginningOfLine) {
-        // Move to the end of the previous node
+          break;
+        }
+        if (NS_WARN_IF(!advancedFromEmptyBlock)) {
+          return NS_ERROR_FAILURE;
+        }
+        ErrorResult error;
+        SelectionRef().Collapse(afterEmptyBlock, error);
+        if (NS_WARN_IF(!CanHandleEditAction())) {
+          error.SuppressException();
+          return NS_ERROR_EDITOR_DESTROYED;
+        }
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
+        }
+        break;
+      }
+      case nsIEditor::ePrevious:
+      case nsIEditor::ePreviousWord:
+      case nsIEditor::eToBeginningOfLine: {
+        // Collapse Selection to previous editable node of the empty block
+        // if there is.  Otherwise, to after the empty block.
         EditorRawDOMPoint atEmptyBlock(emptyBlock);
         nsCOMPtr<nsIContent> priorNode =
           HTMLEditorRef().GetPreviousEditableNode(atEmptyBlock);
         if (priorNode) {
           EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction);
           ErrorResult error;
           SelectionRef().Collapse(pt, error);
+          if (NS_WARN_IF(!CanHandleEditAction())) {
+            error.SuppressException();
+            return NS_ERROR_EDITOR_DESTROYED;
+          }
           if (NS_WARN_IF(error.Failed())) {
             return error.StealNSResult();
           }
-        } else {
-          EditorRawDOMPoint afterEmptyBlock(emptyBlock);
-          if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
-            return NS_ERROR_FAILURE;
-          }
-          ErrorResult error;
-          SelectionRef().Collapse(afterEmptyBlock, error);
-          if (NS_WARN_IF(error.Failed())) {
-            return error.StealNSResult();
-          }
-        }
-      } else if (aAction != nsIEditor::eNone) {
+          break;
+        }
+        EditorRawDOMPoint afterEmptyBlock(emptyBlock);
+        if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
+          return NS_ERROR_FAILURE;
+        }
+        ErrorResult error;
+        SelectionRef().Collapse(afterEmptyBlock, error);
+        if (NS_WARN_IF(!CanHandleEditAction())) {
+          error.SuppressException();
+          return NS_ERROR_EDITOR_DESTROYED;
+        }
+        if (NS_WARN_IF(error.Failed())) {
+          return error.StealNSResult();
+        }
+        break;
+      }
+      case nsIEditor::eNone:
+        break;
+      default:
         MOZ_CRASH("CheckForEmptyBlock doesn't support this action yet");
-      }
-    }
-    *aHandled = true;
-    nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*emptyBlock);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    }
+  }
+  *aHandled = true;
+  nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*emptyBlock);
+  if (NS_WARN_IF(!CanHandleEditAction())) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
   return NS_OK;
 }
 
 Element*
 HTMLEditRules::CheckForInvisibleBR(Element& aBlock,
                                    BRLocation aWhere,
                                    int32_t aOffset)
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -459,18 +459,45 @@ protected:
   enum class IgnoreSingleBR
   {
     eYes,
     eNo
   };
   bool IsEmptyBlockElement(Element& aElement,
                            IgnoreSingleBR aIgnoreSingleBR);
 
-  nsresult CheckForEmptyBlock(nsINode* aStartNode, Element* aBodyNode,
-                              nsIEditor::EDirection aAction, bool* aHandled);
+  /**
+   * MaybeDeleteTopMostEmptyAncestor() looks for top most empty block ancestor
+   * of aStartNode in aEditingHostElement.
+   * If found empty ancestor is a list item element, inserts a <br> element
+   * before its parent element if grand parent is a list element.  Then,
+   * collapse Selection to after the empty block.
+   * If found empty ancestor is not a list item element, collapse Selection to
+   * somewhere depending on aAction.
+   * Finally, removes the empty block ancestor.
+   *
+   * @param aStartNode          Start node to look for empty ancestors.
+   * @param aEditingHostElement Current editing host.
+   * @param aAction             If found empty ancestor block is a list item
+   *                            element, this is ignored.  Otherwise:
+   *                            - If eNext, eNextWord or eToEndOfLine, collapse
+   *                              Selection to after found empty ancestor.
+   *                            - If ePrevious, ePreviousWord or
+   *                              eToBeginningOfLine, collapse Selection to
+   *                              end of previous editable node.
+   *                            Otherwise, eNone is allowed but does nothing.
+   * @param aHandled            Returns true if this method removes an empty
+   *                            block ancestor of aStartNode.
+   */
+  MOZ_MUST_USE nsresult
+  MaybeDeleteTopMostEmptyAncestor(nsINode& aStartNode,
+                                  Element& aEditingHostElement,
+                                  nsIEditor::EDirection aAction,
+                                  bool* aHandled);
+
   enum class BRLocation { beforeBlock, blockEnd };
   Element* CheckForInvisibleBR(Element& aBlock, BRLocation aWhere,
                                int32_t aOffset = 0);
 
   /**
    * ExpandSelectionForDeletion() may expand Selection range if it's not
    * collapsed and there is only one range.  This may expand to include
    * invisible <br> element for preventing delete action handler to keep