Modified: trunk/Source/WebCore/editing/TextManipulationController.cpp (262600 => 262601)
--- trunk/Source/WebCore/editing/TextManipulationController.cpp 2020-06-05 05:58:55 UTC (rev 262600)
+++ trunk/Source/WebCore/editing/TextManipulationController.cpp 2020-06-05 06:13:30 UTC (rev 262601)
@@ -294,12 +294,13 @@
return false;
}
-TextManipulationController::ManipulationTokens TextManipulationController::parse(StringView text, Node* textNode)
+TextManipulationController::ManipulationUnit TextManipulationController::parse(StringView text, Node* textNode)
{
Vector<ManipulationToken> tokens;
ExclusionRuleMatcher exclusionRuleMatcher(m_exclusionRules);
size_t positionOfLastNonHTMLSpace = WTF::notFound;
size_t startPositionOfCurrentToken = 0;
+ bool isNodeExcluded = exclusionRuleMatcher.isExcluded(textNode);
bool containsOnlyHTMLSpace = true;
bool containsLineBreak = false;
bool firstTokenContainsLineBreak = false;
@@ -315,7 +316,7 @@
containsLineBreak = true;
if (positionOfLastNonHTMLSpace != WTF::notFound && startPositionOfCurrentToken <= positionOfLastNonHTMLSpace) {
auto tokenString = text.substring(startPositionOfCurrentToken, positionOfLastNonHTMLSpace + 1 - startPositionOfCurrentToken).toString();
- tokens.append(ManipulationToken { m_tokenIdentifier.generate(), tokenString, tokenInfo(textNode), exclusionRuleMatcher.isExcluded(textNode) });
+ tokens.append(ManipulationToken { m_tokenIdentifier.generate(), tokenString, tokenInfo(textNode), isNodeExcluded });
startPositionOfCurrentToken = positionOfLastNonHTMLSpace + 1;
}
@@ -337,13 +338,38 @@
if (startPositionOfCurrentToken < text.length()) {
auto tokenString = text.substring(startPositionOfCurrentToken, index + 1 - startPositionOfCurrentToken).toString();
- tokens.append(ManipulationToken { m_tokenIdentifier.generate(), tokenString, tokenInfo(textNode), exclusionRuleMatcher.isExcluded(textNode) });
+ tokens.append(ManipulationToken { m_tokenIdentifier.generate(), tokenString, tokenInfo(textNode), isNodeExcluded });
lastTokenContainsLineBreak = false;
}
- return { WTFMove(tokens), containsOnlyHTMLSpace, containsLineBreak, firstTokenContainsLineBreak, lastTokenContainsLineBreak };
+ return { WTFMove(tokens), *textNode, containsOnlyHTMLSpace || isNodeExcluded, containsLineBreak, firstTokenContainsLineBreak, lastTokenContainsLineBreak };
}
+void TextManipulationController::addItemIfPossible(Vector<ManipulationUnit>&& units)
+{
+ if (units.isEmpty())
+ return;
+
+ size_t index = 0;
+ size_t end = units.size();
+ while (index < units.size() && units[index].areAllTokensExcluded)
+ ++index;
+
+ while (end > 0 && units[end - 1].areAllTokensExcluded)
+ --end;
+
+ if (index == end)
+ return;
+
+ auto startPosition = firstPositionInOrBeforeNode(units.first().node.ptr());
+ auto endPosition = positionAfterNode(units.last().node.ptr());
+ Vector<ManipulationToken> tokens;
+ for (; index < end; ++index)
+ tokens.appendVector(WTFMove(units[index].tokens));
+
+ addItem(ManipulationItemData { startPosition, endPosition, nullptr, nullQName(), WTFMove(tokens) });
+}
+
void TextManipulationController::observeParagraphs(const Position& start, const Position& end)
{
if (start.isNull() || end.isNull())
@@ -351,16 +377,13 @@
auto document = makeRefPtr(start.document());
ASSERT(document);
- ParagraphContentIterator iterator { start, end };
// TextIterator's constructor may have updated the layout and executed arbitrary scripts.
if (document != start.document() || document != end.document())
return;
- Vector<ManipulationToken> tokensInCurrentParagraph;
- Position startOfCurrentParagraph;
- Position endOfCurrentParagraph;
+ Vector<ManipulationUnit> unitsInCurrentParagraph;
RefPtr<Element> enclosingItemBoundaryElement;
-
+ ParagraphContentIterator iterator { start, end };
for (; !iterator.atEnd(); iterator.advance()) {
auto content = iterator.currentContent();
auto* contentNode = content.node.get();
@@ -367,8 +390,7 @@
ASSERT(contentNode);
if (enclosingItemBoundaryElement && !enclosingItemBoundaryElement->contains(contentNode)) {
- if (!tokensInCurrentParagraph.isEmpty())
- addItem(ManipulationItemData { startOfCurrentParagraph, endOfCurrentParagraph, nullptr, nullQName(), std::exchange(tokensInCurrentParagraph, { }) });
+ addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
enclosingItemBoundaryElement = nullptr;
}
@@ -401,10 +423,8 @@
}
if (content.isReplacedContent) {
- if (!tokensInCurrentParagraph.isEmpty()) {
- tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), "[]", tokenInfo(content.node.get()), true });
- endOfCurrentParagraph = positionAfterNode(contentNode);
- }
+ if (!unitsInCurrentParagraph.isEmpty())
+ unitsInCurrentParagraph.append(ManipulationUnit { { ManipulationToken { m_tokenIdentifier.generate(), "[]", tokenInfo(content.node.get()), true } }, *contentNode });
continue;
}
@@ -411,27 +431,20 @@
if (!content.isTextContent)
continue;
- auto tokensInCurrentNode = parse(content.text, contentNode);
- if (!tokensInCurrentParagraph.isEmpty() && tokensInCurrentNode.firstTokenContainsLineBreak)
- addItem(ManipulationItemData { startOfCurrentParagraph, endOfCurrentParagraph, nullptr, nullQName(), std::exchange(tokensInCurrentParagraph, { }) });
+ auto unitsInCurrentNode = parse(content.text, contentNode);
+ if (unitsInCurrentNode.firstTokenContainsLineBreak)
+ addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
- if (tokensInCurrentParagraph.isEmpty()) {
- if (tokensInCurrentNode.containsOnlyHTMLSpace)
+ if (unitsInCurrentParagraph.isEmpty() && unitsInCurrentNode.areAllTokensExcluded)
continue;
- startOfCurrentParagraph = firstPositionInOrBeforeNode(contentNode);
- }
- tokensInCurrentParagraph.appendVector(tokensInCurrentNode.tokens);
- endOfCurrentParagraph = positionAfterNode(contentNode);
+ unitsInCurrentParagraph.append(WTFMove(unitsInCurrentNode));
- if (!tokensInCurrentParagraph.isEmpty() && tokensInCurrentNode.lastTokenContainsLineBreak) {
- ASSERT(!tokensInCurrentParagraph.isEmpty());
- addItem(ManipulationItemData { startOfCurrentParagraph, endOfCurrentParagraph, nullptr, nullQName(), std::exchange(tokensInCurrentParagraph, { }) });
- }
+ if (unitsInCurrentNode.lastTokenContainsLineBreak)
+ addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
}
- if (!tokensInCurrentParagraph.isEmpty())
- addItem(ManipulationItemData { startOfCurrentParagraph, endOfCurrentParagraph, nullptr, nullQName(), WTFMove(tokensInCurrentParagraph) });
+ addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
}
void TextManipulationController::didCreateRendererForElement(Element& element)
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm (262600 => 262601)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm 2020-06-05 05:58:55 UTC (rev 262600)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm 2020-06-05 06:13:30 UTC (rev 262601)
@@ -243,11 +243,9 @@
EXPECT_STREQ("fruit", items[2].tokens[0].content.UTF8String);
EXPECT_FALSE(items[2].tokens[0].isExcluded);
- EXPECT_EQ(items[3].tokens.count, 2UL);
+ EXPECT_EQ(items[3].tokens.count, 1UL);
EXPECT_STREQ("hello", items[3].tokens[0].content.UTF8String);
EXPECT_FALSE(items[3].tokens[0].isExcluded);
- EXPECT_STREQ("[]", items[3].tokens[1].content.UTF8String);
- EXPECT_TRUE(items[3].tokens[1].isExcluded);
}
TEST(TextManipulation, StartTextManipulationSupportsLegacyDelegateCallback)
@@ -451,11 +449,9 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 1UL);
- EXPECT_EQ(items[0].tokens.count, 2UL);
- EXPECT_STREQ("hello, ", items[0].tokens[0].content.UTF8String);
- EXPECT_TRUE(items[0].tokens[0].isExcluded);
- EXPECT_STREQ("world", items[0].tokens[1].content.UTF8String);
- EXPECT_FALSE(items[0].tokens[1].isExcluded);
+ EXPECT_EQ(items[0].tokens.count, 1UL);
+ EXPECT_STREQ("world", items[0].tokens[0].content.UTF8String);
+ EXPECT_FALSE(items[0].tokens[0].isExcluded);
}
TEST(TextManipulation, StartTextManipulationApplyInclusionExclusionRulesForClass)
@@ -465,7 +461,7 @@
[webView _setTextManipulationDelegate:delegate.get()];
[webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
- "<html><body><span class='someClass exclude'>Message: <b>hello, </b><span>world</span></span></body></html>"];
+ "<html><body>Message: <span class='someClass exclude'><b>hello, </b><span>world</span></span></body></html>"];
auto configuration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
[configuration setExclusionRules:@[
@@ -480,13 +476,9 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 1UL);
- EXPECT_EQ(items[0].tokens.count, 3UL);
+ EXPECT_EQ(items[0].tokens.count, 1UL);
EXPECT_STREQ("Message: ", items[0].tokens[0].content.UTF8String);
- EXPECT_TRUE(items[0].tokens[0].isExcluded);
- EXPECT_STREQ("hello, ", items[0].tokens[1].content.UTF8String);
- EXPECT_TRUE(items[0].tokens[1].isExcluded);
- EXPECT_STREQ("world", items[0].tokens[2].content.UTF8String);
- EXPECT_TRUE(items[0].tokens[2].isExcluded);
+ EXPECT_FALSE(items[0].tokens[0].isExcluded);
}
TEST(TextManipulation, StartTextManipulationApplyInclusionExclusionRulesForClassAndAttribute)
@@ -513,13 +505,9 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 1UL);
- EXPECT_EQ(items[0].tokens.count, 3UL);
- EXPECT_STREQ("Message: ", items[0].tokens[0].content.UTF8String);
- EXPECT_TRUE(items[0].tokens[0].isExcluded);
- EXPECT_STREQ("hello, ", items[0].tokens[1].content.UTF8String);
- EXPECT_FALSE(items[0].tokens[1].isExcluded);
- EXPECT_STREQ("world", items[0].tokens[2].content.UTF8String);
- EXPECT_TRUE(items[0].tokens[2].isExcluded);
+ EXPECT_EQ(items[0].tokens.count, 1UL);
+ EXPECT_STREQ("hello, ", items[0].tokens[0].content.UTF8String);
+ EXPECT_FALSE(items[0].tokens[0].isExcluded);
}
TEST(TextManipulation, StartTextManipulationBreaksParagraphInBetweenListItems)
@@ -1293,10 +1281,10 @@
[webView _setTextManipulationDelegate:delegate.get()];
[webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><body>"
- "<section><div style=\"display: inline-block;\">hello</div>"
- "<div style=\"display: inline-block;\"><span style=\"display: inline-flex;\">"
- "<svg viewBox=\"0 0 20 20\" width=\"20\" height=\"20\"><rect width=\"20\" height=\"20\" fill=\"#06f\"></rect></svg>"
- "</span></div></section><p>world</p></body></html>"];
+ "<section><div style=\"display: inline-block;\">hello"
+ "<span style=\"display: inline-flex;\"><svg viewBox=\"0 0 20 20\" width=\"20\" height=\"20\"><rect width=\"20\" height=\"20\" fill=\"#06f\"></rect></svg></span>"
+ "webkit</div></section>"
+ "<p>world</p></body></html>"];
done = false;
[webView _startTextManipulationsWithConfiguration:nil completion:^{
@@ -1307,11 +1295,13 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 2UL);
auto *tokens = items[0].tokens;
- EXPECT_EQ(tokens.count, 2UL);
+ EXPECT_EQ(tokens.count, 3UL);
EXPECT_STREQ("hello", tokens[0].content.UTF8String);
EXPECT_FALSE(tokens[0].isExcluded);
EXPECT_STREQ("[]", tokens[1].content.UTF8String);
EXPECT_TRUE(tokens[1].isExcluded);
+ EXPECT_STREQ("webkit", tokens[2].content.UTF8String);
+ EXPECT_FALSE(tokens[2].isExcluded);
EXPECT_EQ(items[1].tokens.count, 1UL);
EXPECT_STREQ("world", items[1].tokens[0].content.UTF8String);
@@ -1321,15 +1311,13 @@
[webView _completeTextManipulationForItems:@[(_WKTextManipulationItem *)createItem(items[0].identifier, {
{ tokens[0].identifier, @"hey" },
{ tokens[1].identifier, nil },
+ { tokens[2].identifier, @"WebKit" },
})] completion:^(NSArray<NSError *> *errors) {
EXPECT_EQ(errors, nil);
done = true;
}];
TestWebKitAPI::Util::run(&done);
- EXPECT_WK_STREQ("<section><div style=\"display: inline-block;\">hey</div>"
- "<div style=\"display: inline-block;\"><span style=\"display: inline-flex;\">"
- "<svg viewBox=\"0 0 20 20\" width=\"20\" height=\"20\"><rect width=\"20\" height=\"20\" fill=\"#06f\"></rect></svg>"
- "</span></div></section><p>world</p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+ EXPECT_WK_STREQ("<section><div style=\"display: inline-block;\">hey<span style=\"display: inline-flex;\"><svg viewBox=\"0 0 20 20\" width=\"20\" height=\"20\"><rect width=\"20\" height=\"20\" fill=\"#06f\"></rect></svg></span>WebKit</div></section><p>world</p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
}
TEST(TextManipulation, CompleteTextManipulationShouldPreserveOrderOfBlockImage)
@@ -1395,9 +1383,8 @@
EXPECT_EQ(items[2].tokens.count, 1UL);
EXPECT_STREQ("fruit", items[2].tokens[0].content.UTF8String);
- EXPECT_EQ(items[3].tokens.count, 2UL);
+ EXPECT_EQ(items[3].tokens.count, 1UL);
EXPECT_STREQ("hello", items[3].tokens[0].content.UTF8String);
- EXPECT_STREQ("[]", items[3].tokens[1].content.UTF8String);
done = false;
[webView _completeTextManipulationForItems:@[
@@ -1475,7 +1462,7 @@
EXPECT_EQ(items[2].tokens.count, 1UL);
EXPECT_EQ(items[3].tokens.count, 1UL);
EXPECT_EQ(items[4].tokens.count, 1UL);
- EXPECT_EQ(items[5].tokens.count, 2UL);
+ EXPECT_EQ(items[5].tokens.count, 1UL);
EXPECT_WK_STREQ("This is a test", items[0].tokens[0].content);
EXPECT_WK_STREQ("Hello world", items[1].tokens[0].content);
EXPECT_WK_STREQ("Should not be replaced", items[2].tokens[0].content);
@@ -1482,7 +1469,6 @@
EXPECT_WK_STREQ("label", items[3].tokens[0].content);
EXPECT_WK_STREQ("image", items[4].tokens[0].content);
EXPECT_WK_STREQ("Text", items[5].tokens[0].content);
- EXPECT_WK_STREQ("[]", items[5].tokens[1].content);
auto replacementItems = retainPtr(@[
createItem(items[0].identifier, { { items[0].tokens[0].identifier, @"Replacement" }, { items[0].tokens[0].identifier, @"title" } }).autorelease(),
@@ -1925,7 +1911,7 @@
[webView _setTextManipulationDelegate:delegate.get()];
[webView synchronouslyLoadTestPageNamed:@"simple"];
- [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hi, <em>WebKitten</em></p>'"];
+ [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hi, <em>WebKitten</em> bye</p>'"];
RetainPtr<_WKTextManipulationConfiguration> configuration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
[configuration setExclusionRules:@[
@@ -1940,14 +1926,16 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 1UL);
- EXPECT_EQ(items[0].tokens.count, 2UL);
+ EXPECT_EQ(items[0].tokens.count, 3UL);
EXPECT_STREQ("hi, ", items[0].tokens[0].content.UTF8String);
EXPECT_STREQ("WebKitten", items[0].tokens[1].content.UTF8String);
+ EXPECT_STREQ(" bye", items[0].tokens[2].content.UTF8String);
done = false;
__block auto item = createItem(items[0].identifier, {
{ items[0].tokens[0].identifier, @"Hello," },
{ items[0].tokens[1].identifier, @"WebKit" },
+ { items[0].tokens[2].identifier, @"Bye" },
});
[webView _completeTextManipulationForItems:@[item.get()] completion:^(NSArray<NSError *> *errors) {
EXPECT_EQ(errors.count, 1UL);
@@ -1958,7 +1946,7 @@
}];
TestWebKitAPI::Util::run(&done);
- EXPECT_WK_STREQ("<p>hi, <em>WebKitten</em></p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+ EXPECT_WK_STREQ("<p>hi, <em>WebKitten</em> bye</p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
}
TEST(TextManipulation, CompleteTextManipulationFailWhenExcludedContentAppearsMoreThanOnce)
@@ -1968,7 +1956,7 @@
[webView _setTextManipulationDelegate:delegate.get()];
[webView synchronouslyLoadTestPageNamed:@"simple"];
- [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hi, <em>WebKitten</em></p>'"];
+ [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hi, <em>WebKitten</em> bye</p>'"];
RetainPtr<_WKTextManipulationConfiguration> configuration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
[configuration setExclusionRules:@[
@@ -1983,9 +1971,10 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 1UL);
- EXPECT_EQ(items[0].tokens.count, 2UL);
+ EXPECT_EQ(items[0].tokens.count, 3UL);
EXPECT_STREQ("hi, ", items[0].tokens[0].content.UTF8String);
EXPECT_STREQ("WebKitten", items[0].tokens[1].content.UTF8String);
+ EXPECT_STREQ(" bye", items[0].tokens[2].content.UTF8String);
done = false;
__block auto item = createItem(items[0].identifier, {
@@ -1992,6 +1981,7 @@
{ items[0].tokens[1].identifier, nil },
{ items[0].tokens[0].identifier, @"Hello," },
{ items[0].tokens[1].identifier, nil },
+ { items[0].tokens[2].identifier, @"Bye" },
});
[webView _completeTextManipulationForItems:@[item.get()] completion:^(NSArray<NSError *> *errors) {
EXPECT_EQ(errors.count, 1UL);
@@ -2002,7 +1992,7 @@
}];
TestWebKitAPI::Util::run(&done);
- EXPECT_WK_STREQ("<p>hi, <em>WebKitten</em></p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+ EXPECT_WK_STREQ("<p>hi, <em>WebKitten</em> bye</p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
}
TEST(TextManipulation, CompleteTextManipulationPreservesExcludedContent)
@@ -2011,7 +2001,7 @@
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
[webView _setTextManipulationDelegate:delegate.get()];
- [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><body><p>hi, <em>WebKitten</em></p></body></html>"];
+ [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><body><p>hi, <em>WebKitten</em> bye</p></body></html>"];
RetainPtr<_WKTextManipulationConfiguration> configuration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
[configuration setExclusionRules:@[
@@ -2026,14 +2016,16 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 1UL);
- EXPECT_EQ(items[0].tokens.count, 2UL);
+ EXPECT_EQ(items[0].tokens.count, 3UL);
EXPECT_STREQ("hi, ", items[0].tokens[0].content.UTF8String);
EXPECT_STREQ("WebKitten", items[0].tokens[1].content.UTF8String);
+ EXPECT_STREQ(" bye", items[0].tokens[2].content.UTF8String);
done = false;
[webView _completeTextManipulationForItems:@[(_WKTextManipulationItem *)createItem(items[0].identifier, {
{ items[0].tokens[0].identifier, @"Hello, " },
{ items[0].tokens[1].identifier, nil },
+ { items[0].tokens[2].identifier, @" Bye" },
})] completion:^(NSArray<NSError *> *errors) {
EXPECT_EQ(errors, nil);
done = true;
@@ -2040,7 +2032,7 @@
}];
TestWebKitAPI::Util::run(&done);
- EXPECT_WK_STREQ("<p>Hello, <em>WebKitten</em></p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+ EXPECT_WK_STREQ("<p>Hello, <em>WebKitten</em> Bye</p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
}
TEST(TextManipulation, CompleteTextManipulationDoesNotCreateMoreTextManipulationItems)