Text manipulation does not observe newly displayed element inside previously observed content
https://bugs.webkit.org/show_bug.cgi?id=213156

Reviewed by Darin Adler.

Source/WebCore:

Fix two issues:
1. Some inserted nodes are marked as manipulated, but they are inserted because they get removed in the
replacement process, not because they are manipulated or in the range of item.
2. TextManipulationController does not perform manipulation on an element if its ancestor is manipulated. This
means the newly inserted/displayed children of a manipulated element are ruled out for manipulation.

Test: TextManipulation.CompleteTextManipulationForNewlyDisplayedParagraph

* editing/TextManipulationController.cpp:
(WebCore::TextManipulationController::observeParagraphs):
(WebCore::TextManipulationController::didCreateRendererForElement):
(WebCore::TextManipulationController::updateInsertions):
(WebCore::TextManipulationController::replace):
(WebCore::TextManipulationController::isInManipulatedElement): Deleted.
* editing/TextManipulationController.h:

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm:
(TestWebKitAPI::TEST):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263044 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/editing/TextManipulationController.cpp b/Source/WebCore/editing/TextManipulationController.cpp
index 52696af..2e4b490 100644
--- a/Source/WebCore/editing/TextManipulationController.cpp
+++ b/Source/WebCore/editing/TextManipulationController.cpp
@@ -119,17 +119,6 @@
 {
 }
 
-bool TextManipulationController::isInManipulatedElement(Element& element)
-{
-    if (!m_manipulatedElements.capacity())
-        return false; // Fast path for startObservingParagraphs.
-    for (auto& ancestorOrSelf : lineageOfType<Element>(element)) {
-        if (m_manipulatedElements.contains(ancestorOrSelf))
-            return true;
-    }
-    return false;
-}
-
 void TextManipulationController::startObservingParagraphs(ManipulationItemCallback&& callback, Vector<ExclusionRule>&& exclusionRules)
 {
     auto document = makeRefPtr(m_document.get());
@@ -433,8 +422,7 @@
         }
 
         if (RefPtr<Element> currentElementAncestor = is<Element>(*contentNode) ? downcast<Element>(contentNode) : contentNode->parentOrShadowHostElement()) {
-            // We can exit early here because scheduleObservationUpdate calls this function on each paragraph separately.
-            if (isInManipulatedElement(*currentElementAncestor))
+            if (m_manipulatedElements.contains(*currentElementAncestor))
                 return;
         }
 
@@ -488,7 +476,7 @@
 
 void TextManipulationController::didCreateRendererForElement(Element& element)
 {
-    if (isInManipulatedElement(element))
+    if (m_manipulatedElements.contains(element))
         return;
 
     if (m_elementsWithNewRenderer.computesEmpty())
@@ -630,7 +618,7 @@
     return path;
 }
 
-void TextManipulationController::updateInsertions(Vector<NodeEntry>& lastTopDownPath, const Vector<Ref<Node>>& currentTopDownPath, Node* currentNode, HashSet<Ref<Node>>& insertedNodes, Vector<NodeInsertion>& insertions)
+void TextManipulationController::updateInsertions(Vector<NodeEntry>& lastTopDownPath, const Vector<Ref<Node>>& currentTopDownPath, Node* currentNode, HashSet<Ref<Node>>& insertedNodes, Vector<NodeInsertion>& insertions, IsNodeManipulated isNodeManipulated)
 {
     size_t i =0;
     while (i < lastTopDownPath.size() && i < currentTopDownPath.size() && lastTopDownPath[i].first.ptr() == currentTopDownPath[i].ptr())
@@ -654,7 +642,7 @@
     }
 
     if (currentNode)
-        insertions.append(NodeInsertion { lastTopDownPath.size() ? lastTopDownPath.last().second.ptr() : nullptr, *currentNode });
+        insertions.append(NodeInsertion { lastTopDownPath.size() ? lastTopDownPath.last().second.ptr() : nullptr, *currentNode, isNodeManipulated });
 }
 
 auto TextManipulationController::replace(const ManipulationItemData& item, const Vector<ManipulationToken>& replacementTokens) -> Optional<ManipulationFailureType>
@@ -783,14 +771,14 @@
     RefPtr<Node> endNode = end.firstNode();
     if (node && node != endNode) {
         auto topDownPath = getPath(commonAncestor.get(), node->parentNode());
-        updateInsertions(lastTopDownPath, topDownPath, nullptr, reusedOriginalNodes, insertions);
+        updateInsertions(lastTopDownPath, topDownPath, nullptr, reusedOriginalNodes, insertions, IsNodeManipulated::No);
     }
     while (node != endNode) {
         Ref<Node> parentNode = *node->parentNode();
         while (!lastTopDownPath.isEmpty() && lastTopDownPath.last().first.ptr() != parentNode.ptr())
             lastTopDownPath.removeLast();
 
-        insertions.append(NodeInsertion { lastTopDownPath.size() ? lastTopDownPath.last().second.ptr() : nullptr, *node });
+        insertions.append(NodeInsertion { lastTopDownPath.size() ? lastTopDownPath.last().second.ptr() : nullptr, *node, IsNodeManipulated::No });
         lastTopDownPath.append({ *node, *node });
         node = NodeTraversal::next(*node);
     }
@@ -808,7 +796,7 @@
             insertionPoint = positionInParentAfterNode(insertion.child.ptr());
         } else
             insertion.parentIfDifferentFromCommonAncestor->appendChild(insertion.child);
-        if (is<Element>(insertion.child.get()))
+        if (is<Element>(insertion.child.get()) && insertion.isChildManipulated == IsNodeManipulated::Yes)
             m_manipulatedElements.add(downcast<Element>(insertion.child.get()));
     }