Add exclusion rules to text manipulation SPI
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Oct 2019 20:41:15 +0000 (20:41 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Oct 2019 20:41:15 +0000 (20:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=203398
<rdar://problem/56567256>

Reviewed by Wenson Hsieh.

Source/WebCore:

This patch adds the ability to define a ordered set of rules to exclude or include content an element of
a certain name or one with a certain attribute value.

Also made completeManipulation return more detailed errors for debugging purposes.

Tests: TextManipulation.StartTextManipulationApplySingleExcluionRuleForElement
       TextManipulation.StartTextManipulationApplyInclusionExclusionRulesForAttributes
       TextManipulation.CompleteTextManipulationFailWhenExclusionIsViolated

* editing/TextManipulationController.cpp:
(WebCore::TextManipulationController::ExclusionRule::match const): Added.
(WebCore::ExclusionRuleMatcher): Added. This class is responsible for figuring out whether a given node
is excluded or included in the text manipulation.
(WebCore::ExclusionRuleMatcher::ExclusionRuleMatcher): Added.
(WebCore::ExclusionRuleMatcher::isExcluded): Added.
(WebCore::ExclusionRuleMatcher::typeForElement): Added.
(WebCore::TextManipulationController::startObservingParagraphs): Added a Vector of ExclusionRule as
an argument.
(WebCore::TextManipulationController::completeManipulation):
(WebCore::TextManipulationController::replace):
* editing/TextManipulationController.h:
(WebCore::TextManipulationController::ExclusionRule): Added.
(WebCore::TextManipulationController::ManipulationResult): Added.
(WebCore::TextManipulationController::ManipulationToken::encode const): Include isExcluded boolean.
(WebCore::TextManipulationController::ManipulationToken::decode): Ditto.
(WebCore::TextManipulationController::ExclusionRule::encode const): Added.
(WebCore::TextManipulationController::ExclusionRule::decode): Added.
(WebCore::TextManipulationController::ExclusionRule::ElementRule::encode const): Added.
(WebCore::TextManipulationController::ExclusionRule::ElementRule::decode): Added.
(WebCore::TextManipulationController::ExclusionRule::AttributeRule::encode const): Added.
(WebCore::TextManipulationController::ExclusionRule::AttributeRule::decode): Added.

Source/WebKit:

Added SPI to specify the configuration for the text manipulation (see r251574), in particular, the set of rules
governing which content should be excluded or included in text manipulations.

Test: TextManipulation.StartTextManipulationExitEarlyWithoutDelegate

* SourcesCocoa.txt:
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _startTextManipulationsWithConfiguration:completion:]): Takes _WKTextManipulationConfiguration
as an argument. Also fixed a bug that we weren't calling the completion handler when the delegate was not set.
(-[WKWebView _completeTextManipulation:completion:]):
(-[WKWebView _startTextManipulationsWithCompletionHandler:]): Deleted.
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/API/Cocoa/_WKTextManipulationConfiguration.h: Added.
* UIProcess/API/Cocoa/_WKTextManipulationConfiguration.mm: Added.
* UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.h: Added.
* UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.mm: Added.
(-[_WKTextManipulationExclusionRule initExclusion:forElement:]): Added.
(-[_WKTextManipulationExclusionRule initExclusion:forAttribute:value:]): Added.
(-[_WKTextManipulationExclusionRule elementName]): Added.
(-[_WKTextManipulationExclusionRule attributeName]): Added.
(-[_WKTextManipulationExclusionRule attributeValue]): Added.
* UIProcess/API/Cocoa/_WKTextManipulationToken.h: Added excluded boolean property.
* UIProcess/API/Cocoa/_WKTextManipulationToken.mm: Removed the superflous import of RetainPtr.h
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::startTextManipulations):
(WebKit::WebPageProxy::completeTextManipulation):
* UIProcess/WebPageProxy.h:
* WebKit.xcodeproj/project.pbxproj:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::startTextManipulations):
(WebKit::WebPage::completeTextManipulation):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

Added tests for including & excluding content based on element names and attribute values.

Also added a test to make sure _startTextManipulationsWithConfiguration calls the completion handler
even when the _WKTextManipulationDelegate isn't set.

* TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@251600 268f45cc-cd09-0410-ab3c-d52691b4dbfc

23 files changed:
Source/WebCore/ChangeLog
Source/WebCore/editing/TextManipulationController.cpp
Source/WebCore/editing/TextManipulationController.h
Source/WebKit/ChangeLog
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationConfiguration.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationConfiguration.mm [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.mm [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.h
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.mm
Source/WebKit/UIProcess/Cocoa/SOAuthorization/RedirectSOAuthorizationSession.mm
Source/WebKit/UIProcess/Cocoa/SOAuthorization/SOAuthorizationCoordinator.mm
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm

index e84212b..e2c6668 100644 (file)
@@ -1,3 +1,43 @@
+2019-10-24  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add exclusion rules to text manipulation SPI
+        https://bugs.webkit.org/show_bug.cgi?id=203398
+        <rdar://problem/56567256>
+
+        Reviewed by Wenson Hsieh.
+
+        This patch adds the ability to define a ordered set of rules to exclude or include content an element of
+        a certain name or one with a certain attribute value.
+
+        Also made completeManipulation return more detailed errors for debugging purposes.
+
+        Tests: TextManipulation.StartTextManipulationApplySingleExcluionRuleForElement
+               TextManipulation.StartTextManipulationApplyInclusionExclusionRulesForAttributes
+               TextManipulation.CompleteTextManipulationFailWhenExclusionIsViolated
+
+        * editing/TextManipulationController.cpp:
+        (WebCore::TextManipulationController::ExclusionRule::match const): Added.
+        (WebCore::ExclusionRuleMatcher): Added. This class is responsible for figuring out whether a given node
+        is excluded or included in the text manipulation.
+        (WebCore::ExclusionRuleMatcher::ExclusionRuleMatcher): Added.
+        (WebCore::ExclusionRuleMatcher::isExcluded): Added.
+        (WebCore::ExclusionRuleMatcher::typeForElement): Added.
+        (WebCore::TextManipulationController::startObservingParagraphs): Added a Vector of ExclusionRule as
+        an argument.
+        (WebCore::TextManipulationController::completeManipulation):
+        (WebCore::TextManipulationController::replace):
+        * editing/TextManipulationController.h:
+        (WebCore::TextManipulationController::ExclusionRule): Added.
+        (WebCore::TextManipulationController::ManipulationResult): Added.
+        (WebCore::TextManipulationController::ManipulationToken::encode const): Include isExcluded boolean.
+        (WebCore::TextManipulationController::ManipulationToken::decode): Ditto.
+        (WebCore::TextManipulationController::ExclusionRule::encode const): Added.
+        (WebCore::TextManipulationController::ExclusionRule::decode): Added.
+        (WebCore::TextManipulationController::ExclusionRule::ElementRule::encode const): Added.
+        (WebCore::TextManipulationController::ExclusionRule::ElementRule::decode): Added.
+        (WebCore::TextManipulationController::ExclusionRule::AttributeRule::encode const): Added.
+        (WebCore::TextManipulationController::ExclusionRule::AttributeRule::decode): Added.
+
 2019-10-25  Simon Fraser  <simon.fraser@apple.com>
 
         Move code around in CSSCalculationValue.cpp
index 2e9d813..5741edd 100644 (file)
 #include "config.h"
 #include "TextManipulationController.h"
 
+#include "CharacterData.h"
 #include "Editing.h"
+#include "ElementAncestorIterator.h"
 #include "ScriptDisallowedScope.h"
 #include "TextIterator.h"
 #include "VisibleUnits.h"
 
 namespace WebCore {
 
+inline bool TextManipulationController::ExclusionRule::match(const Element& element) const
+{
+    return switchOn(rule, [&element] (ElementRule rule) {
+        return rule.localName == element.localName();
+    }, [&element] (AttributeRule rule) {
+        return equalIgnoringASCIICase(element.getAttribute(rule.name), rule.value);
+    });
+}
+
+class ExclusionRuleMatcher {
+public:
+    using ExclusionRule = TextManipulationController::ExclusionRule;
+    using Type = TextManipulationController::ExclusionRule::Type;
+
+    ExclusionRuleMatcher(const Vector<ExclusionRule>& rules)
+        : m_rules(rules)
+    { }
+
+    bool isExcluded(Node* node)
+    {
+        if (!node)
+            return false;
+
+        RefPtr<Element> startingElement = is<Element>(*node) ? downcast<Element>(node) : node->parentElement();
+        if (!startingElement)
+            return false;
+
+        Type type = Type::Include;
+        RefPtr<Element> matchingElement;
+        for (auto& element : elementLineage(startingElement.get())) {
+            if (auto typeOrNullopt = typeForElement(element)) {
+                type = *typeOrNullopt;
+                matchingElement = &element;
+                break;
+            }
+        }
+
+        for (auto& element : elementLineage(startingElement.get())) {
+            m_cache.set(element, type);
+            if (&element == matchingElement)
+                break;
+        }
+
+        return type == Type::Exclude;
+    }
+
+    Optional<Type> typeForElement(Element& element)
+    {
+        auto it = m_cache.find(element);
+        if (it != m_cache.end())
+            return it->value;
+
+        for (auto& rule : m_rules) {
+            if (rule.match(element))
+                return rule.type;
+        }
+
+        return WTF::nullopt;
+    }
+
+private:
+    const Vector<ExclusionRule>& m_rules;
+    HashMap<Ref<Element>, ExclusionRule::Type> m_cache;
+};
+
 TextManipulationController::TextManipulationController(Document& document)
     : m_document(makeWeakPtr(document))
 {
 }
 
-void TextManipulationController::startObservingParagraphs(ManipulationItemCallback&& callback)
+void TextManipulationController::startObservingParagraphs(ManipulationItemCallback&& callback, Vector<ExclusionRule>&& exclusionRules)
 {
     auto document = makeRefPtr(m_document.get());
     if (!document)
         return;
 
     m_callback = WTFMove(callback);
+    m_exclusionRules = WTFMove(exclusionRules);
 
     VisiblePosition start = firstPositionInNode(m_document.get());
     VisiblePosition end = lastPositionInNode(m_document.get());
@@ -52,6 +120,7 @@ void TextManipulationController::startObservingParagraphs(ManipulationItemCallba
     if (!document)
         return; // VisiblePosition or TextIterator's constructor may have updated the layout and executed arbitrary scripts.
 
+    ExclusionRuleMatcher exclusionRuleMatcher(m_exclusionRules);
     Vector<ManipulationToken> tokensInCurrentParagraph;
     Position startOfCurrentParagraph = start.deepEquivalent();
     while (!iterator.atEnd()) {
@@ -64,8 +133,8 @@ void TextManipulationController::startObservingParagraphs(ManipulationItemCallba
         size_t offsetOfNextNewLine = 0;
         while ((offsetOfNextNewLine = currentText.find('\n', endOfLastNewLine)) != notFound) {
             if (endOfLastNewLine < offsetOfNextNewLine) {
-                tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(),
-                    currentText.substring(endOfLastNewLine, offsetOfNextNewLine - endOfLastNewLine).toString() });
+                auto stringUntilEndOfLine = currentText.substring(endOfLastNewLine, offsetOfNextNewLine - endOfLastNewLine).toString();
+                tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), stringUntilEndOfLine, exclusionRuleMatcher.isExcluded(iterator.node()) });
             }
 
             auto lastRange = iterator.range();
@@ -83,7 +152,7 @@ void TextManipulationController::startObservingParagraphs(ManipulationItemCallba
 
         auto remainingText = currentText.substring(endOfLastNewLine);
         if (remainingText.length())
-            tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), remainingText.toString() });
+            tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), remainingText.toString(), exclusionRuleMatcher.isExcluded(iterator.node()) });
 
         iterator.advance();
     }
@@ -99,14 +168,14 @@ void TextManipulationController::addItem(const Position& startOfParagraph, const
     m_callback(*m_document, result.iterator->key, result.iterator->value.tokens);
 }
 
-bool TextManipulationController::completeManipulation(ItemIdentifier itemIdentifier, const Vector<ManipulationToken>& replacementTokens)
+auto TextManipulationController::completeManipulation(ItemIdentifier itemIdentifier, const Vector<ManipulationToken>& replacementTokens) -> ManipulationResult
 {
     if (!itemIdentifier)
-        return false;
+        return ManipulationResult::InvalidItem;
 
     auto itemIterator = m_items.find(itemIdentifier);
     if (itemIterator == m_items.end())
-        return false;
+        return ManipulationResult::InvalidItem;
 
     auto didReplace = replace(itemIterator->value, replacementTokens);
 
@@ -115,35 +184,48 @@ bool TextManipulationController::completeManipulation(ItemIdentifier itemIdentif
     return didReplace;
 }
 
-bool TextManipulationController::replace(const ManipulationItem& item, const Vector<ManipulationToken>& replacementTokens)
+struct DOMChange {
+    Ref<CharacterData> node;
+    String newData;
+};
+
+auto TextManipulationController::replace(const ManipulationItem& item, const Vector<ManipulationToken>& replacementTokens) -> ManipulationResult
 {
     TextIterator iterator { item.start, item.end };
     size_t currentTokenIndex = 0;
-    HashMap<TokenIdentifier, Ref<Node>> tokenToNode;
+    HashMap<TokenIdentifier, std::pair<RefPtr<Node>, const ManipulationToken*>> tokenToNodeTokenPair;
+
     while (!iterator.atEnd()) {
         auto string = iterator.text().toString();
         if (currentTokenIndex >= item.tokens.size())
-            return false;
+            return ManipulationResult::ContentChanged;
         auto& currentToken = item.tokens[currentTokenIndex];
         if (iterator.text() != currentToken.content)
-            return false;
-        tokenToNode.add(currentToken.identifier, *iterator.node());
+            return ManipulationResult::ContentChanged;
+        tokenToNodeTokenPair.set(currentToken.identifier, std::pair<RefPtr<Node>, const ManipulationToken*> { iterator.node(), &currentToken });
         iterator.advance();
         ++currentTokenIndex;
     }
 
     // FIXME: This doesn't preseve the order of the replacement at all.
-    for (auto& token : replacementTokens) {
-        auto* node = tokenToNode.get(token.identifier);
-        if (!node)
-            return false;
-        if (!is<CharacterData>(node))
+    Vector<DOMChange> changes;
+    for (auto& newToken : replacementTokens) {
+        auto it = tokenToNodeTokenPair.find(newToken.identifier);
+        if (it == tokenToNodeTokenPair.end())
+            return ManipulationResult::InvalidToken;
+        auto& oldToken = *it->value.second;
+        if (oldToken.isExcluded)
+            return ManipulationResult::ExclusionViolation;
+        auto* node = it->value.first.get();
+        if (!node || !is<CharacterData>(*node))
             continue;
-        // FIXME: It's not safe to update DOM while iterating over the tokens.
-        downcast<CharacterData>(node)->setData(token.content);
+        changes.append({ downcast<CharacterData>(*node), newToken.content });
     }
 
-    return true;
+    for (auto& change : changes)
+        change.node->setData(change.newData);
+
+    return ManipulationResult::Success;
 }
 
 } // namespace WebCore
index 4657974..43fa40e 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "Position.h"
 #include <wtf/CompletionHandler.h>
+#include <wtf/EnumTraits.h>
 #include <wtf/ObjectIdentifier.h>
 #include <wtf/Optional.h>
 #include <wtf/WeakPtr.h>
@@ -46,6 +47,7 @@ public:
     struct ManipulationToken {
         TokenIdentifier identifier;
         String content;
+        bool isExcluded { false };
 
         template<class Encoder> void encode(Encoder&) const;
         template<class Decoder> static Optional<ManipulationToken> decode(Decoder&);
@@ -54,10 +56,44 @@ public:
     enum ItemIdentifierType { };
     using ItemIdentifier = ObjectIdentifier<ItemIdentifierType>;
 
-    using ManipulationItemCallback = WTF::Function<void(Document&, ItemIdentifier, const Vector<ManipulationToken>&)>;
-    WEBCORE_EXPORT void startObservingParagraphs(ManipulationItemCallback&&);
+    struct ExclusionRule {
+        enum class Type : uint8_t { Exclude, Include };
+
+        struct ElementRule {
+            AtomString localName;
+
+            template<class Encoder> void encode(Encoder&) const;
+            template<class Decoder> static Optional<ElementRule> decode(Decoder&);
+        };
+
+        struct AttributeRule {
+            AtomString name;
+            String value;
 
-    WEBCORE_EXPORT bool completeManipulation(ItemIdentifier, const Vector<ManipulationToken>&);
+            template<class Encoder> void encode(Encoder&) const;
+            template<class Decoder> static Optional<AttributeRule> decode(Decoder&);
+        };
+
+        Type type;
+        WTF::Variant<ElementRule, AttributeRule> rule;
+
+        bool match(const Element&) const;
+
+        template<class Encoder> void encode(Encoder&) const;
+        template<class Decoder> static Optional<ExclusionRule> decode(Decoder&);
+    };
+
+    using ManipulationItemCallback = WTF::Function<void(Document&, ItemIdentifier, const Vector<ManipulationToken>&)>;
+    WEBCORE_EXPORT void startObservingParagraphs(ManipulationItemCallback&&, Vector<ExclusionRule>&& = { });
+
+    enum class ManipulationResult : uint8_t {
+        Success,
+        ContentChanged,
+        InvalidItem,
+        InvalidToken,
+        ExclusionViolation,
+    };
+    WEBCORE_EXPORT ManipulationResult completeManipulation(ItemIdentifier, const Vector<ManipulationToken>&);
 
 private:
     struct ManipulationItem {
@@ -67,10 +103,11 @@ private:
     };
 
     void addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&&);
-    bool replace(const ManipulationItem&, const Vector<ManipulationToken>&);
+    ManipulationResult replace(const ManipulationItem&, const Vector<ManipulationToken>&);
 
     WeakPtr<Document> m_document;
     ManipulationItemCallback m_callback;
+    Vector<ExclusionRule> m_exclusionRules;
     HashMap<ItemIdentifier, ManipulationItem> m_items;
     ItemIdentifier m_itemIdentifier;
     TokenIdentifier m_tokenIdentifier;
@@ -79,7 +116,7 @@ private:
 template<class Encoder>
 void TextManipulationController::ManipulationToken::encode(Encoder& encoder) const
 {
-    encoder << identifier << content;
+    encoder << identifier << content << isExcluded;
 }
 
 template<class Decoder>
@@ -90,7 +127,83 @@ Optional<TextManipulationController::ManipulationToken> TextManipulationControll
         return WTF::nullopt;
     if (!decoder.decode(result.content))
         return WTF::nullopt;
+    if (!decoder.decode(result.isExcluded))
+        return WTF::nullopt;
+    return result;
+}
+
+template<class Encoder>
+void TextManipulationController::ExclusionRule::encode(Encoder& encoder) const
+{
+    encoder << type << rule;
+}
+
+template<class Decoder>
+Optional<TextManipulationController::ExclusionRule> TextManipulationController::ExclusionRule::decode(Decoder& decoder)
+{
+    ExclusionRule result;
+    if (!decoder.decode(result.type))
+        return WTF::nullopt;
+    if (!decoder.decode(result.rule))
+        return WTF::nullopt;
+    return result;
+}
+
+template<class Encoder>
+void TextManipulationController::ExclusionRule::ElementRule::encode(Encoder& encoder) const
+{
+    encoder << localName;
+}
+
+template<class Decoder>
+Optional<TextManipulationController::ExclusionRule::ElementRule> TextManipulationController::ExclusionRule::ElementRule::decode(Decoder& decoder)
+{
+    ElementRule result;
+    if (!decoder.decode(result.localName))
+        return WTF::nullopt;
+    return result;
+}
+
+template<class Encoder>
+void TextManipulationController::ExclusionRule::AttributeRule::encode(Encoder& encoder) const
+{
+    encoder << name << value;
+}
+
+template<class Decoder>
+Optional<TextManipulationController::ExclusionRule::AttributeRule> TextManipulationController::ExclusionRule::AttributeRule::decode(Decoder& decoder)
+{
+    AttributeRule result;
+    if (!decoder.decode(result.name))
+        return WTF::nullopt;
+    if (!decoder.decode(result.value))
+        return WTF::nullopt;
     return result;
 }
 
 } // namespace WebCore
+
+namespace WTF {
+
+template<> struct EnumTraits<WebCore::TextManipulationController::ExclusionRule::Type> {
+    using ExclusionRule = WebCore::TextManipulationController::ExclusionRule;
+    using values = EnumValues<
+        ExclusionRule::Type,
+        ExclusionRule::Type::Include,
+        ExclusionRule::Type::Exclude
+    >;
+};
+
+template<> struct EnumTraits<WebCore::TextManipulationController::ManipulationResult> {
+    using ManipulationResult = WebCore::TextManipulationController::ManipulationResult;
+    using values = EnumValues<
+        ManipulationResult,
+        ManipulationResult::Success,
+        ManipulationResult::ContentChanged,
+        ManipulationResult::InvalidItem,
+        ManipulationResult::InvalidToken,
+        ManipulationResult::ExclusionViolation
+    >;
+};
+
+} // namespace WTF
index ec0bbd5..b5ceb14 100644 (file)
@@ -1,3 +1,45 @@
+2019-10-24  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add exclusion rules to text manipulation SPI
+        https://bugs.webkit.org/show_bug.cgi?id=203398
+        <rdar://problem/56567256>
+
+        Reviewed by Wenson Hsieh.
+
+        Added SPI to specify the configuration for the text manipulation (see r251574), in particular, the set of rules
+        governing which content should be excluded or included in text manipulations.
+
+        Test: TextManipulation.StartTextManipulationExitEarlyWithoutDelegate
+
+        * SourcesCocoa.txt:
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _startTextManipulationsWithConfiguration:completion:]): Takes _WKTextManipulationConfiguration
+        as an argument. Also fixed a bug that we weren't calling the completion handler when the delegate was not set.
+        (-[WKWebView _completeTextManipulation:completion:]):
+        (-[WKWebView _startTextManipulationsWithCompletionHandler:]): Deleted.
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/API/Cocoa/_WKTextManipulationConfiguration.h: Added.
+        * UIProcess/API/Cocoa/_WKTextManipulationConfiguration.mm: Added.
+        * UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.h: Added.
+        * UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.mm: Added.
+        (-[_WKTextManipulationExclusionRule initExclusion:forElement:]): Added.
+        (-[_WKTextManipulationExclusionRule initExclusion:forAttribute:value:]): Added.
+        (-[_WKTextManipulationExclusionRule elementName]): Added.
+        (-[_WKTextManipulationExclusionRule attributeName]): Added.
+        (-[_WKTextManipulationExclusionRule attributeValue]): Added.
+        * UIProcess/API/Cocoa/_WKTextManipulationToken.h: Added excluded boolean property.
+        * UIProcess/API/Cocoa/_WKTextManipulationToken.mm: Removed the superflous import of RetainPtr.h
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::startTextManipulations):
+        (WebKit::WebPageProxy::completeTextManipulation):
+        * UIProcess/WebPageProxy.h:
+        * WebKit.xcodeproj/project.pbxproj:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::startTextManipulations):
+        (WebKit::WebPage::completeTextManipulation):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
 2019-10-25  Chris Dumez  <cdumez@apple.com>
 
         [iOS][WK2] Use sendWithAsyncReply() to simplify the prepareToSuspend logic
index 2d46ccc..2c95016 100644 (file)
@@ -260,6 +260,8 @@ UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm
 UIProcess/API/Cocoa/_WKRemoteWebInspectorViewController.mm
 UIProcess/API/Cocoa/_WKSessionState.mm
 UIProcess/API/Cocoa/_WKTextInputContext.mm
+UIProcess/API/Cocoa/_WKTextManipulationConfiguration.mm
+UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.mm
 UIProcess/API/Cocoa/_WKTextManipulationItem.mm
 UIProcess/API/Cocoa/_WKTextManipulationToken.mm
 UIProcess/API/Cocoa/_WKThumbnailView.mm
index a3f8546..b3b9960 100644 (file)
@@ -99,7 +99,9 @@
 #import "_WKRemoteObjectRegistryInternal.h"
 #import "_WKSessionStateInternal.h"
 #import "_WKTextInputContextInternal.h"
+#import "_WKTextManipulationConfiguration.h"
 #import "_WKTextManipulationDelegate.h"
+#import "_WKTextManipulationExclusionRule.h"
 #import "_WKTextManipulationItem.h"
 #import "_WKTextManipulationToken.h"
 #import "_WKVisitedLinkStoreInternal.h"
@@ -7477,13 +7479,27 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
     _textManipulationDelegate = delegate;
 }
 
-- (void)_startTextManipulationsWithCompletionHandler:(void(^)())completionHandler
+- (void)_startTextManipulationsWithConfiguration:(_WKTextManipulationConfiguration *)configuration completion:(void(^)())completionHandler
 {
-    if (!_textManipulationDelegate)
-        return;
-    if (!_page)
+    using ExclusionRule = WebCore::TextManipulationController::ExclusionRule;
+
+    if (!_textManipulationDelegate || !_page) {
+        completionHandler();
         return;
-    _page->startTextManipulations([weakSelf = WeakObjCPtr<WKWebView>(self)] (WebCore::TextManipulationController::ItemIdentifier itemID,
+    }
+
+    Vector<WebCore::TextManipulationController::ExclusionRule> exclusionRules;
+    if (configuration) {
+        for (_WKTextManipulationExclusionRule *wkRule in configuration.exclusionRules) {
+            auto type = wkRule.isExclusion ? ExclusionRule::Type::Exclude : ExclusionRule::Type::Include;
+            if (wkRule.attributeName)
+                exclusionRules.append({type, ExclusionRule::AttributeRule { wkRule.attributeName, wkRule.attributeValue } });
+            else
+                exclusionRules.append({type, ExclusionRule::ElementRule { wkRule.elementName } });
+        }
+    }
+
+    _page->startTextManipulations(exclusionRules, [weakSelf = WeakObjCPtr<WKWebView>(self)] (WebCore::TextManipulationController::ItemIdentifier itemID,
         const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens) {
         if (!weakSelf)
             return;
@@ -7498,6 +7514,7 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
             auto wkToken = adoptNS([[_WKTextManipulationToken alloc] init]);
             [wkToken setIdentifier:String::number(token.identifier.toUInt64())];
             [wkToken setContent:token.content];
+            [wkToken setExcluded:token.isExcluded];
             [wkTokens addObject:wkToken.get()];
         }
 
@@ -7510,6 +7527,8 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
 
 - (void)_completeTextManipulation:(_WKTextManipulationItem *)item completion:(void(^)(BOOL success))completionHandler
 {
+    using ManipulationResult = WebCore::TextManipulationController::ManipulationResult;
+
     if (!_page)
         return;
 
@@ -7521,8 +7540,8 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
         tokens.append(WebCore::TextManipulationController::ManipulationToken { tokenID, wkToken.content });
     }
 
-    _page->completeTextManipulation(itemID, tokens, [capturedCompletionBlock = makeBlockPtr(completionHandler)] (bool success) {
-        capturedCompletionBlock(success);
+    _page->completeTextManipulation(itemID, tokens, [capturedCompletionBlock = makeBlockPtr(completionHandler)] (ManipulationResult result) {
+        capturedCompletionBlock(result == ManipulationResult::Success);
     });
 }
 
index 5778ab8..f19606a 100644 (file)
@@ -113,6 +113,7 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 @class _WKSafeBrowsingWarning;
 @class _WKSessionState;
 @class _WKTextInputContext;
+@class _WKTextManipulationConfiguration;
 @class _WKTextManipulationItem;
 @class _WKThumbnailView;
 @class _WKWebsitePolicies;
@@ -578,7 +579,7 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 @property (nonatomic, readonly) _WKFrameHandle *_mainFrame WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
 
 @property (nonatomic, weak, setter=_setTextManipulationDelegate:) id <_WKTextManipulationDelegate> _textManipulationDelegate WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
-- (void)_startTextManipulationsWithCompletionHandler:(void(^)(void))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_startTextManipulationsWithConfiguration:(_WKTextManipulationConfiguration *)snapshotConfiguration completion:(void(^)(void))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 - (void)_completeTextManipulation:(_WKTextManipulationItem *)item completion:(void(^)(BOOL success))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @property (nonatomic, setter=_setScrollingUpdatesDisabledForTesting:) BOOL _scrollingUpdatesDisabledForTesting WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationConfiguration.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationConfiguration.h
new file mode 100644 (file)
index 0000000..a61848a
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#import <Foundation/Foundation.h>
+
+@class _WKTextManipulationExclusionRule;
+
+WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
+@interface _WKTextManipulationConfiguration : NSObject
+
+@property (nonatomic, copy) NSArray<_WKTextManipulationExclusionRule *> *exclusionRules;
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationConfiguration.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationConfiguration.mm
new file mode 100644 (file)
index 0000000..1ad1ef7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "_WKTextManipulationConfiguration.h"
+
+@implementation _WKTextManipulationConfiguration
+@end
+
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.h
new file mode 100644 (file)
index 0000000..f08d3e4
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#import <Foundation/Foundation.h>
+
+WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
+@interface _WKTextManipulationExclusionRule : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initExclusion:(BOOL)exclusion forElement:(NSString *)localName;
+- (instancetype)initExclusion:(BOOL)exclusion forAttribute:(NSString *)name value:(NSString *)value;
+
+@property (nonatomic, readonly) BOOL isExclusion;
+@property (nonatomic, readonly) NSString *elementName;
+@property (nonatomic, readonly) NSString *attributeName;
+@property (nonatomic, readonly) NSString *attributeValue;
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationExclusionRule.mm
new file mode 100644 (file)
index 0000000..795e727
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "_WKTextManipulationExclusionRule.h"
+
+#include <wtf/RetainPtr.h>
+
+@implementation _WKTextManipulationExclusionRule {
+    BOOL _isExclusion;
+    RetainPtr<NSString> _elementName;
+    RetainPtr<NSString> _attributeName;
+    RetainPtr<NSString> _attributeValue;
+}
+
+- (instancetype)initExclusion:(BOOL)exclusion forElement:(NSString *)localName
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _isExclusion = exclusion;
+    _elementName = localName;
+    
+    return self;
+}
+
+- (instancetype)initExclusion:(BOOL)exclusion forAttribute:(NSString *)name value:(NSString *)value
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _isExclusion = exclusion;
+    _attributeName = name;
+    _attributeValue = value;
+
+    return self;
+}
+
+- (NSString *)elementName
+{
+    return _elementName.get();
+}
+
+- (NSString *)attributeName
+{
+    return _attributeName.get();
+}
+
+- (NSString *)attributeValue
+{
+    return _attributeValue.get();
+}
+
+@end
+
index 0b85eae..52129d4 100644 (file)
@@ -32,5 +32,6 @@ WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
 
 @property (nonatomic, copy) NSString *identifier;
 @property (nonatomic, copy) NSString *content;
+@property (nonatomic, getter=isExcluded) BOOL excluded;
 
 @end
index a50a8ea..3fcc4e9 100644 (file)
@@ -26,7 +26,5 @@
 #import "config.h"
 #import "_WKTextManipulationToken.h"
 
-#import <wtf/RetainPtr.h>
-
 @implementation _WKTextManipulationToken
 @end
index 0f8bf43..8ab393c 100644 (file)
@@ -28,7 +28,9 @@
 
 #if HAVE(APP_SSO)
 
+#import "APINavigationAction.h"
 #import "DataReference.h"
+#import "WebPageProxy.h"
 #import <WebCore/ResourceResponse.h>
 
 namespace WebKit {
index f2b073f..9a07f1a 100644 (file)
@@ -29,6 +29,7 @@
 #if HAVE(APP_SSO)
 
 #import "APIFrameHandle.h"
+#import "APINavigationAction.h"
 #import "PopUpSOAuthorizationSession.h"
 #import "RedirectSOAuthorizationSession.h"
 #import "SubFrameSOAuthorizationSession.h"
index 8a06d7f..7df55dd 100644 (file)
@@ -9419,14 +9419,15 @@ void WebPageProxy::setMockWebAuthenticationConfiguration(MockWebAuthenticationCo
 }
 #endif
 
-void WebPageProxy::startTextManipulations(TextManipulationItemCallback&& callback, WTF::CompletionHandler<void()>&& completionHandler)
+void WebPageProxy::startTextManipulations(const Vector<WebCore::TextManipulationController::ExclusionRule>& exclusionRules,
+    TextManipulationItemCallback&& callback, WTF::CompletionHandler<void()>&& completionHandler)
 {
     if (!hasRunningProcess()) {
         completionHandler();
         return;
     }
     m_textManipulationItemCallback = WTFMove(callback);
-    m_process->connection()->sendWithAsyncReply(Messages::WebPage::StartTextManipulations(), WTFMove(completionHandler), m_webPageID);
+    m_process->connection()->sendWithAsyncReply(Messages::WebPage::StartTextManipulations(exclusionRules), WTFMove(completionHandler), m_webPageID);
 }
 
 void WebPageProxy::didFindTextManipulationItem(WebCore::TextManipulationController::ItemIdentifier itemID,
@@ -9438,10 +9439,10 @@ void WebPageProxy::didFindTextManipulationItem(WebCore::TextManipulationControll
 }
 
 void WebPageProxy::completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier itemID,
-    const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens, WTF::Function<void (bool success)>&& completionHandler)
+    const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens, WTF::Function<void (WebCore::TextManipulationController::ManipulationResult result)>&& completionHandler)
 {
     if (!hasRunningProcess()) {
-        completionHandler(false);
+        completionHandler(WebCore::TextManipulationController::ManipulationResult::InvalidItem);
         return;
     }
     m_process->connection()->sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(itemID, tokens), WTFMove(completionHandler), m_webPageID);
index d47b7f4..5a99d13 100644 (file)
@@ -1614,10 +1614,10 @@ public:
 #endif
 
     using TextManipulationItemCallback = WTF::Function<void (WebCore::TextManipulationController::ItemIdentifier, const Vector<WebCore::TextManipulationController::ManipulationToken>&)>;
-    void startTextManipulations(TextManipulationItemCallback&&, WTF::CompletionHandler<void()>&&);
+    void startTextManipulations(const Vector<WebCore::TextManipulationController::ExclusionRule>&, TextManipulationItemCallback&&, WTF::CompletionHandler<void()>&&);
     void didFindTextManipulationItem(WebCore::TextManipulationController::ItemIdentifier, const Vector<WebCore::TextManipulationController::ManipulationToken>&);
-    void completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier,
-        const Vector<WebCore::TextManipulationController::ManipulationToken>&, WTF::Function<void (bool success)>&&);
+    void completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier, const Vector<WebCore::TextManipulationController::ManipulationToken>&,
+        WTF::Function<void (WebCore::TextManipulationController::ManipulationResult)>&&);
 
 private:
     WebPageProxy(PageClient&, WebProcessProxy&, Ref<API::PageConfiguration>&&);
index f77a7d4..1322d98 100644 (file)
                9B02E0CB235EB953004044B2 /* _WKTextManipulationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B02E0C9235EB62D004044B2 /* _WKTextManipulationDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9B02E0CC235EB957004044B2 /* _WKTextManipulationItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B02E0CA235EB884004044B2 /* _WKTextManipulationItem.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9B02E0D7235FC94F004044B2 /* _WKTextManipulationToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B02E0CD235EB967004044B2 /* _WKTextManipulationToken.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B5499AD2362A6F900DF8BA5 /* _WKTextManipulationConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B5499AC2362A6F600DF8BA5 /* _WKTextManipulationConfiguration.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B5499B22362A7EC00DF8BA5 /* _WKTextManipulationExclusionRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B5499B02362A7EC00DF8BA5 /* _WKTextManipulationExclusionRule.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9FB5F395169E6A80002C25BF /* WKContextPrivateMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 9FB5F393169E6A80002C25BF /* WKContextPrivateMac.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A102A7081EC0EEE900D81D82 /* com.macromedia.Flash Player ESR.plugin.sb in Copy Plug-in Sandbox Profiles */ = {isa = PBXBuildFile; fileRef = 7A5E39491D5BD8A700B4B7CE /* com.macromedia.Flash Player ESR.plugin.sb */; };
                A1046EA12079263100F0C5D8 /* WKPDFView.h in Headers */ = {isa = PBXBuildFile; fileRef = A1046E9F2079263100F0C5D8 /* WKPDFView.h */; };
                9B02E0CD235EB967004044B2 /* _WKTextManipulationToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKTextManipulationToken.h; sourceTree = "<group>"; };
                9B02E0CE235EBB14004044B2 /* _WKTextManipulationToken.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationToken.mm; sourceTree = "<group>"; };
                9B02E0D0235EBCCA004044B2 /* _WKTextManipulationItem.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationItem.mm; sourceTree = "<group>"; };
+               9B5499AC2362A6F600DF8BA5 /* _WKTextManipulationConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKTextManipulationConfiguration.h; sourceTree = "<group>"; };
+               9B5499AE2362A7A700DF8BA5 /* _WKTextManipulationConfiguration.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationConfiguration.mm; sourceTree = "<group>"; };
+               9B5499B02362A7EC00DF8BA5 /* _WKTextManipulationExclusionRule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKTextManipulationExclusionRule.h; sourceTree = "<group>"; };
+               9B5499B12362A7EC00DF8BA5 /* _WKTextManipulationExclusionRule.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationExclusionRule.mm; sourceTree = "<group>"; };
                9BC59D6C1EFCCCB6001E8D09 /* CallbackID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallbackID.h; sourceTree = "<group>"; };
                9BC59D6D1EFCDC6D001E8D09 /* OptionalCallbackID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OptionalCallbackID.h; sourceTree = "<group>"; };
                9F54F88E16488E87007DF81A /* AuxiliaryProcessMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AuxiliaryProcessMac.mm; sourceTree = "<group>"; };
                                2DE9B1382231F61C005287B7 /* _WKTextInputContext.h */,
                                2DE9B1372231F61C005287B7 /* _WKTextInputContext.mm */,
                                2DE9B13B2231F77C005287B7 /* _WKTextInputContextInternal.h */,
+                               9B5499AC2362A6F600DF8BA5 /* _WKTextManipulationConfiguration.h */,
+                               9B5499AE2362A7A700DF8BA5 /* _WKTextManipulationConfiguration.mm */,
                                9B02E0C9235EB62D004044B2 /* _WKTextManipulationDelegate.h */,
+                               9B5499B02362A7EC00DF8BA5 /* _WKTextManipulationExclusionRule.h */,
+                               9B5499B12362A7EC00DF8BA5 /* _WKTextManipulationExclusionRule.mm */,
                                9B02E0CA235EB884004044B2 /* _WKTextManipulationItem.h */,
                                9B02E0D0235EBCCA004044B2 /* _WKTextManipulationItem.mm */,
                                9B02E0CD235EB967004044B2 /* _WKTextManipulationToken.h */,
                                1A002D43196B337000B9AD44 /* _WKSessionStateInternal.h in Headers */,
                                2DE9B13A2231F61C005287B7 /* _WKTextInputContext.h in Headers */,
                                2DE9B13C2231F77C005287B7 /* _WKTextInputContextInternal.h in Headers */,
+                               9B5499AD2362A6F900DF8BA5 /* _WKTextManipulationConfiguration.h in Headers */,
                                9B02E0CB235EB953004044B2 /* _WKTextManipulationDelegate.h in Headers */,
+                               9B5499B22362A7EC00DF8BA5 /* _WKTextManipulationExclusionRule.h in Headers */,
                                9B02E0CC235EB957004044B2 /* _WKTextManipulationItem.h in Headers */,
                                9B02E0D7235FC94F004044B2 /* _WKTextManipulationToken.h in Headers */,
                                2D6B371B18A967AD0042AE80 /* _WKThumbnailView.h in Headers */,
index cb9d68e..a15e1d4 100644 (file)
@@ -6836,7 +6836,7 @@ Optional<WebCore::ElementContext> WebPage::contextForElement(WebCore::Element& e
     return WebCore::ElementContext { elementRectInRootViewCoordinates(element, *frame), m_identifier, document.identifier(), document.identifierForElement(element) };
 }
 
-void WebPage::startTextManipulations(CompletionHandler<void()>&& completionHandler)
+void WebPage::startTextManipulations(Vector<WebCore::TextManipulationController::ExclusionRule>&& exclusionRules, CompletionHandler<void()>&& completionHandler)
 {
     if (!m_page)
         return;
@@ -6856,25 +6856,26 @@ void WebPage::startTextManipulations(CompletionHandler<void()>&& completionHandl
             return;
 
         webPage->send(Messages::WebPageProxy::DidFindTextManipulationItem(itemIdentifier, tokens));
-    });
+    }, WTFMove(exclusionRules));
     // For now, we assume startObservingParagraphs find all paragraphs synchronously at once.
     completionHandler();
 }
 
 void WebPage::completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier itemID,
-    const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens, CompletionHandler<void(bool)>&& completionHandler)
+    const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens, CompletionHandler<void(WebCore::TextManipulationController::ManipulationResult)>&& completionHandler)
 {
+    using ManipulationResult = WebCore::TextManipulationController::ManipulationResult;
     auto completeManipulation = [&] {
         if (!m_page)
-            return false;
+            return ManipulationResult::InvalidItem;
 
         auto mainDocument = makeRefPtr(m_page->mainFrame().document());
         if (!mainDocument)
-            return false;
+            return ManipulationResult::InvalidItem;
 
         auto* controller = mainDocument->textManipulationControllerIfExists();
         if (!controller)
-            return false;
+            return ManipulationResult::InvalidItem;
 
         return controller->completeManipulation(itemID, tokens);
     };
index 43ec3b3..72bb562 100644 (file)
@@ -1197,9 +1197,10 @@ public:
     WebCore::Element* elementForContext(const WebCore::ElementContext&) const;
     Optional<WebCore::ElementContext> contextForElement(WebCore::Element&) const;
 
-    void startTextManipulations(CompletionHandler<void()>&&);
+    void startTextManipulations(Vector<WebCore::TextManipulationController::ExclusionRule>&&, CompletionHandler<void()>&&);
     void completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier,
-        const Vector<WebCore::TextManipulationController::ManipulationToken>&, CompletionHandler<void(bool)>&&);
+        const Vector<WebCore::TextManipulationController::ManipulationToken>&,
+        CompletionHandler<void(WebCore::TextManipulationController::ManipulationResult)>&&);
 
 #if ENABLE(APPLE_PAY)
     WebPaymentCoordinator* paymentCoordinator();
index 1f985a0..198028a 100644 (file)
@@ -583,6 +583,6 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType
     SendMessageToWebExtensionWithReply(struct WebKit::UserMessage userMessage) -> (struct WebKit::UserMessage replyMessage) Async
 #endif
 
-    StartTextManipulations() -> () Async
-    CompleteTextManipulation(WebCore::TextManipulationController::ItemIdentifier itemID, Vector<WebCore::TextManipulationController::ManipulationToken> tokens) -> (bool success) Async
+    StartTextManipulations(Vector<WebCore::TextManipulationController::ExclusionRule> exclusionRules) -> () Async
+    CompleteTextManipulation(WebCore::TextManipulationController::ItemIdentifier itemID, Vector<WebCore::TextManipulationController::ManipulationToken> tokens) -> (enum:uint8_t WebCore::TextManipulationController::ManipulationResult result) Async
 }
index 3ab7d16..f21fcb6 100644 (file)
@@ -1,3 +1,18 @@
+2019-10-24  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add exclusion rules to text manipulation SPI
+        https://bugs.webkit.org/show_bug.cgi?id=203398
+        <rdar://problem/56567256>
+
+        Reviewed by Wenson Hsieh.
+
+        Added tests for including & excluding content based on element names and attribute values.
+
+        Also added a test to make sure _startTextManipulationsWithConfiguration calls the completion handler
+        even when the _WKTextManipulationDelegate isn't set.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm:
+
 2019-10-25  Jonathan Bedard  <jbedard@apple.com>
 
         results.webkit.org: Report the correct flavor for leaks
index da23efc..c62ce06 100644 (file)
@@ -29,7 +29,9 @@
 #import "Test.h"
 #import "TestWKWebView.h"
 #import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/_WKTextManipulationConfiguration.h>
 #import <WebKit/_WKTextManipulationDelegate.h>
+#import <WebKit/_WKTextManipulationExclusionRule.h>
 #import <WebKit/_WKTextManipulationItem.h>
 #import <WebKit/_WKTextManipulationToken.h>
 #import <wtf/BlockPtr.h>
@@ -71,6 +73,23 @@ static bool done = false;
 
 namespace TestWebKitAPI {
 
+TEST(TextManipulation, StartTextManipulationExitEarlyWithoutDelegate)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
+        "<html><body>hello<br>world<div>WebKit</div></body></html>"];
+
+    done = false;
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_EQ([delegate items].count, 0UL);
+}
+
 TEST(TextManipulation, StartTextManipulationFindSimpleParagraphs)
 {
     auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
@@ -81,7 +100,7 @@ TEST(TextManipulation, StartTextManipulationFindSimpleParagraphs)
         "<html><body>hello<br>world<div>WebKit</div></body></html>"];
 
     done = false;
-    [webView _startTextManipulationsWithCompletionHandler:^{
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
         done = true;
     }];
     TestWebKitAPI::Util::run(&done);
@@ -108,7 +127,7 @@ TEST(TextManipulation, StartTextManipulationFindMultipleParagraphsInSingleTextNo
         "<html><body><pre>hello\nworld\nWebKit</pre></body></html>"];
 
     done = false;
-    [webView _startTextManipulationsWithCompletionHandler:^{
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
         done = true;
     }];
     TestWebKitAPI::Util::run(&done);
@@ -135,7 +154,7 @@ TEST(TextManipulation, StartTextManipulationFindParagraphsWithMultileTokens)
         "<html><body>hello,  <b>world</b><br><div><em> <b>Web</b>Kit</em>  </div></body></html>"];
     
     done = false;
-    [webView _startTextManipulationsWithCompletionHandler:^{
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
         done = true;
     }];
     TestWebKitAPI::Util::run(&done);
@@ -151,6 +170,72 @@ TEST(TextManipulation, StartTextManipulationFindParagraphsWithMultileTokens)
     EXPECT_STREQ("Kit", items[1].tokens[1].content.UTF8String);
 }
 
+TEST(TextManipulation, StartTextManipulationApplySingleExcluionRuleForElement)
+{
+    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><body>Here's some code:<code>function <span>F</span>() { }</code>.</body></html>"];
+
+    RetainPtr<_WKTextManipulationConfiguration> configuration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    [configuration setExclusionRules:@[
+        [[[_WKTextManipulationExclusionRule alloc] initExclusion:(BOOL)YES forElement:@"code"] autorelease],
+    ]];
+
+    done = false;
+    [webView _startTextManipulationsWithConfiguration:configuration.get() completion:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 1UL);
+    EXPECT_EQ(items[0].tokens.count, 5UL);
+    auto* tokens = items[0].tokens;
+    EXPECT_STREQ("Here's some code:", tokens[0].content.UTF8String);
+    EXPECT_FALSE(tokens[0].isExcluded);
+    EXPECT_STREQ("function ", tokens[1].content.UTF8String);
+    EXPECT_TRUE(tokens[1].isExcluded);
+    EXPECT_STREQ("F", tokens[2].content.UTF8String);
+    EXPECT_TRUE(tokens[2].isExcluded);
+    EXPECT_STREQ("() { }", tokens[3].content.UTF8String);
+    EXPECT_TRUE(tokens[3].isExcluded);
+    EXPECT_STREQ(".", tokens[4].content.UTF8String);
+    EXPECT_FALSE(tokens[4].isExcluded);
+}
+
+TEST(TextManipulation, StartTextManipulationApplyInclusionExclusionRulesForAttributes)
+{
+    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><body><span data-exclude=Yes><b>hello, </b><span data-exclude=NO>world</span></span></body></html>"];
+
+    RetainPtr<_WKTextManipulationConfiguration> configuration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    [configuration setExclusionRules:@[
+        [[[_WKTextManipulationExclusionRule alloc] initExclusion:(BOOL)YES forAttribute:@"data-exclude" value:@"yes"] autorelease],
+        [[[_WKTextManipulationExclusionRule alloc] initExclusion:(BOOL)NO forAttribute:@"data-exclude" value:@"no"] autorelease],
+    ]];
+
+    done = false;
+    [webView _startTextManipulationsWithConfiguration:configuration.get() completion:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    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);
+}
+
 struct Token {
     NSString *identifier;
     NSString *content;
@@ -169,7 +254,7 @@ static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, c
     return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
 }
 
-TEST(TextManipulation, CompleteTextManipulationShouldReplaceSimpleParagraphContent)
+TEST(TextManipulation, CompleteTextManipulationReplaceSimpleParagraphContent)
 {
     auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
@@ -179,7 +264,7 @@ TEST(TextManipulation, CompleteTextManipulationShouldReplaceSimpleParagraphConte
         "<html><body><p>helllo, wooorld</p><p> hey, <b> Kits</b> is <em>cuuute</em></p></body></html>"];
 
     done = false;
-    [webView _startTextManipulationsWithCompletionHandler:^{
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
         done = true;
     }];
     TestWebKitAPI::Util::run(&done);
@@ -221,7 +306,7 @@ TEST(TextManipulation, CompleteTextManipulationShouldReplaceSimpleParagraphConte
         [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
 }
 
-TEST(TextManipulation, CompleteTextManipulationShouldFailWhenContentIsChanged)
+TEST(TextManipulation, CompleteTextManipulationFailWhenContentIsChanged)
 {
     auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
@@ -231,7 +316,7 @@ TEST(TextManipulation, CompleteTextManipulationShouldFailWhenContentIsChanged)
         "<html><body><p> what <em>time</em> are they now?</p></body></html>"];
 
     done = false;
-    [webView _startTextManipulationsWithCompletionHandler:^{
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
         done = true;
     }];
     TestWebKitAPI::Util::run(&done);
@@ -259,7 +344,7 @@ TEST(TextManipulation, CompleteTextManipulationShouldFailWhenContentIsChanged)
         [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
 }
 
-TEST(TextManipulation, CompleteTextManipulationShouldFailWhenDocumentHasBeenNavigatedAway)
+TEST(TextManipulation, CompleteTextManipulationFailWhenDocumentHasBeenNavigatedAway)
 {
     auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
@@ -269,7 +354,7 @@ TEST(TextManipulation, CompleteTextManipulationShouldFailWhenDocumentHasBeenNavi
     [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hey, <em>earth</em></p>'"];
 
     done = false;
-    [webView _startTextManipulationsWithCompletionHandler:^{
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
         done = true;
     }];
     TestWebKitAPI::Util::run(&done);
@@ -284,7 +369,7 @@ TEST(TextManipulation, CompleteTextManipulationShouldFailWhenDocumentHasBeenNavi
     [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hey, <em>earth</em></p>'"];
 
     done = false;
-    [webView _startTextManipulationsWithCompletionHandler:^{
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
         done = true;
     }];
     TestWebKitAPI::Util::run(&done);
@@ -301,5 +386,43 @@ TEST(TextManipulation, CompleteTextManipulationShouldFailWhenDocumentHasBeenNavi
     TestWebKitAPI::Util::run(&done);
 }
 
+TEST(TextManipulation, CompleteTextManipulationFailWhenExclusionIsViolated)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+
+    [webView synchronouslyLoadTestPageNamed:@"simple"];
+    [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hi, <em>WebKitten</em></p>'"];
+
+    RetainPtr<_WKTextManipulationConfiguration> configuration = adoptNS([[_WKTextManipulationConfiguration alloc] init]);
+    [configuration setExclusionRules:@[
+        [[[_WKTextManipulationExclusionRule alloc] initExclusion:(BOOL)YES forElement:@"p"] autorelease],
+    ]];
+
+    done = false;
+    [webView _startTextManipulationsWithConfiguration:configuration.get() completion:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 1UL);
+    EXPECT_EQ(items[0].tokens.count, 2UL);
+    EXPECT_STREQ("hi, ", items[0].tokens[0].content.UTF8String);
+    EXPECT_STREQ("WebKitten", items[0].tokens[1].content.UTF8String);
+
+    done = false;
+    [webView _completeTextManipulation:(_WKTextManipulationItem *)createItem(items[0].identifier, {
+        { items[0].tokens[1].identifier, @"WebKit" },
+    }).get() completion:^(BOOL success) {
+        EXPECT_FALSE(success);
+        done = true;
+    }];
+
+    TestWebKitAPI::Util::run(&done);
+    EXPECT_WK_STREQ("<p>hi, <em>WebKitten</em></p>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+}
+
 } // namespace TestWebKitAPI