TextManipulationController should observe newly inserted or displayed contents
https://bugs.webkit.org/show_bug.cgi?id=205203
<rdar://problem/56567020>
Reviewed by Wenson Hsieh.
Source/WebCore:
This patch makes TextManipulationController detect newly inserted or displayed contents and invoke
the callbacks with the newly found items.
To do this, we add a new WeakHashSet to TextManipulationController to which an element is added
whenever its renderer is created. Because it's expensive (and not safe) to find paragraphs around
a newly inserted content, we schedule a new event loop task to do this work.
To find newly inserted paragraphs, we first expand the element's boundary to its start and end of
paragraphs. Because each element in this paragraph could have been added in the weak hash set, we
use hash map to de-duplicate start and end positions. We also filter out any element whose parent
is also in the weak hash set since they would simply find inner paragraphs.
Tests: TextManipulation.StartTextManipulationFindNewlyInsertedParagraph
TextManipulation.StartTextManipulationFindNewlyDisplayedParagraph
TextManipulation.StartTextManipulationFindSameParagraphWithNewContent
* dom/TaskSource.h:
(WebCore::TaskSource::InternalAsyncTask): Added.
* editing/TextManipulationController.cpp:
(WebCore::TextManipulationController::startObservingParagraphs):
(WebCore::TextManipulationController::observeParagraphs): Extracted out of startObservingParagraphs.
(WebCore::TextManipulationController::didCreateRendererForElement): Added. Gets called whenever
a new RenderElement is created.
(WebCore::makePositionTuple): Added.
(WebCore::makeHashablePositionRange): Added.
(WebCore::TextManipulationController::scheduleObservartionUpdate): Added.
* editing/TextManipulationController.h:
* rendering/updating/RenderTreeUpdater.cpp:
(WebCore::RenderTreeUpdater::createRenderer):
Tools:
Added tests for detecting newly inserted or displayed contents in WKTextManipulation SPI.
* TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm:
(-[TextManipulationDelegate initWithItemCallback]):
(-[TextManipulationDelegate _webView:didFindTextManipulationItem:]):
(TestWebKitAPI::TEST):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@253583 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/editing/TextManipulationController.cpp b/Source/WebCore/editing/TextManipulationController.cpp
index d2e39f7..e17bdec 100644
--- a/Source/WebCore/editing/TextManipulationController.cpp
+++ b/Source/WebCore/editing/TextManipulationController.cpp
@@ -29,6 +29,7 @@
#include "CharacterData.h"
#include "Editing.h"
#include "ElementAncestorIterator.h"
+#include "EventLoop.h"
#include "ScriptDisallowedScope.h"
#include "TextIterator.h"
#include "VisibleUnits.h"
@@ -118,9 +119,17 @@
VisiblePosition start = firstPositionInNode(m_document.get());
VisiblePosition end = lastPositionInNode(m_document.get());
+
+ observeParagraphs(start, end);
+}
+
+void TextManipulationController::observeParagraphs(VisiblePosition& start, VisiblePosition& end)
+{
+ auto document = makeRefPtr(start.deepEquivalent().document());
+ ASSERT(document);
TextIterator iterator { start.deepEquivalent(), end.deepEquivalent() };
- if (!document)
- return; // VisiblePosition or TextIterator's constructor may have updated the layout and executed arbitrary scripts.
+ if (document != start.deepEquivalent().document() || document != end.deepEquivalent().document())
+ return; // TextIterator's constructor may have updated the layout and executed arbitrary scripts.
ExclusionRuleMatcher exclusionRuleMatcher(m_exclusionRules);
Vector<ManipulationToken> tokensInCurrentParagraph;
@@ -163,6 +172,65 @@
addItem(startOfCurrentParagraph, end.deepEquivalent(), WTFMove(tokensInCurrentParagraph));
}
+void TextManipulationController::didCreateRendererForElement(Element& element)
+{
+ if (m_mutatedElements.computesEmpty())
+ scheduleObservartionUpdate();
+ m_mutatedElements.add(element);
+}
+
+using PositionTuple = std::tuple<RefPtr<Node>, unsigned, unsigned>;
+static const PositionTuple makePositionTuple(const Position& position)
+{
+ return { position.anchorNode(), static_cast<unsigned>(position.anchorType()), position.anchorType() == Position::PositionIsOffsetInAnchor ? position.offsetInContainerNode() : 0 };
+}
+
+static const std::pair<PositionTuple, PositionTuple> makeHashablePositionRange(const VisiblePosition& start, const VisiblePosition& end)
+{
+ return { makePositionTuple(start.deepEquivalent()), makePositionTuple(end.deepEquivalent()) };
+}
+
+void TextManipulationController::scheduleObservartionUpdate()
+{
+ if (!m_document)
+ return;
+
+ m_document->eventLoop().queueTask(TaskSource::InternalAsyncTask, [weakThis = makeWeakPtr(*this)] {
+ auto* controller = weakThis.get();
+ if (!controller)
+ return;
+
+ HashSet<Ref<Element>> mutatedElements;
+ for (auto& weakElement : controller->m_mutatedElements)
+ mutatedElements.add(weakElement);
+ controller->m_mutatedElements.clear();
+
+ HashSet<Ref<Element>> filteredElements;
+ for (auto& element : mutatedElements) {
+ auto* parentElement = element->parentElement();
+ if (!parentElement || !mutatedElements.contains(parentElement))
+ filteredElements.add(element.copyRef());
+ }
+ mutatedElements.clear();
+
+ HashSet<std::pair<PositionTuple, PositionTuple>> paragraphSets;
+ for (auto& element : filteredElements) {
+ auto start = startOfParagraph(firstPositionInOrBeforeNode(element.ptr()));
+ auto end = endOfParagraph(lastPositionInOrAfterNode(element.ptr()));
+
+ auto key = makeHashablePositionRange(start, end);
+ if (!paragraphSets.add(key).isNewEntry)
+ continue;
+
+ auto* controller = weakThis.get();
+ if (!controller)
+ return; // Finding the start/end of paragraph may have updated layout & executed arbitrary scripts.
+
+ controller->observeParagraphs(start, end);
+ }
+ });
+}
+
void TextManipulationController::addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&& tokens)
{
ASSERT(m_document);