Title: [239931] trunk
Revision
239931
Author
wenson_hs...@apple.com
Date
2019-01-14 12:09:58 -0800 (Mon, 14 Jan 2019)

Log Message

[iOS] Expose SPI to access the current sentence boundary and selection state
https://bugs.webkit.org/show_bug.cgi?id=193398
<rdar://problem/45893108>

Reviewed by Dean Jackson.

Source/WebKit:

Expose SPI on WKWebView for internal clients to grab information about attributes at the current selection; so
far, this only includes whether the selection is a caret or a range, and whether or not the start of the
selection is at the start of a new sentence.

Test: EditorStateTests.ObserveSelectionAttributeChanges

* Shared/EditorState.cpp:
(WebKit::EditorState::PostLayoutData::encode const):
(WebKit::EditorState::PostLayoutData::decode):
* Shared/EditorState.h:

Add a new bit in EditorState on iOS to compute whether or not the start of the selection is at the start of a
new sentence. This is computed and set when sending post-layout data in `WebPageIOS.mm`.

* UIProcess/API/Cocoa/WKWebView.mm:
(selectionAttributes):
(-[WKWebView _didChangeEditorState]):
(-[WKWebView _selectionAttributes]):

Make the new SPI property support KVO by invoking `-willChangeValueForKey:` and `-didChangeValueForKey:`
whenever the selection attributes change.

* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::platformEditorState const):

Tools:

Add an API test to verify that an SPI client can observe changes in the `@"_selectionAttributes"` key path on
WKWebView, and that inserting text, deleting, and changing the selection cause selection attributes to change as
expected.

* TestWebKitAPI/EditingTestHarness.h:
* TestWebKitAPI/EditingTestHarness.mm:
(-[EditingTestHarness moveBackward]):
(-[EditingTestHarness moveForward]):
(-[EditingTestHarness moveForwardAndExpectEditorStateWith:]):

Add a couple of new helper methods on EditingTestHarness.

* TestWebKitAPI/Tests/WebKitCocoa/EditorStateTests.mm:
(-[SelectionChangeObserver initWithWebView:]):
(-[SelectionChangeObserver webView]):
(-[SelectionChangeObserver observeValueForKeyPath:ofObject:change:context:]):
(-[SelectionChangeObserver currentSelectionAttributes]):

Modified Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (239930 => 239931)


--- trunk/Source/WebKit/ChangeLog	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Source/WebKit/ChangeLog	2019-01-14 20:09:58 UTC (rev 239931)
@@ -1,3 +1,37 @@
+2019-01-14  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [iOS] Expose SPI to access the current sentence boundary and selection state
+        https://bugs.webkit.org/show_bug.cgi?id=193398
+        <rdar://problem/45893108>
+
+        Reviewed by Dean Jackson.
+
+        Expose SPI on WKWebView for internal clients to grab information about attributes at the current selection; so
+        far, this only includes whether the selection is a caret or a range, and whether or not the start of the
+        selection is at the start of a new sentence.
+
+        Test: EditorStateTests.ObserveSelectionAttributeChanges
+
+        * Shared/EditorState.cpp:
+        (WebKit::EditorState::PostLayoutData::encode const):
+        (WebKit::EditorState::PostLayoutData::decode):
+        * Shared/EditorState.h:
+
+        Add a new bit in EditorState on iOS to compute whether or not the start of the selection is at the start of a
+        new sentence. This is computed and set when sending post-layout data in `WebPageIOS.mm`.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (selectionAttributes):
+        (-[WKWebView _didChangeEditorState]):
+        (-[WKWebView _selectionAttributes]):
+
+        Make the new SPI property support KVO by invoking `-willChangeValueForKey:` and `-didChangeValueForKey:`
+        whenever the selection attributes change.
+
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::platformEditorState const):
+
 2019-01-14  Carlos Garcia Campos  <cgar...@igalia.com>
 
         Unreviewed. Update OptionsGTK.cmake and NEWS for 2.23.3 release

Modified: trunk/Source/WebKit/Shared/EditorState.cpp (239930 => 239931)


--- trunk/Source/WebKit/Shared/EditorState.cpp	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Source/WebKit/Shared/EditorState.cpp	2019-01-14 20:09:58 UTC (rev 239931)
@@ -132,6 +132,7 @@
     encoder << hasPlainText;
     encoder << elementIsTransparentOrFullyClipped;
     encoder << caretColor;
+    encoder << atStartOfSentence;
 #endif
 #if PLATFORM(MAC)
     encoder << candidateRequestStartPosition;
@@ -191,6 +192,8 @@
         return false;
     if (!decoder.decode(result.caretColor))
         return false;
+    if (!decoder.decode(result.atStartOfSentence))
+        return false;
 #endif
 #if PLATFORM(MAC)
     if (!decoder.decode(result.candidateRequestStartPosition))

Modified: trunk/Source/WebKit/Shared/EditorState.h (239930 => 239931)


--- trunk/Source/WebKit/Shared/EditorState.h	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Source/WebKit/Shared/EditorState.h	2019-01-14 20:09:58 UTC (rev 239931)
@@ -109,6 +109,7 @@
         bool hasPlainText { false };
         bool elementIsTransparentOrFullyClipped { false };
         WebCore::Color caretColor;
+        bool atStartOfSentence { false };
 #endif
 #if PLATFORM(MAC)
         uint64_t candidateRequestStartPosition { 0 };

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (239930 => 239931)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-01-14 20:09:58 UTC (rev 239931)
@@ -377,6 +377,7 @@
     std::unique_ptr<WebKit::WebViewImpl> _impl;
     RetainPtr<WKTextFinderClient> _textFinderClient;
 #endif
+    _WKSelectionAttributes _selectionAttributes;
     CGFloat _minimumEffectiveDeviceWidth;
 }
 
@@ -1267,17 +1268,51 @@
     };
 }
 
+static _WKSelectionAttributes selectionAttributes(const WebKit::EditorState& editorState, _WKSelectionAttributes previousAttributes)
+{
+    _WKSelectionAttributes attributes = _WKSelectionAttributeNoSelection;
+    if (editorState.selectionIsNone)
+        return attributes;
+
+    if (editorState.selectionIsRange)
+        attributes |= _WKSelectionAttributeIsRange;
+    else
+        attributes |= _WKSelectionAttributeIsCaret;
+
+    if (!editorState.isMissingPostLayoutData) {
+#if PLATFORM(IOS_FAMILY)
+        if (editorState.postLayoutData().atStartOfSentence)
+            attributes |= _WKSelectionAttributeAtStartOfSentence;
+#endif
+    } else if (previousAttributes & _WKSelectionAttributeAtStartOfSentence)
+        attributes |= _WKSelectionAttributeAtStartOfSentence;
+
+    return attributes;
+}
+
 - (void)_didChangeEditorState
 {
-    id <WKUIDelegatePrivate> uiDelegate = (id <WKUIDelegatePrivate>)self.UIDelegate;
+    auto newSelectionAttributes = selectionAttributes(_page->editorState(), _selectionAttributes);
+    if (_selectionAttributes != newSelectionAttributes) {
+        NSString *selectionAttributesKey = NSStringFromSelector(@selector(_selectionAttributes));
+        [self willChangeValueForKey:selectionAttributesKey];
+        _selectionAttributes = newSelectionAttributes;
+        [self didChangeValueForKey:selectionAttributesKey];
+    }
 
     // FIXME: We should either rename -_webView:editorStateDidChange: to clarify that it's only intended for use when testing,
     // or remove it entirely and use -_webView:didChangeFontAttributes: instead once text alignment is supported in the set of
     // font attributes.
+    id <WKUIDelegatePrivate> uiDelegate = (id <WKUIDelegatePrivate>)self.UIDelegate;
     if ([uiDelegate respondsToSelector:@selector(_webView:editorStateDidChange:)])
         [uiDelegate _webView:self editorStateDidChange:dictionaryRepresentationForEditorState(_page->editorState())];
 }
 
+- (_WKSelectionAttributes)_selectionAttributes
+{
+    return _selectionAttributes;
+}
+
 - (void)_showSafeBrowsingWarning:(const WebKit::SafeBrowsingWarning&)warning completionHandler:(CompletionHandler<void(Variant<WebKit::ContinueUnsafeLoad, URL>&&)>&&)completionHandler
 {
     _safeBrowsingWarning = adoptNS([[WKSafeBrowsingWarning alloc] initWithFrame:self.bounds safeBrowsingWarning:warning completionHandler:[weakSelf = WeakObjCPtr<WKWebView>(self), completionHandler = WTFMove(completionHandler)] (auto&& result) mutable {

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h (239930 => 239931)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h	2019-01-14 20:09:58 UTC (rev 239931)
@@ -63,6 +63,13 @@
     _WKCaptureDeviceDisplay = 1 << 2,
 } WK_API_AVAILABLE(macosx(10.13), ios(11.0));
 
+typedef NS_OPTIONS(NSUInteger, _WKSelectionAttributes) {
+    _WKSelectionAttributeNoSelection = 0,
+    _WKSelectionAttributeIsCaret = 1 << 0,
+    _WKSelectionAttributeIsRange = 1 << 1,
+    _WKSelectionAttributeAtStartOfSentence = 1 << 2,
+} WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 #if TARGET_OS_IPHONE
 
 typedef NS_ENUM(NSUInteger, _WKDragInteractionPolicy) {
@@ -220,6 +227,7 @@
 - (IBAction)_takeFindStringFromSelection:(id)sender WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @property (class, nonatomic, copy, setter=_setStringForFind:) NSString *_stringForFind WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+@property (nonatomic, readonly) _WKSelectionAttributes _selectionAttributes WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 #if TARGET_OS_IPHONE
 

Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (239930 => 239931)


--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-01-14 20:09:58 UTC (rev 239931)
@@ -240,6 +240,7 @@
         // FIXME: We should disallow replace when the string contains only CJ characters.
         postLayoutData.isReplaceAllowed = result.isContentEditable && !result.isInPasswordField && !selectedText.isAllSpecialCharacters<isHTMLSpace>();
     }
+    postLayoutData.atStartOfSentence = frame.selection().selectionAtSentenceStart();
     postLayoutData.insideFixedPosition = startNodeIsInsideFixedPosition || endNodeIsInsideFixedPosition;
     if (!selection.isNone()) {
         if (m_focusedElement && m_focusedElement->renderer()) {

Modified: trunk/Tools/ChangeLog (239930 => 239931)


--- trunk/Tools/ChangeLog	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Tools/ChangeLog	2019-01-14 20:09:58 UTC (rev 239931)
@@ -1,3 +1,29 @@
+2019-01-14  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [iOS] Expose SPI to access the current sentence boundary and selection state
+        https://bugs.webkit.org/show_bug.cgi?id=193398
+        <rdar://problem/45893108>
+
+        Reviewed by Dean Jackson.
+
+        Add an API test to verify that an SPI client can observe changes in the `@"_selectionAttributes"` key path on
+        WKWebView, and that inserting text, deleting, and changing the selection cause selection attributes to change as
+        expected.
+
+        * TestWebKitAPI/EditingTestHarness.h:
+        * TestWebKitAPI/EditingTestHarness.mm:
+        (-[EditingTestHarness moveBackward]):
+        (-[EditingTestHarness moveForward]):
+        (-[EditingTestHarness moveForwardAndExpectEditorStateWith:]):
+
+        Add a couple of new helper methods on EditingTestHarness.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/EditorStateTests.mm:
+        (-[SelectionChangeObserver initWithWebView:]):
+        (-[SelectionChangeObserver webView]):
+        (-[SelectionChangeObserver observeValueForKeyPath:ofObject:change:context:]):
+        (-[SelectionChangeObserver currentSelectionAttributes]):
+
 2019-01-14  Zalan Bujtas  <za...@apple.com>
 
         [LFC][BFC] Add basic box-sizing support.

Modified: trunk/Tools/TestWebKitAPI/EditingTestHarness.h (239930 => 239931)


--- trunk/Tools/TestWebKitAPI/EditingTestHarness.h	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Tools/TestWebKitAPI/EditingTestHarness.h	2019-01-14 20:09:58 UTC (rev 239931)
@@ -46,6 +46,8 @@
 - (void)insertHTML:(NSString *)html;
 - (void)selectAll;
 - (void)deleteBackwards;
+- (void)moveBackward;
+- (void)moveForward;
 - (void)insertParagraphAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
 - (void)insertText:(NSString *)text andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
 - (void)insertHTML:(NSString *)html andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;

Modified: trunk/Tools/TestWebKitAPI/EditingTestHarness.mm (239930 => 239931)


--- trunk/Tools/TestWebKitAPI/EditingTestHarness.mm	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Tools/TestWebKitAPI/EditingTestHarness.mm	2019-01-14 20:09:58 UTC (rev 239931)
@@ -92,6 +92,16 @@
     [self deleteBackwardAndExpectEditorStateWith:nil];
 }
 
+- (void)moveBackward
+{
+    [self moveBackwardAndExpectEditorStateWith:nil];
+}
+
+- (void)moveForward
+{
+    [self moveForwardAndExpectEditorStateWith:nil];
+}
+
 - (void)insertText:(NSString *)text andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
 {
     [self _execCommand:@"InsertText" argument:text expectEntries:entries];
@@ -117,6 +127,11 @@
     [self _execCommand:@"MoveWordBackward" argument:nil expectEntries:entries];
 }
 
+- (void)moveForwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
+{
+    [self _execCommand:@"MoveForward" argument:nil expectEntries:entries];
+}
+
 - (void)toggleBold
 {
     [self _execCommand:@"ToggleBold" argument:nil expectEntries:nil];

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/EditorStateTests.mm (239930 => 239931)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/EditorStateTests.mm	2019-01-14 19:21:50 UTC (rev 239930)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/EditorStateTests.mm	2019-01-14 20:09:58 UTC (rev 239931)
@@ -31,6 +31,7 @@
 #import "PlatformUtilities.h"
 #import "TestWKWebView.h"
 #import <WebKit/WKWebViewPrivate.h>
+#import <wtf/Vector.h>
 
 #if PLATFORM(IOS_FAMILY)
 #import "UIKitSPI.h"
@@ -37,6 +38,52 @@
 #import <UIKit/UIKit.h>
 #endif
 
+static void* const SelectionAttributesObservationContext = (void*)&SelectionAttributesObservationContext;
+
+@interface SelectionChangeObserver : NSObject
+- (instancetype)initWithWebView:(TestWKWebView *)webView;
+@property (nonatomic, readonly) TestWKWebView *webView;
+@property (nonatomic, readonly) _WKSelectionAttributes currentSelectionAttributes;
+@end
+
+@implementation SelectionChangeObserver {
+    RetainPtr<TestWKWebView> _webView;
+    Vector<_WKSelectionAttributes> _observedSelectionAttributes;
+}
+
+- (instancetype)initWithWebView:(TestWKWebView *)webView
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _webView = webView;
+    [_webView addObserver:self forKeyPath:@"_selectionAttributes" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:SelectionAttributesObservationContext];
+    return self;
+}
+
+- (TestWKWebView *)webView
+{
+    return _webView.get();
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
+{
+    if (context == SelectionAttributesObservationContext) {
+        if (!_observedSelectionAttributes.isEmpty())
+            EXPECT_EQ(_observedSelectionAttributes.last(), [change[NSKeyValueChangeOldKey] unsignedIntValue]);
+        _observedSelectionAttributes.append([change[NSKeyValueChangeNewKey] unsignedIntValue]);
+        return;
+    }
+    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+}
+
+- (_WKSelectionAttributes)currentSelectionAttributes
+{
+    return _observedSelectionAttributes.isEmpty() ? _WKSelectionAttributeNoSelection : _observedSelectionAttributes.last();
+}
+
+@end
+
 namespace TestWebKitAPI {
 
 static RetainPtr<EditingTestHarness> setUpEditorStateTestHarness()
@@ -312,8 +359,48 @@
     auto cgRedColor = adoptCF(CGColorCreateCopyByMatchingToColorSpace(colorSpace.get(), kCGRenderingIntentDefault, redColor.CGColor, NULL));
     EXPECT_TRUE(CGColorEqualToColor(cgInsertionPointColor.get(), cgRedColor.get()));
 }
-#endif
 
+TEST(EditorStateTests, ObserveSelectionAttributeChanges)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    auto editor = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
+    [webView _setEditable:YES];
+    [webView synchronouslyLoadHTMLString:@"<body></body>"];
+
+    auto observer = adoptNS([[SelectionChangeObserver alloc] initWithWebView:webView.get()]);
+
+    [webView evaluateJavaScript:@"document.body.focus()" completionHandler:nil];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
+
+    [editor insertText:@"Hello"];
+    EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
+
+    [editor insertText:@"."];
+    EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
+
+    [editor moveBackward];
+    EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
+
+    [editor moveForward];
+    EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
+
+    [editor deleteBackwards];
+    EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
+
+    [editor insertParagraph];
+    EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
+
+    [editor selectAll];
+    EXPECT_EQ(_WKSelectionAttributeIsRange | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
+
+    [webView evaluateJavaScript:@"getSelection().removeAllRanges()" completionHandler:nil];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_EQ(_WKSelectionAttributeNoSelection, [observer currentSelectionAttributes]);
+}
+
+#endif // PLATFORM(IOS_FAMILY)
+
 } // namespace TestWebKitAPI
 
 #endif // WK_API_ENABLED
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to