TextManipulationController should not generate a new item for content in manipulated paragraphs
https://bugs.webkit.org/show_bug.cgi?id=208286

Reviewed by Wenson Hsieh.

Source/WebCore:

This patch makes TextManipulationController to ignore any content change in previously manipulated paragraphs.

Added an early exist to observeParagraphs when the observed content has an element that has already been manipulated
as an ancestor. Note that the only case in which this logic matters is when it's called by scheduleObservartionUpdate,
which calls this function on each paragraph separately, unlike startObservingParagraphs which calls it on
the entire document, we can simply exit early instead of ignoring just the current paragraph.

Renamed TextManipulationController's m_recentlyInsertedElements to m_manipulatedElements and made it persist
forever so that we can track any element that has already been manipulated. Als renamed m_mutatedElements
to m_elementsWithNewRenderer for clarity.

Test: TestWebKitAPI.TextManipulation.InsertingContentIntoAlreadyManipulatedContentDoesNotCreateTextManipulationItem

* editing/TextManipulationController.cpp:
(WebCore::TextManipulationController::isInManipulatedElement): Added. Has a fast path for when
m_manipulatedElements's capacity is 0, which happens when observeParagraphs is called by startObservingParagraphs.
(WebCore::TextManipulationController::observeParagraphs): Added an early exit when there is a content that has
already been manipulated.
(WebCore::TextManipulationController::didCreateRendererForElement): Added the same check to fail early.
(WebCore::TextManipulationController::scheduleObservartionUpdate):
(WebCore::TextManipulationController::replace): Removed the code to clear m_recentlyInsertedElements.
* editing/TextManipulationController.h:

Tools:

Added a regression test.

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


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@257613 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/editing/TextManipulationController.cpp b/Source/WebCore/editing/TextManipulationController.cpp
index 1e760de..d05af44 100644
--- a/Source/WebCore/editing/TextManipulationController.cpp
+++ b/Source/WebCore/editing/TextManipulationController.cpp
@@ -112,6 +112,17 @@
 {
 }
 
+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());
@@ -144,6 +155,13 @@
         if (startOfCurrentParagraph.isNull())
             startOfCurrentParagraph = iterator.range()->startPosition();
 
+        if (auto* currentNode = iterator.node()) {
+            if (RefPtr<Element> currentElementAncestor = is<Element>(currentNode) ? downcast<Element>(currentNode) : currentNode->parentOrShadowHostElement()) {
+                if (isInManipulatedElement(*currentElementAncestor))
+                    return; // We can exit early here because scheduleObservartionUpdate calls this function on each paragraph separately.
+            }
+        }
+
         size_t endOfLastNewLine = 0;
         size_t offsetOfNextNewLine = 0;
         while ((offsetOfNextNewLine = currentText.find('\n', endOfLastNewLine)) != notFound) {
@@ -178,17 +196,17 @@
 
 void TextManipulationController::didCreateRendererForElement(Element& element)
 {
-    if (m_recentlyInsertedElements.contains(element))
+    if (isInManipulatedElement(element))
         return;
 
-    if (m_mutatedElements.computesEmpty())
+    if (m_elementsWithNewRenderer.computesEmpty())
         scheduleObservartionUpdate();
 
     if (is<PseudoElement>(element)) {
         if (auto* host = downcast<PseudoElement>(element).hostElement())
-            m_mutatedElements.add(*host);
+            m_elementsWithNewRenderer.add(*host);
     } else
-        m_mutatedElements.add(element);
+        m_elementsWithNewRenderer.add(element);
 }
 
 using PositionTuple = std::tuple<RefPtr<Node>, unsigned, unsigned>;
@@ -213,9 +231,9 @@
             return;
 
         HashSet<Ref<Element>> mutatedElements;
-        for (auto& weakElement : controller->m_mutatedElements)
+        for (auto& weakElement : controller->m_elementsWithNewRenderer)
             mutatedElements.add(weakElement);
-        controller->m_mutatedElements.clear();
+        controller->m_elementsWithNewRenderer.clear();
 
         HashSet<Ref<Element>> filteredElements;
         for (auto& element : mutatedElements) {
@@ -402,12 +420,8 @@
         else
             insertion.parentIfDifferentFromCommonAncestor->appendChild(insertion.child);
         if (is<Element>(insertion.child.get()))
-            m_recentlyInsertedElements.add(downcast<Element>(insertion.child.get()));
+            m_manipulatedElements.add(downcast<Element>(insertion.child.get()));
     }
-    m_document->eventLoop().queueTask(TaskSource::InternalAsyncTask, [weakThis = makeWeakPtr(*this)] {
-        if (auto strongThis = weakThis.get())
-            strongThis->m_recentlyInsertedElements.clear();
-    });
 
     return ManipulationResult::Success;
 }