Skip to content
This repository was archived by the owner on Jun 24, 2022. It is now read-only.

Commit 5d60e68

Browse files
[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]): git-svn-id: http://svn.webkit.org/repository/webkit/trunk@239931 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent 1551015 commit 5d60e68

File tree

10 files changed

+214
-2
lines changed

10 files changed

+214
-2
lines changed

Source/WebKit/ChangeLog

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,37 @@
1+
2019-01-14 Wenson Hsieh <[email protected]>
2+
3+
[iOS] Expose SPI to access the current sentence boundary and selection state
4+
https://bugs.webkit.org/show_bug.cgi?id=193398
5+
<rdar://problem/45893108>
6+
7+
Reviewed by Dean Jackson.
8+
9+
Expose SPI on WKWebView for internal clients to grab information about attributes at the current selection; so
10+
far, this only includes whether the selection is a caret or a range, and whether or not the start of the
11+
selection is at the start of a new sentence.
12+
13+
Test: EditorStateTests.ObserveSelectionAttributeChanges
14+
15+
* Shared/EditorState.cpp:
16+
(WebKit::EditorState::PostLayoutData::encode const):
17+
(WebKit::EditorState::PostLayoutData::decode):
18+
* Shared/EditorState.h:
19+
20+
Add a new bit in EditorState on iOS to compute whether or not the start of the selection is at the start of a
21+
new sentence. This is computed and set when sending post-layout data in `WebPageIOS.mm`.
22+
23+
* UIProcess/API/Cocoa/WKWebView.mm:
24+
(selectionAttributes):
25+
(-[WKWebView _didChangeEditorState]):
26+
(-[WKWebView _selectionAttributes]):
27+
28+
Make the new SPI property support KVO by invoking `-willChangeValueForKey:` and `-didChangeValueForKey:`
29+
whenever the selection attributes change.
30+
31+
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
32+
* WebProcess/WebPage/ios/WebPageIOS.mm:
33+
(WebKit::WebPage::platformEditorState const):
34+
135
2019-01-14 Carlos Garcia Campos <[email protected]>
236

337
Unreviewed. Update OptionsGTK.cmake and NEWS for 2.23.3 release

Source/WebKit/Shared/EditorState.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ void EditorState::PostLayoutData::encode(IPC::Encoder& encoder) const
132132
encoder << hasPlainText;
133133
encoder << elementIsTransparentOrFullyClipped;
134134
encoder << caretColor;
135+
encoder << atStartOfSentence;
135136
#endif
136137
#if PLATFORM(MAC)
137138
encoder << candidateRequestStartPosition;
@@ -191,6 +192,8 @@ bool EditorState::PostLayoutData::decode(IPC::Decoder& decoder, PostLayoutData&
191192
return false;
192193
if (!decoder.decode(result.caretColor))
193194
return false;
195+
if (!decoder.decode(result.atStartOfSentence))
196+
return false;
194197
#endif
195198
#if PLATFORM(MAC)
196199
if (!decoder.decode(result.candidateRequestStartPosition))

Source/WebKit/Shared/EditorState.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ struct EditorState {
109109
bool hasPlainText { false };
110110
bool elementIsTransparentOrFullyClipped { false };
111111
WebCore::Color caretColor;
112+
bool atStartOfSentence { false };
112113
#endif
113114
#if PLATFORM(MAC)
114115
uint64_t candidateRequestStartPosition { 0 };

Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ @implementation WKWebView {
377377
std::unique_ptr<WebKit::WebViewImpl> _impl;
378378
RetainPtr<WKTextFinderClient> _textFinderClient;
379379
#endif
380+
_WKSelectionAttributes _selectionAttributes;
380381
CGFloat _minimumEffectiveDeviceWidth;
381382
}
382383

@@ -1267,17 +1268,51 @@ static NSTextAlignment nsTextAlignment(WebKit::TextAlignment alignment)
12671268
};
12681269
}
12691270

1271+
static _WKSelectionAttributes selectionAttributes(const WebKit::EditorState& editorState, _WKSelectionAttributes previousAttributes)
1272+
{
1273+
_WKSelectionAttributes attributes = _WKSelectionAttributeNoSelection;
1274+
if (editorState.selectionIsNone)
1275+
return attributes;
1276+
1277+
if (editorState.selectionIsRange)
1278+
attributes |= _WKSelectionAttributeIsRange;
1279+
else
1280+
attributes |= _WKSelectionAttributeIsCaret;
1281+
1282+
if (!editorState.isMissingPostLayoutData) {
1283+
#if PLATFORM(IOS_FAMILY)
1284+
if (editorState.postLayoutData().atStartOfSentence)
1285+
attributes |= _WKSelectionAttributeAtStartOfSentence;
1286+
#endif
1287+
} else if (previousAttributes & _WKSelectionAttributeAtStartOfSentence)
1288+
attributes |= _WKSelectionAttributeAtStartOfSentence;
1289+
1290+
return attributes;
1291+
}
1292+
12701293
- (void)_didChangeEditorState
12711294
{
1272-
id <WKUIDelegatePrivate> uiDelegate = (id <WKUIDelegatePrivate>)self.UIDelegate;
1295+
auto newSelectionAttributes = selectionAttributes(_page->editorState(), _selectionAttributes);
1296+
if (_selectionAttributes != newSelectionAttributes) {
1297+
NSString *selectionAttributesKey = NSStringFromSelector(@selector(_selectionAttributes));
1298+
[self willChangeValueForKey:selectionAttributesKey];
1299+
_selectionAttributes = newSelectionAttributes;
1300+
[self didChangeValueForKey:selectionAttributesKey];
1301+
}
12731302

12741303
// FIXME: We should either rename -_webView:editorStateDidChange: to clarify that it's only intended for use when testing,
12751304
// or remove it entirely and use -_webView:didChangeFontAttributes: instead once text alignment is supported in the set of
12761305
// font attributes.
1306+
id <WKUIDelegatePrivate> uiDelegate = (id <WKUIDelegatePrivate>)self.UIDelegate;
12771307
if ([uiDelegate respondsToSelector:@selector(_webView:editorStateDidChange:)])
12781308
[uiDelegate _webView:self editorStateDidChange:dictionaryRepresentationForEditorState(_page->editorState())];
12791309
}
12801310

1311+
- (_WKSelectionAttributes)_selectionAttributes
1312+
{
1313+
return _selectionAttributes;
1314+
}
1315+
12811316
- (void)_showSafeBrowsingWarning:(const WebKit::SafeBrowsingWarning&)warning completionHandler:(CompletionHandler<void(Variant<WebKit::ContinueUnsafeLoad, URL>&&)>&&)completionHandler
12821317
{
12831318
_safeBrowsingWarning = adoptNS([[WKSafeBrowsingWarning alloc] initWithFrame:self.bounds safeBrowsingWarning:warning completionHandler:[weakSelf = WeakObjCPtr<WKWebView>(self), completionHandler = WTFMove(completionHandler)] (auto&& result) mutable {

Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ typedef NS_OPTIONS(NSUInteger, _WKCaptureDevices) {
6363
_WKCaptureDeviceDisplay = 1 << 2,
6464
} WK_API_AVAILABLE(macosx(10.13), ios(11.0));
6565

66+
typedef NS_OPTIONS(NSUInteger, _WKSelectionAttributes) {
67+
_WKSelectionAttributeNoSelection = 0,
68+
_WKSelectionAttributeIsCaret = 1 << 0,
69+
_WKSelectionAttributeIsRange = 1 << 1,
70+
_WKSelectionAttributeAtStartOfSentence = 1 << 2,
71+
} WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
72+
6673
#if TARGET_OS_IPHONE
6774

6875
typedef NS_ENUM(NSUInteger, _WKDragInteractionPolicy) {
@@ -220,6 +227,7 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
220227
- (IBAction)_takeFindStringFromSelection:(id)sender WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
221228

222229
@property (class, nonatomic, copy, setter=_setStringForFind:) NSString *_stringForFind WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
230+
@property (nonatomic, readonly) _WKSelectionAttributes _selectionAttributes WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
223231

224232
#if TARGET_OS_IPHONE
225233

Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ static void computeEditableRootHasContentAndPlainText(const VisibleSelection& se
240240
// FIXME: We should disallow replace when the string contains only CJ characters.
241241
postLayoutData.isReplaceAllowed = result.isContentEditable && !result.isInPasswordField && !selectedText.isAllSpecialCharacters<isHTMLSpace>();
242242
}
243+
postLayoutData.atStartOfSentence = frame.selection().selectionAtSentenceStart();
243244
postLayoutData.insideFixedPosition = startNodeIsInsideFixedPosition || endNodeIsInsideFixedPosition;
244245
if (!selection.isNone()) {
245246
if (m_focusedElement && m_focusedElement->renderer()) {

Tools/ChangeLog

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
2019-01-14 Wenson Hsieh <[email protected]>
2+
3+
[iOS] Expose SPI to access the current sentence boundary and selection state
4+
https://bugs.webkit.org/show_bug.cgi?id=193398
5+
<rdar://problem/45893108>
6+
7+
Reviewed by Dean Jackson.
8+
9+
Add an API test to verify that an SPI client can observe changes in the `@"_selectionAttributes"` key path on
10+
WKWebView, and that inserting text, deleting, and changing the selection cause selection attributes to change as
11+
expected.
12+
13+
* TestWebKitAPI/EditingTestHarness.h:
14+
* TestWebKitAPI/EditingTestHarness.mm:
15+
(-[EditingTestHarness moveBackward]):
16+
(-[EditingTestHarness moveForward]):
17+
(-[EditingTestHarness moveForwardAndExpectEditorStateWith:]):
18+
19+
Add a couple of new helper methods on EditingTestHarness.
20+
21+
* TestWebKitAPI/Tests/WebKitCocoa/EditorStateTests.mm:
22+
(-[SelectionChangeObserver initWithWebView:]):
23+
(-[SelectionChangeObserver webView]):
24+
(-[SelectionChangeObserver observeValueForKeyPath:ofObject:change:context:]):
25+
(-[SelectionChangeObserver currentSelectionAttributes]):
26+
127
2019-01-14 Zalan Bujtas <[email protected]>
228

329
[LFC][BFC] Add basic box-sizing support.

Tools/TestWebKitAPI/EditingTestHarness.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
- (void)insertHTML:(NSString *)html;
4747
- (void)selectAll;
4848
- (void)deleteBackwards;
49+
- (void)moveBackward;
50+
- (void)moveForward;
4951
- (void)insertParagraphAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
5052
- (void)insertText:(NSString *)text andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;
5153
- (void)insertHTML:(NSString *)html andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries;

Tools/TestWebKitAPI/EditingTestHarness.mm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ - (void)deleteBackwards
9292
[self deleteBackwardAndExpectEditorStateWith:nil];
9393
}
9494

95+
- (void)moveBackward
96+
{
97+
[self moveBackwardAndExpectEditorStateWith:nil];
98+
}
99+
100+
- (void)moveForward
101+
{
102+
[self moveForwardAndExpectEditorStateWith:nil];
103+
}
104+
95105
- (void)insertText:(NSString *)text andExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
96106
{
97107
[self _execCommand:@"InsertText" argument:text expectEntries:entries];
@@ -117,6 +127,11 @@ - (void)moveWordBackwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *
117127
[self _execCommand:@"MoveWordBackward" argument:nil expectEntries:entries];
118128
}
119129

130+
- (void)moveForwardAndExpectEditorStateWith:(NSDictionary<NSString *, id> *)entries
131+
{
132+
[self _execCommand:@"MoveForward" argument:nil expectEntries:entries];
133+
}
134+
120135
- (void)toggleBold
121136
{
122137
[self _execCommand:@"ToggleBold" argument:nil expectEntries:nil];

Tools/TestWebKitAPI/Tests/WebKitCocoa/EditorStateTests.mm

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,59 @@
3131
#import "PlatformUtilities.h"
3232
#import "TestWKWebView.h"
3333
#import <WebKit/WKWebViewPrivate.h>
34+
#import <wtf/Vector.h>
3435

3536
#if PLATFORM(IOS_FAMILY)
3637
#import "UIKitSPI.h"
3738
#import <UIKit/UIKit.h>
3839
#endif
3940

41+
static void* const SelectionAttributesObservationContext = (void*)&SelectionAttributesObservationContext;
42+
43+
@interface SelectionChangeObserver : NSObject
44+
- (instancetype)initWithWebView:(TestWKWebView *)webView;
45+
@property (nonatomic, readonly) TestWKWebView *webView;
46+
@property (nonatomic, readonly) _WKSelectionAttributes currentSelectionAttributes;
47+
@end
48+
49+
@implementation SelectionChangeObserver {
50+
RetainPtr<TestWKWebView> _webView;
51+
Vector<_WKSelectionAttributes> _observedSelectionAttributes;
52+
}
53+
54+
- (instancetype)initWithWebView:(TestWKWebView *)webView
55+
{
56+
if (!(self = [super init]))
57+
return nil;
58+
59+
_webView = webView;
60+
[_webView addObserver:self forKeyPath:@"_selectionAttributes" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:SelectionAttributesObservationContext];
61+
return self;
62+
}
63+
64+
- (TestWKWebView *)webView
65+
{
66+
return _webView.get();
67+
}
68+
69+
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
70+
{
71+
if (context == SelectionAttributesObservationContext) {
72+
if (!_observedSelectionAttributes.isEmpty())
73+
EXPECT_EQ(_observedSelectionAttributes.last(), [change[NSKeyValueChangeOldKey] unsignedIntValue]);
74+
_observedSelectionAttributes.append([change[NSKeyValueChangeNewKey] unsignedIntValue]);
75+
return;
76+
}
77+
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
78+
}
79+
80+
- (_WKSelectionAttributes)currentSelectionAttributes
81+
{
82+
return _observedSelectionAttributes.isEmpty() ? _WKSelectionAttributeNoSelection : _observedSelectionAttributes.last();
83+
}
84+
85+
@end
86+
4087
namespace TestWebKitAPI {
4188

4289
static RetainPtr<EditingTestHarness> setUpEditorStateTestHarness()
@@ -312,7 +359,47 @@ static void checkContentViewHasTextWithFailureDescription(TestWKWebView *webView
312359
auto cgRedColor = adoptCF(CGColorCreateCopyByMatchingToColorSpace(colorSpace.get(), kCGRenderingIntentDefault, redColor.CGColor, NULL));
313360
EXPECT_TRUE(CGColorEqualToColor(cgInsertionPointColor.get(), cgRedColor.get()));
314361
}
315-
#endif
362+
363+
TEST(EditorStateTests, ObserveSelectionAttributeChanges)
364+
{
365+
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
366+
auto editor = adoptNS([[EditingTestHarness alloc] initWithWebView:webView.get()]);
367+
[webView _setEditable:YES];
368+
[webView synchronouslyLoadHTMLString:@"<body></body>"];
369+
370+
auto observer = adoptNS([[SelectionChangeObserver alloc] initWithWebView:webView.get()]);
371+
372+
[webView evaluateJavaScript:@"document.body.focus()" completionHandler:nil];
373+
[webView waitForNextPresentationUpdate];
374+
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
375+
376+
[editor insertText:@"Hello"];
377+
EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
378+
379+
[editor insertText:@"."];
380+
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
381+
382+
[editor moveBackward];
383+
EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
384+
385+
[editor moveForward];
386+
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
387+
388+
[editor deleteBackwards];
389+
EXPECT_EQ(_WKSelectionAttributeIsCaret, [observer currentSelectionAttributes]);
390+
391+
[editor insertParagraph];
392+
EXPECT_EQ(_WKSelectionAttributeIsCaret | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
393+
394+
[editor selectAll];
395+
EXPECT_EQ(_WKSelectionAttributeIsRange | _WKSelectionAttributeAtStartOfSentence, [observer currentSelectionAttributes]);
396+
397+
[webView evaluateJavaScript:@"getSelection().removeAllRanges()" completionHandler:nil];
398+
[webView waitForNextPresentationUpdate];
399+
EXPECT_EQ(_WKSelectionAttributeNoSelection, [observer currentSelectionAttributes]);
400+
}
401+
402+
#endif // PLATFORM(IOS_FAMILY)
316403

317404
} // namespace TestWebKitAPI
318405

0 commit comments

Comments
 (0)