Diff
Modified: branches/safari-610.1.7-branch/Source/WebCore/ChangeLog (258376 => 258377)
--- branches/safari-610.1.7-branch/Source/WebCore/ChangeLog 2020-03-13 00:51:38 UTC (rev 258376)
+++ branches/safari-610.1.7-branch/Source/WebCore/ChangeLog 2020-03-13 00:51:42 UTC (rev 258377)
@@ -1,5 +1,9 @@
2020-03-12 Russell Epstein <repst...@apple.com>
+ Revert r258353. rdar://problem/60395490
+
+2020-03-12 Russell Epstein <repst...@apple.com>
+
Revert r258354. rdar://problem/60395490
2020-03-12 Russell Epstein <repst...@apple.com>
Modified: branches/safari-610.1.7-branch/Source/WebCore/editing/TextManipulationController.cpp (258376 => 258377)
--- branches/safari-610.1.7-branch/Source/WebCore/editing/TextManipulationController.cpp 2020-03-13 00:51:38 UTC (rev 258376)
+++ branches/safari-610.1.7-branch/Source/WebCore/editing/TextManipulationController.cpp 2020-03-13 00:51:42 UTC (rev 258377)
@@ -30,6 +30,7 @@
#include "Editing.h"
#include "ElementAncestorIterator.h"
#include "EventLoop.h"
+#include "HTMLNames.h"
#include "NodeTraversal.h"
#include "PseudoElement.h"
#include "Range.h"
@@ -132,10 +133,7 @@
m_callback = WTFMove(callback);
m_exclusionRules = WTFMove(exclusionRules);
- VisiblePosition start = firstPositionInNode(m_document.get());
- VisiblePosition end = lastPositionInNode(m_document.get());
-
- observeParagraphs(start, end);
+ observeParagraphs(firstPositionInNode(m_document.get()), lastPositionInNode(m_document.get()));
flushPendingItemsForCallback();
}
@@ -219,17 +217,38 @@
RefPtr<Node> m_pastEndNode;
};
-void TextManipulationController::observeParagraphs(VisiblePosition& start, VisiblePosition& end)
+static bool isAttributeForTextManipulation(const QualifiedName& nameToCheck)
{
- auto document = makeRefPtr(start.deepEquivalent().document());
+ using namespace HTMLNames;
+ static const QualifiedName* const attributeNames[] = {
+ &titleAttr.get(),
+ &altAttr.get(),
+ &placeholderAttr.get(),
+ &aria_labelAttr.get(),
+ &aria_placeholderAttr.get(),
+ &aria_roledescriptionAttr.get(),
+ &aria_valuetextAttr.get(),
+ };
+ for (auto& entry : attributeNames) {
+ if (*entry == nameToCheck)
+ return true;
+ }
+ return false;
+}
+
+void TextManipulationController::observeParagraphs(const Position& start, const Position& end)
+{
+ auto document = makeRefPtr(start.document());
ASSERT(document);
- ParagraphContentIterator iterator { start.deepEquivalent(), end.deepEquivalent() };
- if (document != start.deepEquivalent().document() || document != end.deepEquivalent().document())
+ ParagraphContentIterator iterator { start, end };
+ VisiblePosition visibleStart = start;
+ VisiblePosition visibleEnd = end;
+ if (document != start.document() || document != end.document())
return; // TextIterator's constructor may have updated the layout and executed arbitrary scripts.
ExclusionRuleMatcher exclusionRuleMatcher(m_exclusionRules);
Vector<ManipulationToken> tokensInCurrentParagraph;
- Position startOfCurrentParagraph = start.deepEquivalent();
+ Position startOfCurrentParagraph = visibleStart.deepEquivalent();
for (; !iterator.atEnd(); iterator.advance()) {
auto content = iterator.currentContent();
if (content.node) {
@@ -238,7 +257,23 @@
return; // We can exit early here because scheduleObservartionUpdate calls this function on each paragraph separately.
}
- if (startOfCurrentParagraph.isNull())
+ if (is<Element>(*content.node)) {
+ auto& currentElement = downcast<Element>(*content.node);
+ if (!content.isTextContent && (content.node->hasTagName(HTMLNames::titleTag) || content.node->hasTagName(HTMLNames::optionTag))) {
+ addItem(ManipulationItemData { Position(), Position(), makeWeakPtr(currentElement), nullQName(),
+ { ManipulationToken { m_tokenIdentifier.generate(), currentElement.textContent() } } });
+ }
+ if (currentElement.hasAttributes()) {
+ for (auto& attribute : currentElement.attributesIterator()) {
+ if (isAttributeForTextManipulation(attribute.name())) {
+ addItem(ManipulationItemData { Position(), Position(), makeWeakPtr(currentElement), attribute.name(),
+ { ManipulationToken { m_tokenIdentifier.generate(), attribute.value() } } });
+ }
+ }
+ }
+ }
+
+ if (startOfCurrentParagraph.isNull() && content.isTextContent)
startOfCurrentParagraph = iterator.startPosition();
}
@@ -266,7 +301,7 @@
endOfCurrentParagraph = Position(&textNode, offsetOfNextNewLine);
startOfCurrentParagraph = Position(&textNode, offsetOfNextNewLine + 1);
}
- addItem(startOfCurrentParagraph, endOfCurrentParagraph, WTFMove(tokensInCurrentParagraph));
+ addItem(ManipulationItemData { startOfCurrentParagraph, endOfCurrentParagraph, nullptr, nullQName(), WTFMove(tokensInCurrentParagraph) });
startOfCurrentParagraph.clear();
}
startOfCurrentLine = offsetOfNextNewLine + 1;
@@ -278,7 +313,7 @@
}
if (!tokensInCurrentParagraph.isEmpty())
- addItem(startOfCurrentParagraph, end.deepEquivalent(), WTFMove(tokensInCurrentParagraph));
+ addItem(ManipulationItemData { startOfCurrentParagraph, visibleEnd.deepEquivalent(), nullptr, nullQName(), WTFMove(tokensInCurrentParagraph) });
}
void TextManipulationController::didCreateRendererForElement(Element& element)
@@ -343,13 +378,13 @@
if (!controller)
return; // Finding the start/end of paragraph may have updated layout & executed arbitrary scripts.
- controller->observeParagraphs(start, end);
+ controller->observeParagraphs(start.deepEquivalent(), end.deepEquivalent());
}
controller->flushPendingItemsForCallback();
});
}
-void TextManipulationController::addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&& tokens)
+void TextManipulationController::addItem(ManipulationItemData&& itemData)
{
const unsigned itemCallbackBatchingSize = 128;
@@ -357,9 +392,9 @@
auto newID = m_itemIdentifier.generate();
m_pendingItemsForCallback.append(ManipulationItem {
newID,
- tokens.map([](auto& token) { return token; })
+ itemData.tokens.map([](auto& token) { return token; })
});
- m_items.add(newID, ManipulationItemData { startOfParagraph, endOfParagraph, WTFMove(tokens) });
+ m_items.add(newID, WTFMove(itemData));
if (m_pendingItemsForCallback.size() >= itemCallbackBatchingSize)
flushPendingItemsForCallback();
@@ -424,6 +459,26 @@
size_t currentTokenIndex = 0;
HashMap<TokenIdentifier, TokenExchangeData> tokenExchangeMap;
+ if (item.start.isNull() && item.end.isNull()) {
+ RELEASE_ASSERT(item.tokens.size() == 1);
+ auto element = makeRefPtr(item.element.get());
+ if (!element)
+ return ManipulationFailureType::ContentChanged;
+ if (replacementTokens.size() > 1)
+ return ManipulationFailureType::InvalidToken;
+ String newValue;
+ if (!replacementTokens.isEmpty()) {
+ if (replacementTokens[0].identifier != item.tokens[0].identifier)
+ return ManipulationFailureType::InvalidToken;
+ newValue = replacementTokens[0].content;
+ }
+ if (item.attributeName == nullQName())
+ element->setTextContent(newValue);
+ else
+ element->setAttribute(item.attributeName, newValue);
+ return WTF::nullopt;
+ }
+
RefPtr<Node> commonAncestor;
ParagraphContentIterator iterator { item.start, item.end };
HashSet<Ref<Node>> excludedNodes;
Modified: branches/safari-610.1.7-branch/Source/WebCore/editing/TextManipulationController.h (258376 => 258377)
--- branches/safari-610.1.7-branch/Source/WebCore/editing/TextManipulationController.h 2020-03-13 00:51:38 UTC (rev 258376)
+++ branches/safari-610.1.7-branch/Source/WebCore/editing/TextManipulationController.h 2020-03-13 00:51:42 UTC (rev 258377)
@@ -26,6 +26,7 @@
#pragma once
#include "Position.h"
+#include "QualifiedName.h"
#include <wtf/CompletionHandler.h>
#include <wtf/EnumTraits.h>
#include <wtf/ObjectIdentifier.h>
@@ -126,16 +127,20 @@
private:
bool isInManipulatedElement(Element&);
- void observeParagraphs(VisiblePosition& start, VisiblePosition& end);
+ void observeParagraphs(const Position& start, const Position& end);
void scheduleObservartionUpdate();
struct ManipulationItemData {
Position start;
Position end;
+
+ WeakPtr<Element> element;
+ QualifiedName attributeName { nullQName() };
+
Vector<ManipulationToken> tokens;
};
- void addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&&);
+ void addItem(ManipulationItemData&&);
void flushPendingItemsForCallback();
Optional<ManipulationFailureType> replace(const ManipulationItemData&, const Vector<ManipulationToken>&);
Modified: branches/safari-610.1.7-branch/Tools/ChangeLog (258376 => 258377)
--- branches/safari-610.1.7-branch/Tools/ChangeLog 2020-03-13 00:51:38 UTC (rev 258376)
+++ branches/safari-610.1.7-branch/Tools/ChangeLog 2020-03-13 00:51:42 UTC (rev 258377)
@@ -1,5 +1,9 @@
2020-03-12 Russell Epstein <repst...@apple.com>
+ Revert r258353. rdar://problem/60395490
+
+2020-03-12 Russell Epstein <repst...@apple.com>
+
Revert r258354. rdar://problem/60395490
2020-03-12 Russell Epstein <repst...@apple.com>
Modified: branches/safari-610.1.7-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm (258376 => 258377)
--- branches/safari-610.1.7-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm 2020-03-13 00:51:38 UTC (rev 258376)
+++ branches/safari-610.1.7-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm 2020-03-13 00:51:42 UTC (rev 258377)
@@ -216,6 +216,42 @@
EXPECT_STREQ("Kit", items[1].tokens[1].content.UTF8String);
}
+TEST(TextManipulation, StartTextManipulationFindAttributeContent)
+{
+ auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+ [webView _setTextManipulationDelegate:delegate.get()];
+
+ [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><head><title>hey</title></head>"
+ "<body><div><span aria-label=\"this is greet\">hello</span><img src="" alt=\"fruit\"></body></html>"];
+
+ done = false;
+ [webView _startTextManipulationsWithConfiguration:nil completion:^{
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+
+ auto *items = [delegate items];
+ EXPECT_EQ(items.count, 4UL);
+ EXPECT_EQ(items[0].tokens.count, 1UL);
+ EXPECT_STREQ("hey", items[0].tokens[0].content.UTF8String);
+ EXPECT_FALSE(items[0].tokens[0].isExcluded);
+
+ EXPECT_EQ(items[1].tokens.count, 1UL);
+ EXPECT_STREQ("this is greet", items[1].tokens[0].content.UTF8String);
+ EXPECT_FALSE(items[1].tokens[0].isExcluded);
+
+ EXPECT_EQ(items[2].tokens.count, 1UL);
+ EXPECT_STREQ("fruit", items[2].tokens[0].content.UTF8String);
+ EXPECT_FALSE(items[2].tokens[0].isExcluded);
+
+ EXPECT_EQ(items[3].tokens.count, 2UL);
+ 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)
{
auto delegate = adoptNS([[LegacyTextManipulationDelegate alloc] init]);
@@ -800,9 +836,11 @@
auto *items = [delegate items];
EXPECT_EQ(items.count, 2UL);
- EXPECT_EQ(items[0].tokens.count, 1UL);
+ EXPECT_EQ(items[0].tokens.count, 2UL);
EXPECT_STREQ("[]", items[0].tokens[0].content.UTF8String);
EXPECT_TRUE(items[0].tokens[0].isExcluded);
+ EXPECT_STREQ("[]", items[0].tokens[1].content.UTF8String);
+ EXPECT_TRUE(items[0].tokens[1].isExcluded);
auto *tokens = items[1].tokens;
EXPECT_EQ(tokens.count, 1UL);
@@ -824,6 +862,50 @@
[webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
}
+TEST(TextManipulation, CompleteTextManipulationShouldReplaceAttributeContent)
+{
+ auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+ [webView _setTextManipulationDelegate:delegate.get()];
+
+ [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><head><title>hey</title></head>"
+ "<body><div><span aria-label=\"this is greet\">hello</span><img src="" alt=\"fruit\"></div></body></html>"];
+
+ done = false;
+ [webView _startTextManipulationsWithConfiguration:nil completion:^{
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+
+ auto *items = [delegate items];
+ EXPECT_EQ(items.count, 4UL);
+ EXPECT_EQ(items[0].tokens.count, 1UL);
+ EXPECT_STREQ("hey", items[0].tokens[0].content.UTF8String);
+
+ EXPECT_EQ(items[1].tokens.count, 1UL);
+ EXPECT_STREQ("this is greet", items[1].tokens[0].content.UTF8String);
+
+ EXPECT_EQ(items[2].tokens.count, 1UL);
+ EXPECT_STREQ("fruit", items[2].tokens[0].content.UTF8String);
+
+ EXPECT_EQ(items[3].tokens.count, 2UL);
+ EXPECT_STREQ("hello", items[3].tokens[0].content.UTF8String);
+ EXPECT_STREQ("[]", items[3].tokens[1].content.UTF8String);
+
+ done = false;
+ [webView _completeTextManipulationForItems:@[
+ (_WKTextManipulationItem *)createItem(items[0].identifier, { { items[0].tokens[0].identifier, @"Hello" } }),
+ (_WKTextManipulationItem *)createItem(items[1].identifier, { { items[1].tokens[0].identifier, @"This is a greeting" } }),
+ (_WKTextManipulationItem *)createItem(items[2].identifier, { { items[2].tokens[0].identifier, @"Apple" } }),
+ ] completion:^(NSArray<NSError *> *errors) {
+ EXPECT_EQ(errors, nil);
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+ EXPECT_WK_STREQ("<head><title>Hello</title></head><body><div><span aria-label=\"This is a greeting\">hello</span>"
+ "<img src="" alt=\"Apple\"></div></body>", [webView stringByEvaluatingJavaScript:@"document.documentElement.innerHTML"]);
+}
+
TEST(TextManipulation, CompleteTextManipulationShouldBatchItemCallback)
{
auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);