Add an platform-driven spell-checking mechanism
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Mar 2019 07:32:40 +0000 (07:32 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Mar 2019 07:32:40 +0000 (07:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195795

Reviewed by Ryosuke Niwa.

Source/WebCore:

* dom/Document.cpp:
(WebCore::Document::textInserted):
PlatformTextChecking markers are not expected
to propagate to newly inserted text, so remove them.

* dom/DocumentMarker.h:
(WebCore::DocumentMarker::allMarkers):
Add a new type of DocumentMarker, PlatformTextChecking,
and a new data variant that stores a key value pair of strings.

* dom/DocumentMarkerController.cpp:
(WebCore::DocumentMarkerController::addPlatformTextCheckingMarker):
(WebCore::DocumentMarkerController::removeMarkers):
(WebCore::DocumentMarkerController::filterMarkers):
(WebCore::shouldInsertAsSeparateMarker):
* dom/DocumentMarkerController.h:
Export some things.
Add addPlatformTextCheckingMarker, like the others.
Make it possible to filter out markers of a particular type
in a range with a predicate function.

* editing/CompositeEditCommand.cpp:
(WebCore::CompositeEditCommand::replaceTextInNodePreservingMarkers):
Propagate PlatformTextChecking data.
A future change should probably make it possible for
any DocumentMarker to copy its data here, instead of
special-casing each type that is important.

* editing/Editor.cpp:
(WebCore::Editor::markMisspellingsAfterTypingToWord):
(WebCore::Editor::markAllMisspellingsAndBadGrammarInRanges):
(WebCore::Editor::markMisspellingsAndBadGrammar):
* editing/TextCheckingHelper.cpp:
(WebCore::TextCheckingHelper::findFirstMisspellingOrBadGrammar):
(WebCore::TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange const):
(WebCore::platformDrivenTextCheckerEnabled):
* editing/TextCheckingHelper.h:
Bail from traditional spell checking if this mechanism is
enabled. (I wrote it this way to make it runtime switchable
in the near future, and to be similar to unifiedTextCheckerEnabled).

Source/WebKit:

* DerivedSources-input.xcfilelist:
* DerivedSources-output.xcfilelist:
* DerivedSources.make:
* SourcesCocoa.txt:
* UIProcess/Cocoa/TextCheckingController.h: Added.
* UIProcess/Cocoa/TextCheckingController.mm: Added.
(WebKit::TextCheckingController::TextCheckingController):
(WebKit::TextCheckingController::replaceRelativeToSelection):
(WebKit::TextCheckingController::removeAnnotationRelativeToSelection):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView setupInteraction]):
(-[WKContentView replaceSelectionOffset:length:withAnnotatedString:relativeReplacementRange:]):
(-[WKContentView removeAnnotation:forSelectionOffset:length:]):
* WebKit.xcodeproj/project.pbxproj:
* WebProcess/WebPage/WebPage.cpp:
* WebProcess/WebPage/WebPage.h:
(WebKit::WebPage::textCheckingController):
Plumb two UITextInput methods through to the Web Content process.
I added a new object instead of just sticking things on WebPage
because there are quite a few more related ones coming down the pipeline,
and will also end up being messages going in the opposite direction.

* WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.h: Added.
* WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.messages.in: Added.
* WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm: Added.
(WebKit::TextCheckingControllerProxy::TextCheckingControllerProxy):
(WebKit::TextCheckingControllerProxy::~TextCheckingControllerProxy):
(WebKit::relevantMarkerTypes):
(WebKit::TextCheckingControllerProxy::rangeAndOffsetRelativeToSelection):
(WebKit::TextCheckingControllerProxy::replaceRelativeToSelection):
(WebKit::TextCheckingControllerProxy::removeAnnotationRelativeToSelection):
Make it possible for the platform to maintain arbitrary key-value pairs
attached to document ranges, as a way for it to keep track of various
text checking context (e.g. if something has been checked, replaced,
what language it might be, ...).

Allow it to replace the text of a range and the annotations in that range,
or remove annotations in a range. Ranges are specified relative to
the selection.

One large missing piece is giving the platform the ability to retrieve
annotations in a range; that is coming in a future patch.

We translate certain annotations into traditional WebCore spelling
and grammar document markers, for normal display-time treatment.

* WebProcess/WebPage/mac/WKAccessibilityWebPageObjectMac.mm:
(-[WKAccessibilityWebPageObject convertScreenPointToRootView:]):
(-[WKAccessibilityWebPageObject accessibilityHitTest:]):
Unified sources fixes.

Source/WTF:

* wtf/Platform.h:
Add an ENABLE flag.

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

29 files changed:
Source/WTF/ChangeLog
Source/WTF/wtf/Platform.h
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/DocumentMarker.h
Source/WebCore/dom/DocumentMarkerController.cpp
Source/WebCore/dom/DocumentMarkerController.h
Source/WebCore/editing/CompositeEditCommand.cpp
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/editing/TextCheckingHelper.cpp
Source/WebCore/editing/TextCheckingHelper.h
Source/WebKit/ChangeLog
Source/WebKit/DerivedSources-input.xcfilelist
Source/WebKit/DerivedSources-output.xcfilelist
Source/WebKit/DerivedSources.make
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/Cocoa/TextCheckingController.h [new file with mode: 0644]
Source/WebKit/UIProcess/Cocoa/TextCheckingController.mm [new file with mode: 0644]
Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.h [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.messages.in [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm [new file with mode: 0644]
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/mac/WKAccessibilityWebPageObjectMac.mm

index 17a7ad3..154e97f 100644 (file)
@@ -1,3 +1,13 @@
+2019-03-20  Tim Horton  <timothy_horton@apple.com>
+
+        Add an platform-driven spell-checking mechanism
+        https://bugs.webkit.org/show_bug.cgi?id=195795
+
+        Reviewed by Ryosuke Niwa.
+
+        * wtf/Platform.h:
+        Add an ENABLE flag.
+
 2019-03-19  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         Build cleanly with GCC 9
index 25e2a3b..dca79da 100644 (file)
 #if PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 130000
 #define HAVE_UI_WEB_TOUCH_EVENTS_GESTURE_RECOGNIZER_WITH_ACTIVE_TOUCHES_BY_ID 1
 #endif
+
+#if PLATFORM(IOSMAC)
+#define ENABLE_PLATFORM_DRIVEN_TEXT_CHECKING 1
+#endif
index 158e9f0..f852c45 100644 (file)
@@ -1,3 +1,51 @@
+2019-03-20  Tim Horton  <timothy_horton@apple.com>
+
+        Add an platform-driven spell-checking mechanism
+        https://bugs.webkit.org/show_bug.cgi?id=195795
+
+        Reviewed by Ryosuke Niwa.
+
+        * dom/Document.cpp:
+        (WebCore::Document::textInserted):
+        PlatformTextChecking markers are not expected
+        to propagate to newly inserted text, so remove them.
+
+        * dom/DocumentMarker.h:
+        (WebCore::DocumentMarker::allMarkers):
+        Add a new type of DocumentMarker, PlatformTextChecking,
+        and a new data variant that stores a key value pair of strings.
+
+        * dom/DocumentMarkerController.cpp:
+        (WebCore::DocumentMarkerController::addPlatformTextCheckingMarker):
+        (WebCore::DocumentMarkerController::removeMarkers):
+        (WebCore::DocumentMarkerController::filterMarkers):
+        (WebCore::shouldInsertAsSeparateMarker):
+        * dom/DocumentMarkerController.h:
+        Export some things.
+        Add addPlatformTextCheckingMarker, like the others.
+        Make it possible to filter out markers of a particular type
+        in a range with a predicate function.
+
+        * editing/CompositeEditCommand.cpp:
+        (WebCore::CompositeEditCommand::replaceTextInNodePreservingMarkers):
+        Propagate PlatformTextChecking data.
+        A future change should probably make it possible for
+        any DocumentMarker to copy its data here, instead of
+        special-casing each type that is important.
+
+        * editing/Editor.cpp:
+        (WebCore::Editor::markMisspellingsAfterTypingToWord):
+        (WebCore::Editor::markAllMisspellingsAndBadGrammarInRanges):
+        (WebCore::Editor::markMisspellingsAndBadGrammar):
+        * editing/TextCheckingHelper.cpp:
+        (WebCore::TextCheckingHelper::findFirstMisspellingOrBadGrammar):
+        (WebCore::TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange const):
+        (WebCore::platformDrivenTextCheckerEnabled):
+        * editing/TextCheckingHelper.h:
+        Bail from traditional spell checking if this mechanism is
+        enabled. (I wrote it this way to make it runtime switchable
+        in the near future, and to be similar to unifiedTextCheckerEnabled).
+
 2019-03-19  Jiewen Tan  <jiewen_tan@apple.com>
 
         [WebAuthN] Implement FIDO AppID extension
index bee6ba8..1835524 100644 (file)
@@ -4533,6 +4533,11 @@ void Document::textInserted(Node& text, unsigned offset, unsigned length)
 
     // Update the markers for spelling and grammar checking.
     m_markers->shiftMarkers(text, offset, length);
+
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    // Freshly inserted text is expected to not inherit PlatformTextChecking markers.
+    m_markers->removeMarkers(text, offset, length, DocumentMarker::PlatformTextChecking);
+#endif
 }
 
 void Document::textRemoved(Node& text, unsigned offset, unsigned length)
index 49b0a15..655bd36 100644 (file)
@@ -79,7 +79,11 @@ public:
         // This marker indicates that the user has selected a text candidate.
         AcceptedCandidate = 1 << 13,
         // This marker indicates that the user has initiated a drag with this content.
-        DraggedContent = 1 << 14
+        DraggedContent = 1 << 14,
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+        // This marker maintains state for the platform text checker.
+        PlatformTextChecking = 1 << 15,
+#endif
     };
 
     static constexpr OptionSet<MarkerType> allMarkers();
@@ -99,7 +103,17 @@ public:
     struct DraggedContentData {
         RefPtr<Node> targetNode;
     };
-    using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData, DraggedContentData>;
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    struct PlatformTextCheckingData {
+        String key;
+        String value;
+    };
+#endif
+    using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData, DraggedContentData
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    , PlatformTextCheckingData
+#endif
+    >;
 
     DocumentMarker(unsigned startOffset, unsigned endOffset, bool isActiveMatch);
     DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, const String& description = String());
@@ -163,6 +177,9 @@ constexpr auto DocumentMarker::allMarkers() -> OptionSet<MarkerType>
         DictationPhraseWithAlternatives,
         DictationResult,
 #endif
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+        PlatformTextChecking
+#endif
     };
 }
 
index 5bbcb80..7291d4a 100644 (file)
@@ -142,6 +142,17 @@ void DocumentMarkerController::addDraggedContentMarker(Range& range)
     }
 }
 
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+void DocumentMarkerController::addPlatformTextCheckingMarker(Range& range, const String& key, const String& value)
+{
+    for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
+        auto textPiece = markedText.range();
+        DocumentMarker::PlatformTextCheckingData textCheckingData { key, value };
+        addMarker(textPiece->startContainer(), { DocumentMarker::PlatformTextChecking, textPiece->startOffset(), textPiece->endOffset(), WTFMove(textCheckingData) });
+    }
+}
+#endif
+
 void DocumentMarkerController::removeMarkers(Range& range, OptionSet<DocumentMarker::MarkerType> markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
 {
     for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
@@ -152,7 +163,21 @@ void DocumentMarkerController::removeMarkers(Range& range, OptionSet<DocumentMar
         auto textPiece = markedText.range();
         unsigned startOffset = textPiece->startOffset();
         unsigned endOffset = textPiece->endOffset();
-        removeMarkers(textPiece->startContainer(), startOffset, endOffset - startOffset, markerTypes, shouldRemovePartiallyOverlappingMarker);
+        removeMarkers(textPiece->startContainer(), startOffset, endOffset - startOffset, markerTypes, nullptr, shouldRemovePartiallyOverlappingMarker);
+    }
+}
+
+void DocumentMarkerController::filterMarkers(Range& range, std::function<bool(DocumentMarker*)> filterFunction, OptionSet<DocumentMarker::MarkerType> markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
+{
+    for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
+        if (!possiblyHasMarkers(markerTypes))
+            return;
+        ASSERT(!m_markers.isEmpty());
+
+        auto textPiece = markedText.range();
+        unsigned startOffset = textPiece->startOffset();
+        unsigned endOffset = textPiece->endOffset();
+        removeMarkers(textPiece->startContainer(), startOffset, endOffset - startOffset, markerTypes, filterFunction, shouldRemovePartiallyOverlappingMarker);
     }
 }
 
@@ -300,6 +325,11 @@ Vector<FloatRect> DocumentMarkerController::renderedRectsForMarkers(DocumentMark
 
 static bool shouldInsertAsSeparateMarker(const DocumentMarker& newMarker)
 {
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    if (newMarker.type() == DocumentMarker::PlatformTextChecking)
+        return true;
+#endif
+
 #if PLATFORM(IOS_FAMILY)
     if (newMarker.type() == DocumentMarker::DictationPhraseWithAlternatives || newMarker.type() == DocumentMarker::DictationResult)
         return true;
@@ -433,7 +463,7 @@ void DocumentMarkerController::copyMarkers(Node& srcNode, unsigned startOffset,
         dstNode.renderer()->repaint();
 }
 
-void DocumentMarkerController::removeMarkers(Node& node, unsigned startOffset, int length, OptionSet<DocumentMarker::MarkerType> markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
+void DocumentMarkerController::removeMarkers(Node& node, unsigned startOffset, int length, OptionSet<DocumentMarker::MarkerType> markerTypes, std::function<bool(DocumentMarker*)> filterFunction, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
 {
     if (length <= 0)
         return;
@@ -461,6 +491,11 @@ void DocumentMarkerController::removeMarkers(Node& node, unsigned startOffset, i
             continue;
         }
 
+        if (filterFunction && !filterFunction(&marker)) {
+            i++;
+            continue;
+        }
+
         // at this point we know that marker and target intersect in some way
         docDirty = true;
 
index 82e03f5..2eca521 100644 (file)
@@ -48,8 +48,8 @@ public:
     ~DocumentMarkerController();
 
     void detach();
-    void addMarker(Range&, DocumentMarker::MarkerType);
-    void addMarker(Range&, DocumentMarker::MarkerType, const String& description);
+    WEBCORE_EXPORT void addMarker(Range&, DocumentMarker::MarkerType);
+    WEBCORE_EXPORT void addMarker(Range&, DocumentMarker::MarkerType, const String& description);
     void addMarkerToNode(Node&, unsigned startOffset, unsigned length, DocumentMarker::MarkerType);
     void addMarkerToNode(Node&, unsigned startOffset, unsigned length, DocumentMarker::MarkerType, DocumentMarker::Data&&);
     WEBCORE_EXPORT void addTextMatchMarker(const Range&, bool activeMatch);
@@ -60,6 +60,10 @@ public:
 #endif
     void addDraggedContentMarker(Range&);
 
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    WEBCORE_EXPORT void addPlatformTextCheckingMarker(Range&, const String& key, const String& value);
+#endif
+
     void copyMarkers(Node& srcNode, unsigned startOffset, int length, Node& dstNode, int delta);
     bool hasMarkers() const
     {
@@ -72,8 +76,11 @@ public:
     // remove the marker. If the argument is false, we will adjust the span of the marker so that it retains
     // the portion that is outside of the range.
     enum RemovePartiallyOverlappingMarkerOrNot { DoNotRemovePartiallyOverlappingMarker, RemovePartiallyOverlappingMarker };
-    void removeMarkers(Range&, OptionSet<DocumentMarker::MarkerType> = DocumentMarker::allMarkers(), RemovePartiallyOverlappingMarkerOrNot = DoNotRemovePartiallyOverlappingMarker);
-    void removeMarkers(Node&, unsigned startOffset, int length, OptionSet<DocumentMarker::MarkerType> = DocumentMarker::allMarkers(),  RemovePartiallyOverlappingMarkerOrNot = DoNotRemovePartiallyOverlappingMarker);
+    WEBCORE_EXPORT void removeMarkers(Range&, OptionSet<DocumentMarker::MarkerType> = DocumentMarker::allMarkers(), RemovePartiallyOverlappingMarkerOrNot = DoNotRemovePartiallyOverlappingMarker);
+    void removeMarkers(Node&, unsigned startOffset, int length, OptionSet<DocumentMarker::MarkerType> = DocumentMarker::allMarkers(), std::function<bool(DocumentMarker*)> filterFunction = nullptr, RemovePartiallyOverlappingMarkerOrNot = DoNotRemovePartiallyOverlappingMarker);
+
+    // Return false from filterFunction to remove the marker.
+    WEBCORE_EXPORT void filterMarkers(Range&, std::function<bool(DocumentMarker*)> filterFunction, OptionSet<DocumentMarker::MarkerType> = DocumentMarker::allMarkers(), RemovePartiallyOverlappingMarkerOrNot = DoNotRemovePartiallyOverlappingMarker);
 
     WEBCORE_EXPORT void removeMarkers(OptionSet<DocumentMarker::MarkerType> = DocumentMarker::allMarkers());
     void removeMarkers(Node&, OptionSet<DocumentMarker::MarkerType> = DocumentMarker::allMarkers());
index fc123bf..ebee074 100644 (file)
@@ -781,6 +781,18 @@ void CompositeEditCommand::replaceTextInNodePreservingMarkers(Text& node, unsign
             continue;
         }
 #endif
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+        if (marker.type() == DocumentMarker::PlatformTextChecking) {
+            if (!WTF::holds_alternative<DocumentMarker::PlatformTextCheckingData>(marker.data())) {
+                ASSERT_NOT_REACHED();
+                continue;
+            }
+
+            auto& textCheckingData = WTF::get<DocumentMarker::PlatformTextCheckingData>(marker.data());
+            markerController.addPlatformTextCheckingMarker(newRange, textCheckingData.key, textCheckingData.value);
+            continue;
+        }
+#endif
         markerController.addMarker(newRange, marker.type(), marker.description());
     }
 }
index 0a33f95..009301e 100644 (file)
@@ -2408,6 +2408,9 @@ void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart,
 {
     Ref<Frame> protection(m_frame);
 
+    if (platformDrivenTextCheckerEnabled())
+        return;
+
 #if PLATFORM(IOS_FAMILY)
     UNUSED_PARAM(selectionAfterTyping);
     UNUSED_PARAM(doReplacement);
@@ -2630,6 +2633,9 @@ void Editor::markBadGrammar(const VisibleSelection& selection)
 
 void Editor::markAllMisspellingsAndBadGrammarInRanges(OptionSet<TextCheckingType> textCheckingOptions, RefPtr<Range>&& spellingRange, RefPtr<Range>&& automaticReplacementRange, RefPtr<Range>&& grammarRange)
 {
+    if (platformDrivenTextCheckerEnabled())
+        return;
+
     ASSERT(unifiedTextCheckerEnabled());
 
     // There shouldn't be pending autocorrection at this moment.
@@ -2693,6 +2699,11 @@ static bool isAutomaticTextReplacementType(TextCheckingType type)
     return false;
 }
 
+void Editor::replaceRangeForSpellChecking(Range& rangeToReplace, const String& replacement)
+{
+    SpellingCorrectionCommand::create(rangeToReplace, replacement)->apply();
+}
+
 static void correctSpellcheckingPreservingTextCheckingParagraph(TextCheckingParagraph& paragraph, Range& rangeToReplace, const String& replacement, int resultLocation, int resultLength)
 {
     auto& scope = downcast<ContainerNode>(paragraph.paragraphRange().startContainer().rootNode());
@@ -2905,6 +2916,9 @@ void Editor::changeBackToReplacedString(const String& replacedString)
 
 void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection)
 {
+    if (platformDrivenTextCheckerEnabled())
+        return;
+
     if (unifiedTextCheckerEnabled()) {
         if (!isContinuousSpellCheckingEnabled())
             return;
index e08da32..9aca8e7 100644 (file)
@@ -301,6 +301,7 @@ public:
     void markBadGrammar(const VisibleSelection&);
     void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection);
     void markAndReplaceFor(const SpellCheckRequest&, const Vector<TextCheckingResult>&);
+    WEBCORE_EXPORT void replaceRangeForSpellChecking(Range&, const String&);
 
     bool isOverwriteModeEnabled() const { return m_overwriteModeEnabled; }
     WEBCORE_EXPORT void toggleOverwriteModeEnabled();
index 104ca38..8c234a4 100644 (file)
@@ -303,6 +303,9 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b
     if (!unifiedTextCheckerEnabled())
         return emptyString();
 
+    if (platformDrivenTextCheckerEnabled())
+        return emptyString();
+
     String firstFoundItem;
     String misspelledWord;
     String badGrammarPhrase;
@@ -572,6 +575,9 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool
     if (!unifiedTextCheckerEnabled())
         return Vector<String>();
 
+    if (platformDrivenTextCheckerEnabled())
+        return Vector<String>();
+
     Vector<String> guesses;
     misspelled = false;
     ungrammatical = false;
@@ -689,4 +695,13 @@ bool unifiedTextCheckerEnabled(const Frame* frame)
     return frame->settings().unifiedTextCheckerEnabled();
 }
 
+bool platformDrivenTextCheckerEnabled()
+{
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    return true;
+#else
+    return false;
+#endif
+}
+
 }
index ca71280..3f39e86 100644 (file)
@@ -111,5 +111,6 @@ private:
 void checkTextOfParagraph(TextCheckerClient&, StringView, OptionSet<TextCheckingType>, Vector<TextCheckingResult>&, const VisibleSelection& currentSelection);
 
 bool unifiedTextCheckerEnabled(const Frame*);
+bool platformDrivenTextCheckerEnabled();
 
 } // namespace WebCore
index 50d9256..117fd67 100644 (file)
@@ -1,3 +1,62 @@
+2019-03-20  Tim Horton  <timothy_horton@apple.com>
+
+        Add an platform-driven spell-checking mechanism
+        https://bugs.webkit.org/show_bug.cgi?id=195795
+
+        Reviewed by Ryosuke Niwa.
+
+        * DerivedSources-input.xcfilelist:
+        * DerivedSources-output.xcfilelist:
+        * DerivedSources.make:
+        * SourcesCocoa.txt:
+        * UIProcess/Cocoa/TextCheckingController.h: Added.
+        * UIProcess/Cocoa/TextCheckingController.mm: Added.
+        (WebKit::TextCheckingController::TextCheckingController):
+        (WebKit::TextCheckingController::replaceRelativeToSelection):
+        (WebKit::TextCheckingController::removeAnnotationRelativeToSelection):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView setupInteraction]):
+        (-[WKContentView replaceSelectionOffset:length:withAnnotatedString:relativeReplacementRange:]):
+        (-[WKContentView removeAnnotation:forSelectionOffset:length:]):
+        * WebKit.xcodeproj/project.pbxproj:
+        * WebProcess/WebPage/WebPage.cpp:
+        * WebProcess/WebPage/WebPage.h:
+        (WebKit::WebPage::textCheckingController):
+        Plumb two UITextInput methods through to the Web Content process.
+        I added a new object instead of just sticking things on WebPage
+        because there are quite a few more related ones coming down the pipeline,
+        and will also end up being messages going in the opposite direction.
+
+        * WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.h: Added.
+        * WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.messages.in: Added.
+        * WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm: Added.
+        (WebKit::TextCheckingControllerProxy::TextCheckingControllerProxy):
+        (WebKit::TextCheckingControllerProxy::~TextCheckingControllerProxy):
+        (WebKit::relevantMarkerTypes):
+        (WebKit::TextCheckingControllerProxy::rangeAndOffsetRelativeToSelection):
+        (WebKit::TextCheckingControllerProxy::replaceRelativeToSelection):
+        (WebKit::TextCheckingControllerProxy::removeAnnotationRelativeToSelection):
+        Make it possible for the platform to maintain arbitrary key-value pairs
+        attached to document ranges, as a way for it to keep track of various
+        text checking context (e.g. if something has been checked, replaced,
+        what language it might be, ...).
+
+        Allow it to replace the text of a range and the annotations in that range,
+        or remove annotations in a range. Ranges are specified relative to
+        the selection.
+
+        One large missing piece is giving the platform the ability to retrieve
+        annotations in a range; that is coming in a future patch.
+
+        We translate certain annotations into traditional WebCore spelling
+        and grammar document markers, for normal display-time treatment.
+
+        * WebProcess/WebPage/mac/WKAccessibilityWebPageObjectMac.mm:
+        (-[WKAccessibilityWebPageObject convertScreenPointToRootView:]):
+        (-[WKAccessibilityWebPageObject accessibilityHitTest:]):
+        Unified sources fixes.
+
 2019-03-19  Jiewen Tan  <jiewen_tan@apple.com>
 
         [WebAuthN] Implement FIDO AppID extension
index c77da5d..b50c865 100644 (file)
@@ -112,6 +112,7 @@ $(PROJECT_DIR)/WebProcess/Storage/WebSWClientConnection.messages.in
 $(PROJECT_DIR)/WebProcess/Storage/WebSWContextManagerConnection.messages.in
 $(PROJECT_DIR)/WebProcess/UserContent/WebUserContentController.messages.in
 $(PROJECT_DIR)/WebProcess/WebAuthentication/WebAuthenticatorCoordinator.messages.in
+$(PROJECT_DIR)/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.messages.in
 $(PROJECT_DIR)/WebProcess/WebPage/DrawingArea.messages.in
 $(PROJECT_DIR)/WebProcess/WebPage/EventDispatcher.messages.in
 $(PROJECT_DIR)/WebProcess/WebPage/RemoteLayerTree/RemoteScrollingCoordinator.messages.in
index 1aa9703..5753167 100644 (file)
@@ -88,6 +88,8 @@ $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/StorageAreaMapMessageReceiver.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/StorageAreaMapMessages.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/StorageManagerMessageReceiver.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/StorageManagerMessages.h
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/TextCheckingControllerProxyMessageReceiver.cpp
+$(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/TextCheckingControllerProxyMessages.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/UserMediaCaptureManagerMessageReceiver.cpp
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/UserMediaCaptureManagerMessages.h
 $(BUILT_PRODUCTS_DIR)/DerivedSources/WebKit2/UserMediaCaptureManagerProxyMessageReceiver.cpp
index 7435d06..01482e6 100644 (file)
@@ -59,6 +59,7 @@ VPATH = \
     $(WebKit2)/WebProcess/WebAuthentication \
     $(WebKit2)/WebProcess/WebCoreSupport \
     $(WebKit2)/WebProcess/WebPage \
+    $(WebKit2)/WebProcess/WebPage/Cocoa \
     $(WebKit2)/WebProcess/WebPage/RemoteLayerTree \
     $(WebKit2)/WebProcess/WebStorage \
     $(WebKit2)/WebProcess/cocoa \
@@ -135,6 +136,7 @@ MESSAGE_RECEIVERS = \
     SmartMagnificationController \
     StorageAreaMap \
     StorageManager \
+    TextCheckingControllerProxy \
     UserMediaCaptureManager \
     UserMediaCaptureManagerProxy \
     VideoFullscreenManager \
index 7eb510a..61ec29c 100644 (file)
@@ -339,6 +339,7 @@ UIProcess/Cocoa/PlaybackSessionManagerProxy.mm
 UIProcess/Cocoa/SafeBrowsingWarningCocoa.mm
 UIProcess/Cocoa/SessionStateCoding.mm
 UIProcess/Cocoa/SystemPreviewControllerCocoa.mm
+UIProcess/Cocoa/TextCheckingController.mm
 UIProcess/Cocoa/UIDelegate.mm
 UIProcess/Cocoa/UserMediaCaptureManagerProxy.cpp
 UIProcess/Cocoa/VersionChecks.mm
@@ -558,6 +559,7 @@ WebProcess/WebPage/ViewGestureGeometryCollector.cpp
 WebProcess/WebPage/ViewUpdateDispatcher.cpp
 WebProcess/WebPage/WKAccessibilityWebPageObjectIOS.mm
 
+WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm
 WebProcess/WebPage/Cocoa/WebPageCocoa.mm
 
 WebProcess/WebPage/ios/FindControllerIOS.mm
@@ -584,3 +586,4 @@ WebProcess/WebPage/RemoteLayerTree/RemoteScrollingCoordinator.mm
 // Derived Sources
 EditableImageControllerMessageReceiver.cpp
 ServiceWorkerFetchTaskMessageReceiver.cpp
+TextCheckingControllerProxyMessageReceiver.cpp
diff --git a/Source/WebKit/UIProcess/Cocoa/TextCheckingController.h b/Source/WebKit/UIProcess/Cocoa/TextCheckingController.h
new file mode 100644 (file)
index 0000000..81a82fc
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+
+#import "EditingRange.h"
+#import "WebPageProxy.h"
+#import <WebCore/FloatRect.h>
+#import <wtf/CompletionHandler.h>
+
+OBJC_CLASS NSAttributedString;
+
+namespace WebKit {
+
+class TextCheckingController final {
+    WTF_MAKE_NONCOPYABLE(TextCheckingController);
+public:
+    explicit TextCheckingController(WebPageProxy&);
+    ~TextCheckingController() = default;
+
+    void replaceRelativeToSelection(NSAttributedString *annotatedString, int64_t selectionOffset, uint64_t length, bool textChanged);
+    void removeAnnotationRelativeToSelection(NSString *annotationName, int64_t selectionOffset, uint64_t length);
+
+private:
+    WebPageProxy& m_page;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
diff --git a/Source/WebKit/UIProcess/Cocoa/TextCheckingController.mm b/Source/WebKit/UIProcess/Cocoa/TextCheckingController.mm
new file mode 100644 (file)
index 0000000..d370f03
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 "TextCheckingController.h"
+
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+
+#import "AttributedString.h"
+#import "TextCheckingControllerProxyMessages.h"
+#import "WebProcessProxy.h"
+
+namespace WebKit {
+
+TextCheckingController::TextCheckingController(WebPageProxy& webPageProxy)
+    : m_page(webPageProxy)
+{
+}
+
+void TextCheckingController::replaceRelativeToSelection(NSAttributedString *annotatedString, int64_t selectionOffset, uint64_t length, bool textChanged)
+{
+    if (!m_page.hasRunningProcess())
+        return;
+
+    m_page.process().send(Messages::TextCheckingControllerProxy::ReplaceRelativeToSelection(annotatedString, selectionOffset, length, textChanged), m_page.pageID());
+}
+
+void TextCheckingController::removeAnnotationRelativeToSelection(NSString *annotationName, int64_t selectionOffset, uint64_t length)
+{
+    if (!m_page.hasRunningProcess())
+        return;
+
+    m_page.process().send(Messages::TextCheckingControllerProxy::RemoveAnnotationRelativeToSelection(annotationName, selectionOffset, length), m_page.pageID());
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
index cf49638..bea0d92 100644 (file)
 #import "UIKitSPI.h"
 #endif
 
-#if PLATFORM(IOS)
-#import "UIKitSPI.h"
-#import <wtf/SoftLinking.h>
-#endif
-
 NSString *WebServiceWorkerRegistrationDirectoryDefaultsKey = @"WebServiceWorkerRegistrationDirectory";
 NSString *WebKitLocalCacheDefaultsKey = @"WebKitLocalCache";
 NSString *WebKitJSCJITEnabledDefaultsKey = @"WebKitJSCJITEnabledDefaultsKey";
index 907975c..78839f0 100644 (file)
@@ -36,6 +36,7 @@
 #import "FocusedElementInformation.h"
 #import "GestureTypes.h"
 #import "InteractionInformationAtPosition.h"
+#import "TextCheckingController.h"
 #import "UIKitSPI.h"
 #import "WKActionSheetAssistant.h"
 #import "WKAirPlayRoutePicker.h"
@@ -340,6 +341,10 @@ struct WKAutoCorrectionData {
     BOOL _shouldRestoreFirstResponderStatusAfterLosingFocus;
     BlockPtr<void()> _activeFocusedStateRetainBlock;
 #endif
+
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    std::unique_ptr<WebKit::TextCheckingController> _textCheckingController;
+#endif
 }
 
 @end
index f545485..ff08351 100644 (file)
@@ -782,6 +782,10 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl
     _dataListTextSuggestions = nil;
 #endif
 
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    _textCheckingController = std::make_unique<WebKit::TextCheckingController>(*_page);
+#endif
+
     _hasSetUpInteractions = YES;
 }
 
@@ -5511,6 +5515,18 @@ static BOOL allPasteboardItemOriginsMatchOrigin(UIPasteboard *pasteboard, const
     _page->extendSelection(WebCore::WordGranularity);
 }
 
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+- (void)replaceSelectionOffset:(NSInteger)selectionOffset length:(NSUInteger)length withAnnotatedString:(NSAttributedString *)annotatedString relativeReplacementRange:(NSRange)relativeReplacementRange
+{
+    _textCheckingController->replaceRelativeToSelection(annotatedString, selectionOffset, length, relativeReplacementRange.location != NSNotFound);
+}
+
+- (void)removeAnnotation:(NSAttributedStringKey)annotationName forSelectionOffset:(NSInteger)selectionOffset length:(NSUInteger)length
+{
+    _textCheckingController->removeAnnotationRelativeToSelection(annotationName, selectionOffset, length);
+}
+#endif
+
 - (void)_updateChangedSelection
 {
     [self _updateChangedSelection:NO];
index 6249b1c..bc1336d 100644 (file)
                29D04E2821F7C73D0076741D /* AccessibilityPrivSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AccessibilityPrivSPI.h; sourceTree = "<group>"; };
                2D0035221BC7414800DA8716 /* PDFPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PDFPlugin.h; path = PDF/PDFPlugin.h; sourceTree = "<group>"; };
                2D0035231BC7414800DA8716 /* PDFPlugin.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = PDFPlugin.mm; path = PDF/PDFPlugin.mm; sourceTree = "<group>"; };
+               2D0CF64B21F2A80300787566 /* TextCheckingController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TextCheckingController.mm; sourceTree = "<group>"; };
+               2D0CF64C21F2A80300787566 /* TextCheckingController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextCheckingController.h; sourceTree = "<group>"; };
                2D10875E1D2C573E00B85F82 /* LoadParameters.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LoadParameters.cpp; sourceTree = "<group>"; };
                2D10875F1D2C573E00B85F82 /* LoadParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadParameters.h; sourceTree = "<group>"; };
                2D1087621D2C641B00B85F82 /* LoadParametersCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LoadParametersCocoa.mm; sourceTree = "<group>"; };
                2D8786221BDB58FF00D02ABB /* APIUserStyleSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APIUserStyleSheet.h; sourceTree = "<group>"; };
                2D8949EE182044F600E898AA /* PlatformCALayerRemoteTiledBacking.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlatformCALayerRemoteTiledBacking.cpp; sourceTree = "<group>"; };
                2D8949EF182044F600E898AA /* PlatformCALayerRemoteTiledBacking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlatformCALayerRemoteTiledBacking.h; sourceTree = "<group>"; };
+               2D9CD5EB21FA503F0029ACFA /* TextCheckingControllerProxy.messages.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextCheckingControllerProxy.messages.in; sourceTree = "<group>"; };
+               2D9CD5EC21FA503F0029ACFA /* TextCheckingControllerProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TextCheckingControllerProxy.mm; sourceTree = "<group>"; };
+               2D9CD5ED21FA503F0029ACFA /* TextCheckingControllerProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextCheckingControllerProxy.h; sourceTree = "<group>"; };
                2D9CD5EE21FA75EE0029ACFA /* EditingRange.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EditingRange.cpp; sourceTree = "<group>"; };
                2D9EA30C1A96CB59002D2807 /* WKPageInjectedBundleClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKPageInjectedBundleClient.h; sourceTree = "<group>"; };
                2D9EA30E1A96CBFF002D2807 /* WebPageInjectedBundleClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebPageInjectedBundleClient.h; sourceTree = "<group>"; };
                                1A002D47196B345D00B9AD44 /* SessionStateCoding.h */,
                                1A002D46196B345D00B9AD44 /* SessionStateCoding.mm */,
                                3157135C2040A9B20084F9CF /* SystemPreviewControllerCocoa.mm */,
+                               2D0CF64C21F2A80300787566 /* TextCheckingController.h */,
+                               2D0CF64B21F2A80300787566 /* TextCheckingController.mm */,
                                1AFE436418B6C081009C7A48 /* UIDelegate.h */,
                                1AFE436318B6C081009C7A48 /* UIDelegate.mm */,
                                E4E8648E1B1673FB00C82F40 /* VersionChecks.h */,
                2D29ECCD192F2C2E00984B78 /* Cocoa */ = {
                        isa = PBXGroup;
                        children = (
+                               2D9CD5ED21FA503F0029ACFA /* TextCheckingControllerProxy.h */,
+                               2D9CD5EB21FA503F0029ACFA /* TextCheckingControllerProxy.messages.in */,
+                               2D9CD5EC21FA503F0029ACFA /* TextCheckingControllerProxy.mm */,
                                2DC4CF7A1D2DE24B00ECCC94 /* WebPageCocoa.mm */,
                        );
                        path = Cocoa;
diff --git a/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.h b/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.h
new file mode 100644 (file)
index 0000000..bd93b9d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+
+#include "AttributedString.h"
+#include "Connection.h"
+#include "EditingRange.h"
+#include "MessageReceiver.h"
+#include <wtf/Vector.h>
+
+namespace IPC {
+class Decoder;
+class Encoder;
+}
+
+namespace WebKit {
+
+class WebPage;
+
+class TextCheckingControllerProxy : public IPC::MessageReceiver {
+public:
+    TextCheckingControllerProxy(WebPage&);
+    ~TextCheckingControllerProxy();
+
+private:
+    // IPC::MessageReceiver
+    void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override;
+
+    struct RangeAndOffset {
+        RefPtr<WebCore::Range> range;
+        size_t locationInRoot;    
+    };
+    Optional<RangeAndOffset> rangeAndOffsetRelativeToSelection(int64_t offset, uint64_t length);
+
+    // Message handlers.
+    void replaceRelativeToSelection(AttributedString, int64_t selectionOffset, uint64_t length, bool textChanged);
+    void removeAnnotationRelativeToSelection(String annotationName, int64_t selectionOffset, uint64_t length);
+
+    WebPage& m_page;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
diff --git a/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.messages.in b/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.messages.in
new file mode 100644 (file)
index 0000000..1ffa271
--- /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.
+
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+
+messages -> TextCheckingControllerProxy {
+    ReplaceRelativeToSelection(struct WebKit::AttributedString annotatedString, int64_t selectionOffset, uint64_t length, bool textChanged)
+
+    RemoveAnnotationRelativeToSelection(String annotationName, int64_t selectionOffset, uint64_t length)
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm b/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm
new file mode 100644 (file)
index 0000000..0755882
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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 "TextCheckingControllerProxy.h"
+
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+
+#import "ArgumentCoders.h"
+#import "TextCheckingControllerProxyMessages.h"
+#import "WebCoreArgumentCoders.h"
+#import "WebPage.h"
+#import "WebProcess.h"
+#import <WebCore/DocumentMarker.h>
+#import <WebCore/DocumentMarkerController.h>
+#import <WebCore/Editing.h>
+#import <WebCore/Editor.h>
+#import <WebCore/FocusController.h>
+#import <WebCore/RenderObject.h>
+#import <WebCore/RenderedDocumentMarker.h>
+#import <WebCore/TextIterator.h>
+#import <WebCore/VisibleUnits.h>
+
+// FIXME: Remove this after rdar://problem/48914153 is resolved.
+#if PLATFORM(IOSMAC)
+typedef NS_ENUM(NSInteger, NSSpellingState) {
+    NSSpellingStateSpellingFlag = (1 << 0),
+    NSSpellingStateGrammarFlag = (1 << 1)
+};
+#endif
+
+namespace WebKit {
+using namespace WebCore;
+
+TextCheckingControllerProxy::TextCheckingControllerProxy(WebPage& page)
+    : m_page(page)
+{
+    WebProcess::singleton().addMessageReceiver(Messages::TextCheckingControllerProxy::messageReceiverName(), m_page.pageID(), *this);
+}
+
+TextCheckingControllerProxy::~TextCheckingControllerProxy()
+{
+    WebProcess::singleton().removeMessageReceiver(Messages::TextCheckingControllerProxy::messageReceiverName(), m_page.pageID());
+}
+
+static OptionSet<DocumentMarker::MarkerType> relevantMarkerTypes()
+{
+    return { DocumentMarker::PlatformTextChecking, DocumentMarker::Spelling, DocumentMarker::Grammar };
+}
+
+Optional<TextCheckingControllerProxy::RangeAndOffset> TextCheckingControllerProxy::rangeAndOffsetRelativeToSelection(int64_t offset, uint64_t length)
+{
+    Frame& frame = m_page.corePage()->focusController().focusedOrMainFrame();
+    const FrameSelection& frameSelection = frame.selection();
+    const VisibleSelection& selection = frameSelection.selection();
+    if (selection.isNone())
+        return WTF::nullopt;
+
+    auto root = frameSelection.rootEditableElementOrDocumentElement();
+    auto range = selection.toNormalizedRange();
+    range->collapse(true);
+
+    size_t selectionLocation;
+    size_t selectionLength;
+    TextIterator::getLocationAndLengthFromRange(root, range.get(), selectionLocation, selectionLength);
+    selectionLocation += offset;
+
+    return {{ TextIterator::rangeFromLocationAndLength(root, selectionLocation, length), selectionLocation }};
+}
+
+void TextCheckingControllerProxy::replaceRelativeToSelection(AttributedString annotatedString, int64_t selectionOffset, uint64_t length, bool textChanged)
+{
+    Frame& frame = m_page.corePage()->focusController().focusedOrMainFrame();
+    FrameSelection& frameSelection = frame.selection();
+    auto root = frameSelection.rootEditableElementOrDocumentElement();
+
+    auto rangeAndOffset = rangeAndOffsetRelativeToSelection(selectionOffset, length);
+    if (!rangeAndOffset)
+        return;
+    auto range = rangeAndOffset->range;
+    if (!range)
+        return;
+    auto locationInRoot = rangeAndOffset->locationInRoot;
+
+    auto& markers = frame.document()->markers();
+    markers.removeMarkers(*range, relevantMarkerTypes());
+
+    if (textChanged) {
+        bool restoreSelection = frameSelection.selection().isRange();
+
+        frame.editor().replaceRangeForSpellChecking(*range, [annotatedString.string string]);
+
+        size_t selectionLocationToRestore = locationInRoot - selectionOffset;
+        if (restoreSelection && selectionLocationToRestore > locationInRoot + length) {
+            selectionLocationToRestore -= locationInRoot - length;
+            auto selectionToRestore = TextIterator::rangeFromLocationAndLength(root, selectionLocationToRestore, 0);
+            frameSelection.moveTo(selectionToRestore.get());
+        }
+    }
+
+    [annotatedString.string enumerateAttributesInRange:NSMakeRange(0, [annotatedString.string length]) options:0 usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange attributeRange, BOOL *stop) {
+        auto attributeCoreRange = TextIterator::rangeFromLocationAndLength(root, locationInRoot + attributeRange.location, attributeRange.length);
+        if (!attributeCoreRange)
+            return;
+
+        [attrs enumerateKeysAndObjectsUsingBlock:^(NSAttributedStringKey key, id value, BOOL *stop) {
+            if (![value isKindOfClass:[NSString class]])
+                return;
+            markers.addPlatformTextCheckingMarker(*attributeCoreRange, key, (NSString *)value);
+
+            // FIXME: Switch to constants after rdar://problem/48914153 is resolved.
+            if ([key isEqualToString:@"NSSpellingState"]) {
+                NSSpellingState spellingState = (NSSpellingState)[value integerValue];
+                if (spellingState & NSSpellingStateSpellingFlag)
+                    markers.addMarker(*attributeCoreRange, DocumentMarker::Spelling);
+                if (spellingState & NSSpellingStateGrammarFlag) {
+                    NSString *userDescription = [attrs objectForKey:@"NSGrammarUserDescription"];
+                    markers.addMarker(*attributeCoreRange, DocumentMarker::Grammar, userDescription);
+                }
+            }
+        }];
+    }];
+}
+
+void TextCheckingControllerProxy::removeAnnotationRelativeToSelection(String annotation, int64_t selectionOffset, uint64_t length)
+{
+    Frame& frame = m_page.corePage()->focusController().focusedOrMainFrame();
+    auto rangeAndOffset = rangeAndOffsetRelativeToSelection(selectionOffset, length);
+    if (!rangeAndOffset)
+        return;
+    auto range = rangeAndOffset->range;
+    if (!range)
+        return;
+
+    bool removeCoreSpellingMarkers = (annotation == String(@"NSSpellingState"));
+    frame.document()->markers().filterMarkers(*range, [&] (DocumentMarker* marker) {
+        if (!WTF::holds_alternative<DocumentMarker::PlatformTextCheckingData>(marker->data()))
+            return false;
+        auto& textCheckingData = WTF::get<DocumentMarker::PlatformTextCheckingData>(marker->data());
+        return textCheckingData.key != annotation;
+    }, removeCoreSpellingMarkers ? relevantMarkerTypes() : DocumentMarker::PlatformTextChecking);
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
index 78c21ec..4a5ade5 100644 (file)
 #include "PDFPlugin.h"
 #include "PlaybackSessionManager.h"
 #include "RemoteLayerTreeTransaction.h"
+#include "TextCheckingControllerProxy.h"
 #include "TouchBarMenuData.h"
 #include "TouchBarMenuItemData.h"
 #include "VideoFullscreenManager.h"
@@ -370,6 +371,9 @@ WebPage::WebPage(uint64_t pageID, WebPageCreationParameters&& parameters)
     , m_determinePrimarySnapshottedPlugInTimer(RunLoop::main(), this, &WebPage::determinePrimarySnapshottedPlugInTimerFired)
 #endif
     , m_layerHostingMode(parameters.layerHostingMode)
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    , m_textCheckingControllerProxy(makeUniqueRef<TextCheckingControllerProxy>(*this))
+#endif
 #if PLATFORM(COCOA) || PLATFORM(GTK)
     , m_viewGestureGeometryCollector(std::make_unique<ViewGestureGeometryCollector>(*this))
 #elif HAVE(ACCESSIBILITY) && PLATFORM(GTK)
index 0bc6406..8d1a786 100644 (file)
@@ -207,6 +207,7 @@ class PDFPlugin;
 class PageBanner;
 class PluginView;
 class RemoteWebInspectorUI;
+class TextCheckingControllerProxy;
 class UserMediaPermissionRequestManager;
 class ViewGestureGeometryCollector;
 class VisibleContentRectUpdateInfo;
@@ -1167,6 +1168,10 @@ public:
     WebPaymentCoordinator* paymentCoordinator();
 #endif
 
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    TextCheckingControllerProxy& textCheckingController() { return m_textCheckingControllerProxy.get(); }
+#endif
+
 private:
     WebPage(uint64_t pageID, WebPageCreationParameters&&);
 
@@ -1449,8 +1454,6 @@ private:
 
     static PluginView* focusedPluginViewForFrame(WebCore::Frame&);
 
-    static RefPtr<WebCore::Range> rangeFromEditingRange(WebCore::Frame&, const EditingRange&, EditingRangeIsRelativeTo = EditingRangeIsRelativeTo::EditableRoot);
-
     void reportUsedFeatures();
 
     void updateWebsitePolicies(WebsitePoliciesData&&);
@@ -1612,6 +1615,10 @@ private:
     RetainPtr<WKAccessibilityWebPageObject> m_mockAccessibilityElement;
 #endif
 
+#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
+    UniqueRef<TextCheckingControllerProxy> m_textCheckingControllerProxy;
+#endif
+
 #if PLATFORM(COCOA) || PLATFORM(GTK)
     std::unique_ptr<ViewGestureGeometryCollector> m_viewGestureGeometryCollector;
 #endif
index 92d28b6..76892fa 100644 (file)
@@ -119,7 +119,7 @@ IGNORE_WARNINGS_END
 - (NSPoint)convertScreenPointToRootView:(NSPoint)point
 {
     return retrieveAccessibilityValueFromMainThread<NSPoint>([&self, &point] () -> NSPoint {
-        return m_page->screenToRootView(IntPoint(point.x, point.y));
+        return m_page->screenToRootView(WebCore::IntPoint(point.x, point.y));
     });
 }
 
@@ -238,11 +238,11 @@ IGNORE_WARNINGS_END
 ALLOW_DEPRECATED_DECLARATIONS_BEGIN
 - (id)accessibilityHitTest:(NSPoint)point
 {
-    auto convertedPoint = retrieveAccessibilityValueFromMainThread<IntPoint>([&self, &point] () -> IntPoint {
+    auto convertedPoint = retrieveAccessibilityValueFromMainThread<WebCore::IntPoint>([&self, &point] () -> WebCore::IntPoint {
         if (!m_page)
-            return IntPoint(point);
+            return WebCore::IntPoint(point);
         
-        auto convertedPoint = m_page->screenToRootView(IntPoint(point));
+        auto convertedPoint = m_page->screenToRootView(WebCore::IntPoint(point));
         
         // Some plugins may be able to figure out the scroll position and inset on their own.
         bool applyContentOffset = true;