Bug 1451672 - part 24: Rename HTMLEditor::CopyLastEditableChildStyles() to HTMLEditor::CopyLastEditableChildStylesWithTransaction() r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 17 Apr 2018 01:15:23 +0900
changeset 786107 08bd519d88ab5e918d701e2aca4d1cfe4b2e6d40
parent 786106 fcc0ef13b8c774ec303363d3b32a977ffcb4d0d9
push id107393
push usermasayuki@d-toybox.com
push dateSat, 21 Apr 2018 04:40:12 +0000
reviewersm_kato
bugs1451672
milestone61.0a1
Bug 1451672 - part 24: Rename HTMLEditor::CopyLastEditableChildStyles() to HTMLEditor::CopyLastEditableChildStylesWithTransaction() r?m_kato Like this adds new comments into the method, we should optimize the method since it may create a lot of transactions and dispatch a lot of mutation events. MozReview-Commit-ID: FknJfu6QCjS
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -7593,23 +7593,26 @@ HTMLEditRules::ReturnInListItem(Selectio
           ErrorResult error;
           aSelection.Collapse(EditorRawDOMPoint(newListItem, 0), error);
           if (NS_WARN_IF(error.Failed())) {
             return error.StealNSResult();
           }
           return NS_OK;
         }
 
-        nsCOMPtr<Element> brNode;
-        rv = htmlEditor->CopyLastEditableChildStyles(prevItem,
-                                                     &aListItem,
-                                                     getter_AddRefs(brNode));
-        NS_ENSURE_SUCCESS(rv, rv);
-        if (brNode) {
-          EditorRawDOMPoint atBrNode(brNode);
+        RefPtr<Element> brElement;
+        nsresult rv =
+          htmlEditor->CopyLastEditableChildStylesWithTransaction(
+                        *prevItem->AsElement(), aListItem,
+                        address_of(brElement));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return NS_ERROR_FAILURE;
+        }
+        if (brElement) {
+          EditorRawDOMPoint atBrNode(brElement);
           if (NS_WARN_IF(!atBrNode.IsSetAndValid())) {
             return NS_ERROR_FAILURE;
           }
           ErrorResult error;
           aSelection.Collapse(atBrNode, error);
           if (NS_WARN_IF(error.Failed())) {
             return error.StealNSResult();
           }
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -4528,88 +4528,119 @@ HTMLEditor::AreNodesSameType(nsIContent*
   }
 
   // If CSS is enabled, we are stricter about span nodes.
   return CSSEditUtils::ElementsSameStyle(aNode1->AsElement(),
                                          aNode2->AsElement());
 }
 
 nsresult
-HTMLEditor::CopyLastEditableChildStyles(nsINode* aPreviousBlock,
-                                        nsINode* aNewBlock,
-                                        Element** aOutBrNode)
+HTMLEditor::CopyLastEditableChildStylesWithTransaction(
+              Element& aPreviousBlock,
+              Element& aNewBlock,
+              RefPtr<Element>* aNewBrElement)
 {
-  if (NS_WARN_IF(!aNewBlock)) {
+  if (NS_WARN_IF(!aNewBrElement)) {
     return NS_ERROR_INVALID_ARG;
   }
-  nsCOMPtr<nsINode> newBlock(aNewBlock);
-  *aOutBrNode = nullptr;
+  *aNewBrElement = nullptr;
+
+  RefPtr<Element> previousBlock(&aPreviousBlock);
+  RefPtr<Element> newBlock(&aNewBlock);
+
   // First, clear out aNewBlock.  Contract is that we want only the styles
   // from aPreviousBlock.
-  for (nsCOMPtr<nsINode> child = aNewBlock->GetFirstChild();
+  for (nsCOMPtr<nsINode> child = newBlock->GetFirstChild();
        child;
-       child = aNewBlock->GetFirstChild()) {
+       child = newBlock->GetFirstChild()) {
     nsresult rv = DeleteNodeWithTransaction(*child);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
-  // now find and clone the styles
-  nsCOMPtr<nsINode> child = aPreviousBlock;
-  nsCOMPtr<nsINode> tmp = aPreviousBlock;
-  while (tmp) {
-    child = tmp;
-    tmp = GetLastEditableChild(*child);
-  }
-  while (child && TextEditUtils::IsBreak(child)) {
-    child = GetPreviousEditableHTMLNode(*child);
-  }
-  nsCOMPtr<Element> newStyles, deepestStyle;
-  nsCOMPtr<nsINode> childNode = child;
-  nsCOMPtr<Element> childElement;
-  if (childNode) {
-    childElement = childNode->IsElement() ? childNode->AsElement()
-                                          : childNode->GetParentElement();
-  }
-  while (childElement && (childElement != aPreviousBlock)) {
-    if (HTMLEditUtils::IsInlineStyle(childElement) ||
-        childElement->IsHTMLElement(nsGkAtoms::span)) {
-      if (newStyles) {
-        newStyles =
-          InsertContainerWithTransaction(*newStyles,
-                                         *childElement->NodeInfo()->NameAtom());
-        if (NS_WARN_IF(!newStyles)) {
-          return NS_ERROR_FAILURE;
-        }
-      } else {
-        EditorRawDOMPoint atStartOfNewBlock(newBlock, 0);
-        deepestStyle = newStyles =
-          CreateNodeWithTransaction(*childElement->NodeInfo()->NameAtom(),
-                                    atStartOfNewBlock);
-        if (NS_WARN_IF(!newStyles)) {
-          return NS_ERROR_FAILURE;
-        }
+
+  // XXX aNewBlock may be moved or removed.  Even in such case, we should
+  //     keep cloning the styles?
+
+  // Look for the deepest last editable leaf node in aPreviousBlock.
+  // Then, if found one is a <br> element, look for non-<br> element.
+  nsIContent* deepestEditableContent = nullptr;
+  for (nsCOMPtr<nsIContent> child = previousBlock.get();
+       child;
+       child = GetLastEditableChild(*child)) {
+    deepestEditableContent = child;
+  }
+  while (deepestEditableContent &&
+         TextEditUtils::IsBreak(deepestEditableContent)) {
+    deepestEditableContent =
+      GetPreviousEditableHTMLNode(*deepestEditableContent);
+  }
+  Element* deepestVisibleEditableElement = nullptr;
+  if (deepestEditableContent) {
+    deepestVisibleEditableElement =
+      deepestEditableContent->IsElement() ?
+        deepestEditableContent->AsElement() :
+        deepestEditableContent->GetParentElement();
+  }
+
+  // Clone inline elements to keep current style in the new block.
+  // XXX Looks like that this is really slow if lastEditableDescendant is
+  //     far from aPreviousBlock.  Probably, we should clone inline containers
+  //     from ancestor to descendants without transactions, then, insert it
+  //     after that with transaction.
+  RefPtr<Element> lastClonedElement, firstClonsedElement;
+  for (RefPtr<Element> elementInPreviousBlock = deepestVisibleEditableElement;
+       elementInPreviousBlock && elementInPreviousBlock != previousBlock;
+       elementInPreviousBlock = elementInPreviousBlock->GetParentElement()) {
+    if (!HTMLEditUtils::IsInlineStyle(elementInPreviousBlock) &&
+        !elementInPreviousBlock->IsHTMLElement(nsGkAtoms::span)) {
+      continue;
+    }
+    nsAtom* tagName = elementInPreviousBlock->NodeInfo()->NameAtom();
+    // At first time, just create the most descendant inline container element.
+    if (!firstClonsedElement) {
+      EditorRawDOMPoint atStartOfNewBlock(newBlock, 0);
+      firstClonsedElement = lastClonedElement =
+        CreateNodeWithTransaction(*tagName, atStartOfNewBlock);
+      if (NS_WARN_IF(!firstClonsedElement)) {
+        return NS_ERROR_FAILURE;
       }
-      CloneAttributesWithTransaction(*newStyles, *childElement);
+      // Clone all attributes.
+      // XXX Looks like that this clones id attribute too.
+      CloneAttributesWithTransaction(*lastClonedElement,
+                                     *elementInPreviousBlock);
+      continue;
     }
-    childElement = childElement->GetParentElement();
-  }
-  if (deepestStyle) {
-    RefPtr<Selection> selection = GetSelection();
-    if (NS_WARN_IF(!selection)) {
+    // Otherwise, inserts new parent inline container to the previous inserted
+    // inline container.
+    lastClonedElement =
+      InsertContainerWithTransaction(*lastClonedElement, *tagName);
+    if (NS_WARN_IF(!lastClonedElement)) {
       return NS_ERROR_FAILURE;
     }
-    RefPtr<Element> brElement =
-      InsertBrElementWithTransaction(*selection,
-                                     EditorRawDOMPoint(deepestStyle, 0));
-    if (NS_WARN_IF(!brElement)) {
-      return NS_ERROR_FAILURE;
-    }
-    brElement.forget(aOutBrNode);
-  }
+    CloneAttributesWithTransaction(*lastClonedElement, *elementInPreviousBlock);
+  }
+
+  if (!firstClonsedElement) {
+    // XXX Even if no inline elements are cloned, shouldn't we create new
+    //     <br> element for aNewBlock?
+    return NS_OK;
+  }
+
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+  RefPtr<Element> brElement =
+    InsertBrElementWithTransaction(*selection,
+                                   EditorRawDOMPoint(firstClonsedElement, 0));
+  if (NS_WARN_IF(!brElement)) {
+    return NS_ERROR_FAILURE;
+  }
+  *aNewBrElement = brElement.forget();
   return NS_OK;
 }
 
 nsresult
 HTMLEditor::GetElementOrigin(Element& aElement,
                              int32_t& aX,
                              int32_t& aY)
 {
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -154,20 +154,16 @@ public:
 
   // nsIHTMLAbsPosEditor methods (implemented in HTMLAbsPositionEditor.cpp)
   NS_DECL_NSIHTMLABSPOSEDITOR
 
   // nsIHTMLInlineTableEditor methods (implemented in HTMLInlineTableEditor.cpp)
   NS_DECL_NSIHTMLINLINETABLEEDITOR
 
   // XXX Following methods are not overriding but defined here...
-  nsresult CopyLastEditableChildStyles(nsINode* aPreviousBlock,
-                                       nsINode* aNewBlock,
-                                       Element** aOutBrNode);
-
   nsresult LoadHTML(const nsAString& aInputString);
 
   nsresult GetCSSBackgroundColorState(bool* aMixed, nsAString& aOutColor,
                                       bool aBlockLevel);
   nsresult GetHTMLBackgroundColorState(bool* aMixed, nsAString& outColor);
 
   // nsIEditorStyleSheets methods
   NS_DECL_NSIEDITORSTYLESHEETS
@@ -462,16 +458,33 @@ public:
   template<typename PT, typename CT>
   EditorDOMPoint
   InsertNodeIntoProperAncestorWithTransaction(
     nsIContent& aNode,
     const EditorDOMPointBase<PT, CT>& aPointToInsert,
     SplitAtEdges aSplitAtEdges);
 
   /**
+   * CopyLastEditableChildStyles() clones inline container elements into
+   * aPreviousBlock to aNewBlock to keep using same style in it.
+   *
+   * @param aPreviousBlock      The previous block element.  All inline
+   *                            elements which are last sibling of each level
+   *                            are cloned to aNewBlock.
+   * @param aNewBlock           New block container element.
+   * @param aNewBrElement       If this method creates a new <br> element for
+   *                            placeholder, this is set to the new <br>
+   *                            element.
+   */
+  nsresult
+  CopyLastEditableChildStylesWithTransaction(Element& aPreviousBlock,
+                                             Element& aNewBlock,
+                                             RefPtr<Element>* aNewBrElement);
+
+  /**
    * Use this to assure that selection is set after attribute nodes when
    * trying to collapse selection at begining of a block node
    * e.g., when setting at beginning of a table cell
    * This will stop at a table, however, since we don't want to
    * "drill down" into nested tables.
    * @param aSelection      Optional. If null, we get current selection.
    */
   void CollapseSelectionToDeepestNonTableFirstChild(Selection* aSelection,