Title: [233439] trunk
Revision
233439
Author
wenson_hs...@apple.com
Date
2018-07-02 15:04:51 -0700 (Mon, 02 Jul 2018)

Log Message

[WK1] editing/spelling/markers.html is failing on recent builds of macOS Mojave
https://bugs.webkit.org/show_bug.cgi?id=187253

Reviewed by Tim Horton.

Tools:

Enhances and refactors LayoutTestSpellChecker, which was introduced in r233412.

* DumpRenderTree/TestRunner.cpp:
(setSpellCheckerResultsCallback):

Rename setSpellCheckerTextReplacements to setSpellCheckerResults.

(TestRunner::staticFunctions):
(setSpellCheckerTextReplacementsCallback): Deleted.
* DumpRenderTree/TestRunner.h:
* DumpRenderTree/mac/TestRunnerMac.mm:
(TestRunner::setSpellCheckerResults):
(TestRunner::setSpellCheckerTextReplacements): Deleted.
* DumpRenderTree/win/TestRunnerWin.cpp:
(TestRunner::setSpellCheckerResults):
(TestRunner::setSpellCheckerTextReplacements): Deleted.
* TestRunnerShared/cocoa/LayoutTestSpellChecker.h:
* TestRunnerShared/cocoa/LayoutTestSpellChecker.mm:
(-[LayoutTestTextCheckingResult initWithType:range:replacement:details:]):
(-[LayoutTestTextCheckingResult grammarDetails]):
(-[LayoutTestSpellChecker reset]):
(-[LayoutTestSpellChecker results]):
(-[LayoutTestSpellChecker setResults:]):
(-[LayoutTestSpellChecker setResultsFromJSObject:inContext:]):

Add support for passing in a list of grammar correction detail ranges. Necessary for simulating grammar errors.

(-[LayoutTestSpellChecker checkString:range:types:options:inSpellDocumentWithTag:orthography:wordCount:]):

Tweaked to always call the superclass method. This ensures that we set the `orthography` and `wordCount`
outpointers if applicable.

(-[LayoutTestSpellChecker requestCheckingOfString:range:types:options:inSpellDocumentWithTag:completionHandler:]):

Added support for simulating asynchronous spell checking.

(-[LayoutTestTextCheckingResult initWithType:range:replacement:]): Deleted.
(-[LayoutTestSpellChecker replacements]): Deleted.
(-[LayoutTestSpellChecker setReplacements:]): Deleted.
(-[LayoutTestSpellChecker setReplacementsFromJSObject:inContext:]): Deleted.

LayoutTests:

In recent builds of macOS Mojave, NSSpellChecker returns both grammar and spelling errors when asked to analyze
the string "I have a issue.". While arguably correct, the change causes this existing layout test to fail due
to one of the calls to `verifyUnexpectedMarkers` expecting either grammar markers and not spelling markers, or
vice versa.

To fix this, we can leverage the mechanism added in r233412 to simulate different results from the platform
spellchecker, such that the test now exercises all combinations of grammar and spelling corrections observed on
each macOS platform, regardless of the actual platform where the test is being run.

This patch also enhances the capabilities of `LayoutTestSpellChecker`. See `Tools/ChangeLog` for more details.

* editing/spelling/markers-expected.txt:
* editing/spelling/markers.html:
* editing/spelling/text-replacement-after-typing-to-word.html:

Adjusted for a renamed TestRunner method.

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (233438 => 233439)


--- trunk/LayoutTests/ChangeLog	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/LayoutTests/ChangeLog	2018-07-02 22:04:51 UTC (rev 233439)
@@ -1,3 +1,27 @@
+2018-07-02  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [WK1] editing/spelling/markers.html is failing on recent builds of macOS Mojave
+        https://bugs.webkit.org/show_bug.cgi?id=187253
+
+        Reviewed by Tim Horton.
+
+        In recent builds of macOS Mojave, NSSpellChecker returns both grammar and spelling errors when asked to analyze
+        the string "I have a issue.". While arguably correct, the change causes this existing layout test to fail due
+        to one of the calls to `verifyUnexpectedMarkers` expecting either grammar markers and not spelling markers, or
+        vice versa.
+
+        To fix this, we can leverage the mechanism added in r233412 to simulate different results from the platform
+        spellchecker, such that the test now exercises all combinations of grammar and spelling corrections observed on
+        each macOS platform, regardless of the actual platform where the test is being run.
+
+        This patch also enhances the capabilities of `LayoutTestSpellChecker`. See `Tools/ChangeLog` for more details.
+
+        * editing/spelling/markers-expected.txt:
+        * editing/spelling/markers.html:
+        * editing/spelling/text-replacement-after-typing-to-word.html:
+
+        Adjusted for a renamed TestRunner method.
+
 2018-07-02  Michael Catanzaro  <mcatanz...@igalia.com>
 
         Unreviewed GTK gardening

Modified: trunk/LayoutTests/editing/spelling/markers-expected.txt (233438 => 233439)


--- trunk/LayoutTests/editing/spelling/markers-expected.txt	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/LayoutTests/editing/spelling/markers-expected.txt	2018-07-02 22:04:51 UTC (rev 233439)
@@ -7,6 +7,18 @@
 PASS internals.markerRangeForNode(element.firstChild, nextMisspellingData.marker, 0) became different from null
 PASS range.toString() is "a"
 
+Checking for issue on 'I have a issue.'
+PASS internals.markerRangeForNode(element.firstChild, nextMisspellingData.marker, 0) became different from null
+PASS range.toString() is "a"
+
+Checking for issue on 'I have a issue.'
+PASS internals.markerRangeForNode(element.firstChild, nextMisspellingData.marker, 0) became different from null
+PASS range.toString() is "a"
+
+Checking for issue on 'I have a issue.'
+PASS internals.markerRangeForNode(element.firstChild, nextMisspellingData.marker, 0) became different from null
+PASS range.toString() is "a"
+
 Checking for issue on 'zz.'
 PASS internals.markerRangeForNode(element.firstChild, nextMisspellingData.marker, 0) became different from null
 PASS range.toString() is "zz"
@@ -22,6 +34,9 @@
 Checking for no other issues on 'I have a issue.'
 PASS internals.markerCountForNode(element.firstChild, oppositeMarker) became 0
 
+Checking for no other issues on 'I have a issue.'
+PASS internals.markerCountForNode(element.firstChild, oppositeMarker) became 0
+
 Checking for no other issues on 'zz.'
 PASS internals.markerCountForNode(element.firstChild, oppositeMarker) became 0
 

Modified: trunk/LayoutTests/editing/spelling/markers.html (233438 => 233439)


--- trunk/LayoutTests/editing/spelling/markers.html	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/LayoutTests/editing/spelling/markers.html	2018-07-02 22:04:51 UTC (rev 233439)
@@ -25,9 +25,10 @@
     internals.settings.setAsynchronousSpellCheckingEnabled(true);
 }
 
-function createEditableElement(parent) {
+function createEditableElement(parent, textContent) {
     var e = document.createElement('div');
     e.setAttribute("contentEditable", "true");
+    e.textContent = textContent;
     e.className = 'editing';
 
     parent.appendChild(e);
@@ -36,52 +37,77 @@
 
 function typeText(elem, text) {
     elem.focus();
+    selectAllCommand();
+    deleteCommand();
     for (var i = 0; i < text.length; ++i)
         typeCharacterCommand(text[i]);
 }
 
-var container = document.getElementById('container');
+const container = document.getElementById('container');
+const elementWithGrammarIssue = createEditableElement(container, "I have a issue.");
+const elementWithSpellingIssue  = createEditableElement(container, "zz.");
+const elementWithGrammarAndSpellingIssue = createEditableElement(container, "orange,zz,apple.");
 
-var elementWithGrammarIssue = createEditableElement(container);
-typeText(elementWithGrammarIssue, 'I have a issue.');
-
-var elementWithSpellingIssue  = createEditableElement(container);
-typeText(elementWithSpellingIssue, 'zz.');
-
-var elementWithGrammarAndSpellingIssue = createEditableElement(container);
-typeText(elementWithGrammarAndSpellingIssue, 'orange,zz,apple.');
-
-var misspellings = [
-    { marker: internals.sentenceRetroCorrectionEnabled ? 'spelling' : 'grammar',  issue: 'a' },
+const misspellings = [
+    { marker: 'spelling',  issue: 'a' },
+    { marker: 'grammar',  issue: 'a' },
+    { marker: 'grammar',  issue: 'I have a issue.' },
     { marker: 'spelling', issue: 'zz' },
     { marker: 'grammar',  issue: 'orange,zz,apple.' },
     { marker: 'spelling',  issue: 'orange,zz,apple' },
 ];
 
+const results = [
+    { "type": "spelling", "from": 7, "to": 8 },
+    {
+        "type": "grammar",
+        "from": 7,
+        "to": 8,
+        "details": [{ "from": 0, "to": 1 }]
+    },
+    {
+        "type": "grammar",
+        "from": 0,
+        "to": 15,
+        "details": [{ "from": 7, "to": 8 }]
+    }
+];
+
 var tests = [
-    function() { verifyDesiredMarkers(elementWithGrammarIssue, misspellings.slice(0, 1)) },
-    function() { verifyDesiredMarkers(elementWithSpellingIssue, misspellings.slice(1, 2)) },
-    function() { verifyDesiredMarkers(elementWithGrammarAndSpellingIssue, misspellings.slice(2, 4)) },
+    function() { verifyDesiredMarkers(elementWithGrammarIssue, misspellings.slice(0, 1), results.slice(0, 1)) },
+    function() { verifyDesiredMarkers(elementWithGrammarIssue, misspellings.slice(1, 2), results.slice(1, 2)) },
+    function() { verifyDesiredMarkers(elementWithGrammarIssue, misspellings.slice(0, 2), results.slice(0, 2)) },
+    function() { verifyDesiredMarkers(elementWithSpellingIssue, misspellings.slice(3, 4)) },
+    function() { verifyDesiredMarkers(elementWithGrammarAndSpellingIssue, misspellings.slice(4, 6)) },
 
     // Those expect to have only one kind of markers either spelling or grammar.
-    function() { verifyUnexpectedMarkers(elementWithGrammarIssue, misspellings.slice(0, 1)) },
-    function() { verifyUnexpectedMarkers(elementWithSpellingIssue, misspellings.slice(1, 2)) },
+    function() { verifyUnexpectedMarkers(elementWithGrammarIssue, misspellings.slice(0, 1)), results.slice(0, 1) },
+    function() { verifyUnexpectedMarkers(elementWithGrammarIssue, misspellings.slice(1, 2)), results.slice(1, 2) },
+    function() { verifyUnexpectedMarkers(elementWithSpellingIssue, misspellings.slice(3, 4)) },
 ];
 
 var element;
 var nextMisspellingData;
 
-function verifyDesiredMarkers(e, misspellings)
+function verifyDesiredMarkers(e, misspellings, overrideSpellCheckingResults)
 {
     if (!window.internals)
         return done();
 
+    const textToCheck = e.firstChild.nodeValue;
+    const spellCheckerResults = { };
+    if (overrideSpellCheckingResults)
+        spellCheckerResults[textToCheck] = overrideSpellCheckingResults;
+    testRunner.setSpellCheckerResults(spellCheckerResults);
+
+    typeText(e, textToCheck);
+
     element = e;
     nextMisspellingData = misspellings.shift();
     if (!nextMisspellingData)
         return done();
 
-    debug("Checking for issue on '" + element.firstChild.nodeValue + "'");
+    debug(`Checking for issue on '${textToCheck}'`);
 
     shouldBecomeDifferent('internals.markerRangeForNode(element.firstChild, nextMisspellingData.marker, 0)', "null", function() {
         range = internals.markerRangeForNode(element.firstChild, nextMisspellingData.marker, 0);
@@ -92,8 +118,16 @@
 }
 
 var oppositeMarker;
-function verifyUnexpectedMarkers(e, misspellings)
+function verifyUnexpectedMarkers(e, misspellings, overrideSpellCheckingResults)
 {
+    const textToCheck = e.firstChild.nodeValue;
+    const spellCheckerResults = { };
+    if (overrideSpellCheckingResults)
+        spellCheckerResults[textToCheck] = overrideSpellCheckingResults;
+    testRunner.setSpellCheckerResults(spellCheckerResults);
+
+    typeText(e, textToCheck);
+
     element = e;
     nextMisspellingData = misspellings.shift();
 
@@ -102,7 +136,7 @@
     else if (nextMisspellingData.marker == 'spelling')
         oppositeMarker = 'grammar';
 
-    debug("Checking for no other issues on '" + element.firstChild.nodeValue + "'");
+    debug(`Checking for no other issues on '${textToCheck}'`);
 
     shouldBecomeEqual('internals.markerCountForNode(element.firstChild, oppositeMarker)', '0', function() {
         debug("");

Modified: trunk/LayoutTests/editing/spelling/text-replacement-after-typing-to-word.html (233438 => 233439)


--- trunk/LayoutTests/editing/spelling/text-replacement-after-typing-to-word.html	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/LayoutTests/editing/spelling/text-replacement-after-typing-to-word.html	2018-07-02 22:04:51 UTC (rev 233439)
@@ -17,19 +17,23 @@
         internals.settings.setAsynchronousSpellCheckingEnabled(false);
         internals.setAutomaticTextReplacementEnabled(true);
         internals.setAutomaticSpellingCorrectionEnabled(true);
-        testRunner.setSpellCheckerTextReplacements({
-            "YT?": {
-                "replacement": "You there?",
-                "type": "replacement",
-                "from": 0,
-                "to": 3
-            },
-            "YT?\n": {
-                "replacement": "You there?",
-                "type": "replacement",
-                "from": 0,
-                "to": 3
-            }
+        testRunner.setSpellCheckerResults({
+            "YT?": [
+                {
+                    "replacement": "You there?",
+                    "type": "replacement",
+                    "from": 0,
+                    "to": 3
+                }
+            ],
+            "YT?\n": [
+                {
+                    "replacement": "You there?",
+                    "type": "replacement",
+                    "from": 0,
+                    "to": 3
+                }
+            ]
         });
     }
 

Modified: trunk/Tools/ChangeLog (233438 => 233439)


--- trunk/Tools/ChangeLog	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/Tools/ChangeLog	2018-07-02 22:04:51 UTC (rev 233439)
@@ -1,3 +1,51 @@
+2018-07-02  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [WK1] editing/spelling/markers.html is failing on recent builds of macOS Mojave
+        https://bugs.webkit.org/show_bug.cgi?id=187253
+
+        Reviewed by Tim Horton.
+
+        Enhances and refactors LayoutTestSpellChecker, which was introduced in r233412.
+
+        * DumpRenderTree/TestRunner.cpp:
+        (setSpellCheckerResultsCallback):
+
+        Rename setSpellCheckerTextReplacements to setSpellCheckerResults.
+
+        (TestRunner::staticFunctions):
+        (setSpellCheckerTextReplacementsCallback): Deleted.
+        * DumpRenderTree/TestRunner.h:
+        * DumpRenderTree/mac/TestRunnerMac.mm:
+        (TestRunner::setSpellCheckerResults):
+        (TestRunner::setSpellCheckerTextReplacements): Deleted.
+        * DumpRenderTree/win/TestRunnerWin.cpp:
+        (TestRunner::setSpellCheckerResults):
+        (TestRunner::setSpellCheckerTextReplacements): Deleted.
+        * TestRunnerShared/cocoa/LayoutTestSpellChecker.h:
+        * TestRunnerShared/cocoa/LayoutTestSpellChecker.mm:
+        (-[LayoutTestTextCheckingResult initWithType:range:replacement:details:]):
+        (-[LayoutTestTextCheckingResult grammarDetails]):
+        (-[LayoutTestSpellChecker reset]):
+        (-[LayoutTestSpellChecker results]):
+        (-[LayoutTestSpellChecker setResults:]):
+        (-[LayoutTestSpellChecker setResultsFromJSObject:inContext:]):
+
+        Add support for passing in a list of grammar correction detail ranges. Necessary for simulating grammar errors.
+
+        (-[LayoutTestSpellChecker checkString:range:types:options:inSpellDocumentWithTag:orthography:wordCount:]):
+
+        Tweaked to always call the superclass method. This ensures that we set the `orthography` and `wordCount`
+        outpointers if applicable.
+
+        (-[LayoutTestSpellChecker requestCheckingOfString:range:types:options:inSpellDocumentWithTag:completionHandler:]):
+
+        Added support for simulating asynchronous spell checking.
+
+        (-[LayoutTestTextCheckingResult initWithType:range:replacement:]): Deleted.
+        (-[LayoutTestSpellChecker replacements]): Deleted.
+        (-[LayoutTestSpellChecker setReplacements:]): Deleted.
+        (-[LayoutTestSpellChecker setReplacementsFromJSObject:inContext:]): Deleted.
+
 2018-07-02  Brady Eidson  <beid...@apple.com>
 
         Another unreviewed followup to:

Modified: trunk/Tools/DumpRenderTree/TestRunner.cpp (233438 => 233439)


--- trunk/Tools/DumpRenderTree/TestRunner.cpp	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/Tools/DumpRenderTree/TestRunner.cpp	2018-07-02 22:04:51 UTC (rev 233439)
@@ -1788,13 +1788,13 @@
     return JSValueMakeUndefined(context);
 }
 
-static JSValueRef setSpellCheckerTextReplacementsCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+static JSValueRef setSpellCheckerResultsCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
 {
     if (argumentCount < 1)
         return JSValueMakeUndefined(context);
 
     auto* runner = static_cast<TestRunner*>(JSObjectGetPrivate(thisObject));
-    runner->setSpellCheckerTextReplacements(context, JSValueToObject(context, arguments[0], nullptr));
+    runner->setSpellCheckerResults(context, JSValueToObject(context, arguments[0], nullptr));
     return JSValueMakeUndefined(context);
 }
 
@@ -2265,7 +2265,7 @@
         { "runUIScript", runUIScriptCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "imageCountInGeneralPasteboard", imageCountInGeneralPasteboardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setSpellCheckerLoggingEnabled", setSpellCheckerLoggingEnabledCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
-        { "setSpellCheckerTextReplacements", setSpellCheckerTextReplacementsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+        { "setSpellCheckerResults", setSpellCheckerResultsCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "setOpenPanelFiles", setOpenPanelFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "forceImmediateCompletion", forceImmediateCompletionCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { 0, 0, 0 }

Modified: trunk/Tools/DumpRenderTree/TestRunner.h (233438 => 233439)


--- trunk/Tools/DumpRenderTree/TestRunner.h	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/Tools/DumpRenderTree/TestRunner.h	2018-07-02 22:04:51 UTC (rev 233439)
@@ -376,7 +376,7 @@
     bool dumpJSConsoleLogInStdErr() const { return m_dumpJSConsoleLogInStdErr; }
 
     void setSpellCheckerLoggingEnabled(bool);
-    void setSpellCheckerTextReplacements(JSContextRef, JSObjectRef replacements);
+    void setSpellCheckerResults(JSContextRef, JSObjectRef results);
 
     const std::vector<std::string>& openPanelFiles() const { return m_openPanelFiles; }
     void setOpenPanelFiles(JSContextRef, JSValueRef);

Modified: trunk/Tools/DumpRenderTree/mac/TestRunnerMac.mm (233438 => 233439)


--- trunk/Tools/DumpRenderTree/mac/TestRunnerMac.mm	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/Tools/DumpRenderTree/mac/TestRunnerMac.mm	2018-07-02 22:04:51 UTC (rev 233439)
@@ -219,12 +219,12 @@
 #endif
 }
 
-void TestRunner::setSpellCheckerTextReplacements(JSContextRef context, JSObjectRef replacements)
+void TestRunner::setSpellCheckerResults(JSContextRef context, JSObjectRef results)
 {
 #if PLATFORM(MAC)
-    [[LayoutTestSpellChecker checker] setReplacementsFromJSObject:replacements inContext:context];
+    [[LayoutTestSpellChecker checker] setResultsFromJSObject:results inContext:context];
 #else
-    UNUSED_PARAM(replacements);
+    UNUSED_PARAM(results);
     UNUSED_PARAM(context);
 #endif
 }

Modified: trunk/Tools/DumpRenderTree/win/TestRunnerWin.cpp (233438 => 233439)


--- trunk/Tools/DumpRenderTree/win/TestRunnerWin.cpp	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/Tools/DumpRenderTree/win/TestRunnerWin.cpp	2018-07-02 22:04:51 UTC (rev 233439)
@@ -1407,7 +1407,7 @@
     fprintf(testResult, "ERROR: TestRunner::setSpellCheckerLoggingEnabled() not implemented\n");
 }
 
-void TestRunner::setSpellCheckerTextReplacements(JSContextRef, JSObjectRef)
+void TestRunner::setSpellCheckerResults(JSContextRef, JSObjectRef)
 {
-    fprintf(testResult, "ERROR: TestRunner::setSpellCheckerTextReplacements() not implemented\n");
+    fprintf(testResult, "ERROR: TestRunner::setSpellCheckerResults() not implemented\n");
 }

Modified: trunk/Tools/TestRunnerShared/cocoa/LayoutTestSpellChecker.h (233438 => 233439)


--- trunk/Tools/TestRunnerShared/cocoa/LayoutTestSpellChecker.h	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/Tools/TestRunnerShared/cocoa/LayoutTestSpellChecker.h	2018-07-02 22:04:51 UTC (rev 233439)
@@ -34,9 +34,11 @@
 
 @class LayoutTestTextCheckingResult;
 
+using TextCheckingResultsDictionary = NSDictionary<NSString *, NSArray<LayoutTestTextCheckingResult *> *>;
+
 @interface LayoutTestSpellChecker : NSSpellChecker {
 @private
-    RetainPtr<NSDictionary<NSString *, LayoutTestTextCheckingResult *>> _replacements;
+    RetainPtr<TextCheckingResultsDictionary> _results;
     BOOL _spellCheckerLoggingEnabled;
 }
 
@@ -43,8 +45,8 @@
 + (instancetype)checker;
 + (void)uninstallAndReset;
 
-- (void)setReplacementsFromJSObject:(JSObjectRef)replacements inContext:(JSContextRef)context;
-@property (nonatomic, copy) NSDictionary<NSString *, LayoutTestTextCheckingResult *> *replacements;
+- (void)setResultsFromJSObject:(JSObjectRef)resultsObject inContext:(JSContextRef)context;
+@property (nonatomic, copy) TextCheckingResultsDictionary *results;
 @property (nonatomic) BOOL spellCheckerLoggingEnabled;
 @end
 

Modified: trunk/Tools/TestRunnerShared/cocoa/LayoutTestSpellChecker.mm (233438 => 233439)


--- trunk/Tools/TestRunnerShared/cocoa/LayoutTestSpellChecker.mm	2018-07-02 22:02:30 UTC (rev 233438)
+++ trunk/Tools/TestRunnerShared/cocoa/LayoutTestSpellChecker.mm	2018-07-02 22:04:51 UTC (rev 233439)
@@ -29,9 +29,12 @@
 #import <_javascript_Core/JSRetainPtr.h>
 #import <objc/runtime.h>
 #import <wtf/Assertions.h>
+#import <wtf/BlockPtr.h>
 
 #if PLATFORM(MAC)
 
+using TextCheckingCompletionHandler = void(^)(NSInteger, NSArray<NSTextCheckingResult *> *, NSOrthography *, NSInteger);
+
 static LayoutTestSpellChecker *globalSpellChecker = nil;
 static BOOL hasSwizzledLayoutTestSpellChecker = NO;
 static IMP globallySwizzledSharedSpellCheckerImplementation;
@@ -116,14 +119,15 @@
     RetainPtr<NSString> _replacement;
     NSTextCheckingType _type;
     NSRange _range;
+    RetainPtr<NSArray<NSDictionary *>> _details;
 }
 
-- (instancetype)initWithType:(NSTextCheckingType)type range:(NSRange)range replacement:(NSString *)replacement;
+- (instancetype)initWithType:(NSTextCheckingType)type range:(NSRange)range replacement:(NSString *)replacement details:(NSArray<NSDictionary<NSString *, id> *> *)details;
 @end
 
 @implementation LayoutTestTextCheckingResult
 
-- (instancetype)initWithType:(NSTextCheckingType)type range:(NSRange)range replacement:(NSString *)replacement
+- (instancetype)initWithType:(NSTextCheckingType)type range:(NSRange)range replacement:(NSString *)replacement details:(NSArray<NSDictionary<NSString *, id> *> *)details
 {
     if (!(self = [super init]))
         return nil;
@@ -130,11 +134,17 @@
 
     _type = type;
     _range = range;
-    _replacement = replacement;
+    _replacement = adoptNS(replacement.copy);
+    _details = adoptNS(details.copy);
 
     return self;
 }
 
+- (NSArray<NSDictionary<NSString *, id> *> *)grammarDetails
+{
+    return _details.get();
+}
+
 - (NSRange)range
 {
     return _range;
@@ -185,55 +195,80 @@
 
 - (void)reset
 {
-    self.replacements = nil;
+    self.results = nil;
     self.spellCheckerLoggingEnabled = NO;
 }
 
-- (NSDictionary<NSString *, LayoutTestTextCheckingResult *> *)replacements
+- (TextCheckingResultsDictionary *)results
 {
-    return _replacements.get();
+    return _results.get();
 }
 
-- (void)setReplacements:(NSDictionary<NSString *, LayoutTestTextCheckingResult *> *)replacements
+- (void)setResults:(TextCheckingResultsDictionary *)results
 {
-    _replacements = adoptNS(replacements.copy);
+    _results = adoptNS(results.copy);
 }
 
-- (void)setReplacementsFromJSObject:(JSObjectRef)replacements inContext:(JSContextRef)context
+- (void)setResultsFromJSObject:(JSObjectRef)resultsObject inContext:(JSContextRef)context
 {
     auto fromPropertyName = adopt(JSStringCreateWithUTF8CString("from"));
     auto toPropertyName = adopt(JSStringCreateWithUTF8CString("to"));
     auto typePropertyName = adopt(JSStringCreateWithUTF8CString("type"));
     auto replacementPropertyName = adopt(JSStringCreateWithUTF8CString("replacement"));
-    auto properties = JSObjectCopyPropertyNames(context, replacements);
-    auto result = adoptNS([[NSMutableDictionary alloc] init]);
+    auto detailsPropertyName = adopt(JSStringCreateWithUTF8CString("details"));
+    auto results = adoptNS([[NSMutableDictionary alloc] init]);
+
+    // FIXME: Using the Objective-C API would make this logic easier to follow.
+    auto properties = JSObjectCopyPropertyNames(context, resultsObject);
     for (size_t index = 0; index < JSPropertyNameArrayGetCount(properties); ++index) {
-        JSStringRef wordToReplace = JSPropertyNameArrayGetNameAtIndex(properties, index);
-        JSObjectRef replacement = JSValueToObject(context, JSObjectGetProperty(context, replacements, wordToReplace, nullptr), nullptr);
-        long fromValue = lroundl(JSValueToNumber(context, JSObjectGetProperty(context, replacement, fromPropertyName.get(), nullptr), nullptr));
-        long toValue = lroundl(JSValueToNumber(context, JSObjectGetProperty(context, replacement, toPropertyName.get(), nullptr), nullptr));
-        auto typeValue = adopt(JSValueToStringCopy(context, JSObjectGetProperty(context, replacement, typePropertyName.get(), nullptr), nullptr));
-        auto replacementValue = JSObjectGetProperty(context, replacement, replacementPropertyName.get(), nullptr);
-        RetainPtr<CFStringRef> replacementText;
-        if (!JSValueIsUndefined(context, replacementValue)) {
-            auto replacementJSString = adopt(JSValueToStringCopy(context, replacementValue, nullptr));
-            replacementText = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, replacementJSString.get()));
+        JSStringRef textToCheck = JSPropertyNameArrayGetNameAtIndex(properties, index);
+        JSObjectRef resultsArray = JSValueToObject(context, JSObjectGetProperty(context, resultsObject, textToCheck, nullptr), nullptr);
+        auto resultsArrayPropertyNames = JSObjectCopyPropertyNames(context, resultsArray);
+        auto resultsForWord = adoptNS([[NSMutableArray alloc] init]);
+        for (size_t resultIndex = 0; resultIndex < JSPropertyNameArrayGetCount(resultsArrayPropertyNames); ++resultIndex) {
+            auto resultsObject = JSValueToObject(context, JSObjectGetPropertyAtIndex(context, resultsArray, resultIndex, nullptr), nullptr);
+            long fromValue = lroundl(JSValueToNumber(context, JSObjectGetProperty(context, resultsObject, fromPropertyName.get(), nullptr), nullptr));
+            long toValue = lroundl(JSValueToNumber(context, JSObjectGetProperty(context, resultsObject, toPropertyName.get(), nullptr), nullptr));
+            auto typeValue = adopt(JSValueToStringCopy(context, JSObjectGetProperty(context, resultsObject, typePropertyName.get(), nullptr), nullptr));
+            auto replacementValue = JSObjectGetProperty(context, resultsObject, replacementPropertyName.get(), nullptr);
+            RetainPtr<CFStringRef> replacementText;
+            if (!JSValueIsUndefined(context, replacementValue)) {
+                auto replacementJSString = adopt(JSValueToStringCopy(context, replacementValue, nullptr));
+                replacementText = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, replacementJSString.get()));
+            }
+            auto details = adoptNS([[NSMutableArray alloc] init]);
+            auto detailsValue = JSObjectGetProperty(context, resultsObject, detailsPropertyName.get(), nullptr);
+            if (!JSValueIsUndefined(context, detailsValue)) {
+                auto detailsObject = JSValueToObject(context, detailsValue, nullptr);
+                auto detailsObjectProperties = JSObjectCopyPropertyNames(context, detailsObject);
+                for (size_t detailIndex = 0; detailIndex < JSPropertyNameArrayGetCount(detailsObjectProperties); ++detailIndex) {
+                    auto detail = adoptNS([[NSMutableDictionary alloc] init]);
+                    auto detailObject = JSValueToObject(context, JSObjectGetPropertyAtIndex(context, detailsObject, detailIndex, nullptr), nullptr);
+                    long from = lroundl(JSValueToNumber(context, JSObjectGetProperty(context, detailObject, fromPropertyName.get(), nullptr), nullptr));
+                    long to = lroundl(JSValueToNumber(context, JSObjectGetProperty(context, detailObject, toPropertyName.get(), nullptr), nullptr));
+                    [detail setObject:[NSValue valueWithRange:NSMakeRange(from, to - from)] forKey:NSGrammarRange];
+                    [details addObject:detail.get()];
+                }
+                JSPropertyNameArrayRelease(detailsObjectProperties);
+            }
+            [resultsForWord addObject:[[[LayoutTestTextCheckingResult alloc] initWithType:nsTextCheckingType(WTFMove(typeValue)) range:NSMakeRange(fromValue, toValue - fromValue) replacement:(NSString *)replacementText.get() details:details.get()] autorelease]];
         }
-        auto spellCheckResult = adoptNS([[LayoutTestTextCheckingResult alloc] initWithType:nsTextCheckingType(WTFMove(typeValue)) range:NSMakeRange(fromValue, toValue - fromValue) replacement:(NSString *)replacementText.get()]);
-        auto cfWordToReplace = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, wordToReplace));
-        [result setObject:spellCheckResult.get() forKey:(NSString *)cfWordToReplace.get()];
+        auto cfTextToCheck = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, textToCheck));
+        [results setObject:resultsForWord.get() forKey:(NSString *)cfTextToCheck.get()];
+        JSPropertyNameArrayRelease(resultsArrayPropertyNames);
     }
     JSPropertyNameArrayRelease(properties);
 
-    _replacements = WTFMove(result);
+    _results = WTFMove(results);
 }
 
 - (NSArray<NSTextCheckingResult *> *)checkString:(NSString *)stringToCheck range:(NSRange)range types:(NSTextCheckingTypes)checkingTypes options:(NSDictionary<NSString *, id> *)options inSpellDocumentWithTag:(NSInteger)tag orthography:(NSOrthography **)orthography wordCount:(NSInteger *)wordCount
 {
-    if (auto *result = [_replacements objectForKey:stringToCheck])
-        return @[ result ];
+    NSArray *result = [super checkString:stringToCheck range:range types:checkingTypes options:options inSpellDocumentWithTag:tag orthography:orthography wordCount:wordCount];
+    if (auto *overrideResult = [_results objectForKey:stringToCheck])
+        return overrideResult;
 
-    return [super checkString:stringToCheck range:range types:checkingTypes options:options inSpellDocumentWithTag:tag orthography:orthography wordCount:wordCount];
+    return result;
 }
 
 - (void)recordResponse:(NSCorrectionResponse)response toCorrection:(NSString *)correction forWord:(NSString *)word language:(NSString *)language inSpellDocumentWithTag:(NSInteger)tag
@@ -244,6 +279,18 @@
     [super recordResponse:response toCorrection:correction forWord:word language:language inSpellDocumentWithTag:tag];
 }
 
+- (NSInteger)requestCheckingOfString:(NSString *)stringToCheck range:(NSRange)range types:(NSTextCheckingTypes)checkingTypes options:(NSDictionary<NSString *, id> *)options inSpellDocumentWithTag:(NSInteger)tag completionHandler:(TextCheckingCompletionHandler)completionHandler
+{
+    return [super requestCheckingOfString:stringToCheck range:range types:checkingTypes options:options inSpellDocumentWithTag:tag completionHandler:[overrideResult = retainPtr([_results objectForKey:stringToCheck]), completion = makeBlockPtr(completionHandler), stringToCheck = retainPtr(stringToCheck)] (NSInteger sequenceNumber, NSArray<NSTextCheckingResult *> *result, NSOrthography *orthography, NSInteger wordCount) {
+        if (overrideResult) {
+            completion(sequenceNumber, overrideResult.get(), orthography, wordCount);
+            return;
+        }
+
+        completion(sequenceNumber, result, orthography, wordCount);
+    }];
+}
+
 @end
 
 #endif // PLATFORM(MAC)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to