- Revision
- 293030
- Author
- [email protected]
- Date
- 2022-04-19 12:00:12 -0700 (Tue, 19 Apr 2022)
Log Message
[iOS] Text selection flickers when inserting text using dictation
https://bugs.webkit.org/show_bug.cgi?id=239467
rdar://91895659
Reviewed by Aditya Keerthi.
Source/WebCore:
Add a method to invalidate `TemporarySelectionChange` and `IgnoreSelectionChangeForScope`, which immediately
reverts `Editor::ignoreSelectionChanges()` state to `false` without triggering an appearance update, and also
ensures that the RAII object's destructor is a noop.
See WebKit/ChangeLog for more details.
* editing/Editor.cpp:
(WebCore::TemporarySelectionChange::invalidate):
(WebCore::TemporarySelectionChange::~TemporarySelectionChange):
Make the destructor robust in the case where `m_document` has been cleared out already due to invalidating the
temporary selection change.
* editing/Editor.h:
Also mark these as non-copyable and fast-allocated, while we're here. Since these are RAII objects, we never
expect them to be copied, and the `WTF_MAKE_FAST_ALLOCATED` allows us to use `WTF::makeUnique` when creating
unique pointers to these helper objects.
(WebCore::IgnoreSelectionChangeForScope::invalidate):
Source/WebKit:
Implement two new SPI methods on UIWKInteractionViewProtocol (to be added in rdar://91919121), which UIKit uses
to encapsulate text input changes while inserting final dictation results (specifically, a call to
`-replaceDictatedText:withText:`, followed by one or more calls to `-insertText:`).
During this scope, we suppress all DOM selection changes from propagating to the client (thereby causing visible
text selection updates) by creating and holding on to a `IgnoreSelectionChangeForScope` RAII object. At the end
of this scope, we then schedule one final editor state update after resetting the scope, to ensure that the
final selection state propagates to the UI process.
Test: UIWKInteractionViewProtocol.SuppressSelectionChangesDuringDictation
* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView willInsertFinalDictationResult]):
(-[WKContentView didInsertFinalDictationResult]):
(-[WKContentView _updateChangedSelection:]):
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::willInsertFinalDictationResult):
(WebKit::WebPageProxy::didInsertFinalDictationResult):
Plumb these method calls into the web process, through `WebPageProxy` and `WebPage`.
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::didCommitLoad):
If (for any reason) we're committing a load while holding on to `m_ignoreSelectionChangeScopeForDictation`,
forcibly call `Editor::setIgnoreSelectionChanges(false, RevealSelection::No)` and destroy the scoped object.
* WebProcess/WebPage/WebPage.h:
Add `m_ignoreSelectionChangeScopeForDictation`, which is set in `willInsertFinalDictationResult` and
unset in `didInsertFinalDictationResult` below.
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::replaceSelectedText):
(WebKit::WebPage::replaceDictatedText):
Deploy `IgnoreSelectionChangeForScope` here, so that if we're already ignoring selection changes due to
`m_ignoreSelectionChangeScopeForDictation`, we won't prematurely revert "ignore selection change" state (this is
because this RAII helper object consults the preexisting state of `document.editor().ignoreSelectionChanges()`
before calling into `setIgnoreSelectionChanges`, and effectively becomes a no-op if the bit was already set).
(WebKit::WebPage::willInsertFinalDictationResult):
(WebKit::WebPage::didInsertFinalDictationResult):
Tools:
Add an API test to verify that -willInsertFinalDictationResult and -didInsertFinalDictationResult correctly
suppresses intermediate editor UI updates.
* TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm:
(-[EditorStateObserver initWithWebView:]):
(-[EditorStateObserver _webView:editorStateDidChange:]):
(TestWebKitAPI::setUpEditableWebViewAndWaitForInputSession):
(TestWebKitAPI::TEST):
* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/ios/UIKitSPI.h:
Modified Paths
Diff
Modified: trunk/Source/WebCore/ChangeLog (293029 => 293030)
--- trunk/Source/WebCore/ChangeLog 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebCore/ChangeLog 2022-04-19 19:00:12 UTC (rev 293030)
@@ -1,3 +1,32 @@
+2022-04-19 Wenson Hsieh <[email protected]>
+
+ [iOS] Text selection flickers when inserting text using dictation
+ https://bugs.webkit.org/show_bug.cgi?id=239467
+ rdar://91895659
+
+ Reviewed by Aditya Keerthi.
+
+ Add a method to invalidate `TemporarySelectionChange` and `IgnoreSelectionChangeForScope`, which immediately
+ reverts `Editor::ignoreSelectionChanges()` state to `false` without triggering an appearance update, and also
+ ensures that the RAII object's destructor is a noop.
+
+ See WebKit/ChangeLog for more details.
+
+ * editing/Editor.cpp:
+ (WebCore::TemporarySelectionChange::invalidate):
+ (WebCore::TemporarySelectionChange::~TemporarySelectionChange):
+
+ Make the destructor robust in the case where `m_document` has been cleared out already due to invalidating the
+ temporary selection change.
+
+ * editing/Editor.h:
+
+ Also mark these as non-copyable and fast-allocated, while we're here. Since these are RAII objects, we never
+ expect them to be copied, and the `WTF_MAKE_FAST_ALLOCATED` allows us to use `WTF::makeUnique` when creating
+ unique pointers to these helper objects.
+
+ (WebCore::IgnoreSelectionChangeForScope::invalidate):
+
2022-04-19 Tim Nguyen <[email protected]>
user-select: none shouldn't affect editability
Modified: trunk/Source/WebCore/editing/Editor.cpp (293029 => 293030)
--- trunk/Source/WebCore/editing/Editor.cpp 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebCore/editing/Editor.cpp 2022-04-19 19:00:12 UTC (rev 293030)
@@ -235,8 +235,17 @@
}
}
+void TemporarySelectionChange::invalidate()
+{
+ if (auto document = std::exchange(m_document, nullptr))
+ document->editor().setIgnoreSelectionChanges(false, Editor::RevealSelection::No);
+}
+
TemporarySelectionChange::~TemporarySelectionChange()
{
+ if (!m_document)
+ return;
+
if (m_selectionToRestore)
setSelection(m_selectionToRestore.value(), IsTemporarySelection::No);
Modified: trunk/Source/WebCore/editing/Editor.h (293029 => 293030)
--- trunk/Source/WebCore/editing/Editor.h 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebCore/editing/Editor.h 2022-04-19 19:00:12 UTC (rev 293030)
@@ -128,10 +128,13 @@
};
class TemporarySelectionChange {
+ WTF_MAKE_NONCOPYABLE(TemporarySelectionChange); WTF_MAKE_FAST_ALLOCATED;
public:
WEBCORE_EXPORT TemporarySelectionChange(Document&, std::optional<VisibleSelection> = std::nullopt, OptionSet<TemporarySelectionOption> = { });
WEBCORE_EXPORT ~TemporarySelectionChange();
+ WEBCORE_EXPORT void invalidate();
+
private:
enum class IsTemporarySelection { No, Yes };
@@ -147,6 +150,7 @@
};
class IgnoreSelectionChangeForScope {
+ WTF_MAKE_NONCOPYABLE(IgnoreSelectionChangeForScope); WTF_MAKE_FAST_ALLOCATED;
public:
IgnoreSelectionChangeForScope(Frame& frame)
: m_selectionChange(*frame.document(), std::nullopt, TemporarySelectionOption::IgnoreSelectionChanges)
@@ -153,6 +157,8 @@
{
}
+ void invalidate() { m_selectionChange.invalidate(); }
+
~IgnoreSelectionChangeForScope() = default;
private:
Modified: trunk/Source/WebKit/ChangeLog (293029 => 293030)
--- trunk/Source/WebKit/ChangeLog 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/ChangeLog 2022-04-19 19:00:12 UTC (rev 293030)
@@ -1,3 +1,57 @@
+2022-04-19 Wenson Hsieh <[email protected]>
+
+ [iOS] Text selection flickers when inserting text using dictation
+ https://bugs.webkit.org/show_bug.cgi?id=239467
+ rdar://91895659
+
+ Reviewed by Aditya Keerthi.
+
+ Implement two new SPI methods on UIWKInteractionViewProtocol (to be added in rdar://91919121), which UIKit uses
+ to encapsulate text input changes while inserting final dictation results (specifically, a call to
+ `-replaceDictatedText:withText:`, followed by one or more calls to `-insertText:`).
+
+ During this scope, we suppress all DOM selection changes from propagating to the client (thereby causing visible
+ text selection updates) by creating and holding on to a `IgnoreSelectionChangeForScope` RAII object. At the end
+ of this scope, we then schedule one final editor state update after resetting the scope, to ensure that the
+ final selection state propagates to the UI process.
+
+ Test: UIWKInteractionViewProtocol.SuppressSelectionChangesDuringDictation
+
+ * UIProcess/WebPageProxy.h:
+ * UIProcess/ios/WKContentViewInteraction.mm:
+ (-[WKContentView willInsertFinalDictationResult]):
+ (-[WKContentView didInsertFinalDictationResult]):
+ (-[WKContentView _updateChangedSelection:]):
+ * UIProcess/ios/WebPageProxyIOS.mm:
+ (WebKit::WebPageProxy::willInsertFinalDictationResult):
+ (WebKit::WebPageProxy::didInsertFinalDictationResult):
+
+ Plumb these method calls into the web process, through `WebPageProxy` and `WebPage`.
+
+ * WebProcess/WebPage/WebPage.cpp:
+ (WebKit::WebPage::didCommitLoad):
+
+ If (for any reason) we're committing a load while holding on to `m_ignoreSelectionChangeScopeForDictation`,
+ forcibly call `Editor::setIgnoreSelectionChanges(false, RevealSelection::No)` and destroy the scoped object.
+
+ * WebProcess/WebPage/WebPage.h:
+
+ Add `m_ignoreSelectionChangeScopeForDictation`, which is set in `willInsertFinalDictationResult` and
+ unset in `didInsertFinalDictationResult` below.
+
+ * WebProcess/WebPage/WebPage.messages.in:
+ * WebProcess/WebPage/ios/WebPageIOS.mm:
+ (WebKit::WebPage::replaceSelectedText):
+ (WebKit::WebPage::replaceDictatedText):
+
+ Deploy `IgnoreSelectionChangeForScope` here, so that if we're already ignoring selection changes due to
+ `m_ignoreSelectionChangeScopeForDictation`, we won't prematurely revert "ignore selection change" state (this is
+ because this RAII helper object consults the preexisting state of `document.editor().ignoreSelectionChanges()`
+ before calling into `setIgnoreSelectionChanges`, and effectively becomes a no-op if the bit was already set).
+
+ (WebKit::WebPage::willInsertFinalDictationResult):
+ (WebKit::WebPage::didInsertFinalDictationResult):
+
2022-04-19 Simon Fraser <[email protected]>
Use ProcessTerminationReason::Unresponsive for unresponsive network processes
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (293029 => 293030)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.h 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h 2022-04-19 19:00:12 UTC (rev 293030)
@@ -883,6 +883,8 @@
void requestRVItemInCurrentSelectedRange(CompletionHandler<void(const RevealItem&)>&&);
void prepareSelectionForContextMenuWithLocationInView(const WebCore::IntPoint, CompletionHandler<void(bool, const RevealItem&)>&&);
#endif
+ void willInsertFinalDictationResult();
+ void didInsertFinalDictationResult();
void replaceDictatedText(const String& oldText, const String& newText);
void replaceSelectedText(const String& oldText, const String& newText);
void didReceivePositionInformation(const InteractionInformationAtPosition&);
Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (293029 => 293030)
--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2022-04-19 19:00:12 UTC (rev 293030)
@@ -4928,6 +4928,16 @@
return _autocorrectionData.textLastRect;
}
+- (void)willInsertFinalDictationResult
+{
+ _page->willInsertFinalDictationResult();
+}
+
+- (void)didInsertFinalDictationResult
+{
+ _page->didInsertFinalDictationResult();
+}
+
- (void)replaceDictatedText:(NSString*)oldText withText:(NSString *)newText
{
_autocorrectionContextNeedsUpdate = YES;
Modified: trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm (293029 => 293030)
--- trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm 2022-04-19 19:00:12 UTC (rev 293030)
@@ -392,6 +392,16 @@
sendWithAsyncReply(Messages::WebPage::UpdateSelectionWithTouches(point, touches, baseIsStart), WTFMove(callback));
}
+
+void WebPageProxy::willInsertFinalDictationResult()
+{
+ m_process->send(Messages::WebPage::WillInsertFinalDictationResult(), m_webPageID);
+}
+
+void WebPageProxy::didInsertFinalDictationResult()
+{
+ m_process->send(Messages::WebPage::DidInsertFinalDictationResult(), m_webPageID);
+}
void WebPageProxy::replaceDictatedText(const String& oldText, const String& newText)
{
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (293029 => 293030)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2022-04-19 19:00:12 UTC (rev 293030)
@@ -6685,6 +6685,8 @@
m_didUpdateRenderingAfterCommittingLoad = false;
#if PLATFORM(IOS_FAMILY)
+ if (auto scope = std::exchange(m_ignoreSelectionChangeScopeForDictation, nullptr))
+ scope->invalidate();
m_sendAutocorrectionContextAfterFocusingElement = false;
m_hasReceivedVisibleContentRectsAfterDidCommitLoad = false;
m_hasRestoredExposedContentRectAfterDidCommitLoad = false;
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (293029 => 293030)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2022-04-19 19:00:12 UTC (rev 293030)
@@ -201,6 +201,7 @@
class HTMLMenuItemElement;
class HTMLPlugInElement;
class HTMLVideoElement;
+class IgnoreSelectionChangeForScope;
class IntPoint;
class KeyboardEvent;
class MediaPlaybackTargetContext;
@@ -805,6 +806,8 @@
void requestRVItemInCurrentSelectedRange(CompletionHandler<void(const WebKit::RevealItem&)>&&);
void prepareSelectionForContextMenuWithLocationInView(const WebCore::IntPoint, CompletionHandler<void(bool, const RevealItem&)>&&);
#endif
+ void willInsertFinalDictationResult();
+ void didInsertFinalDictationResult();
void replaceDictatedText(const String& oldText, const String& newText);
void replaceSelectedText(const String& oldText, const String& newText);
void requestAutocorrectionData(const String& textForAutocorrection, CompletionHandler<void(WebAutocorrectionData)>&& reply);
@@ -2316,6 +2319,7 @@
CompletionHandler<void(InteractionInformationAtPosition&&)> m_pendingSynchronousPositionInformationReply;
std::optional<std::pair<TransactionID, double>> m_lastLayerTreeTransactionIdAndPageScaleBeforeScalingPage;
bool m_sendAutocorrectionContextAfterFocusingElement { false };
+ std::unique_ptr<WebCore::IgnoreSelectionChangeForScope> m_ignoreSelectionChangeScopeForDictation;
#endif // PLATFORM(IOS_FAMILY)
WebCore::Timer m_layerVolatilityTimer;
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in (293029 => 293030)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2022-04-19 19:00:12 UTC (rev 293030)
@@ -83,6 +83,8 @@
RequestRVItemInCurrentSelectedRange() -> (WebKit::RevealItem item)
PrepareSelectionForContextMenuWithLocationInView(WebCore::IntPoint point) -> (bool shouldShowMenu, WebKit::RevealItem item)
#endif
+ WillInsertFinalDictationResult()
+ DidInsertFinalDictationResult()
ReplaceDictatedText(String oldText, String newText)
ReplaceSelectedText(String oldText, String newText)
RequestAutocorrectionData(String textForAutocorrection) -> (struct WebKit::WebAutocorrectionData data)
Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (293029 => 293030)
--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2022-04-19 19:00:12 UTC (rev 293030)
@@ -2385,10 +2385,10 @@
auto wordRange = frame->selection().isCaret() ? wordRangeFromPosition(frame->selection().selection().start()) : frame->selection().selection().toNormalizedRange();
if (plainTextForContext(wordRange) != oldText)
return;
- frame->editor().setIgnoreSelectionChanges(true);
+
+ IgnoreSelectionChangeForScope ignoreSelectionChanges { frame };
frame->selection().setSelectedRange(wordRange, Affinity::Upstream, WebCore::FrameSelection::ShouldCloseTyping::Yes);
frame->editor().insertText(newText, 0);
- frame->editor().setIgnoreSelectionChanges(false);
}
void WebPage::replaceDictatedText(const String& oldText, const String& newText)
@@ -2412,12 +2412,26 @@
return;
// We don't want to notify the client that the selection has changed until we are done inserting the new text.
- frame->editor().setIgnoreSelectionChanges(true);
+ IgnoreSelectionChangeForScope ignoreSelectionChanges { frame };
frame->selection().setSelectedRange(range, Affinity::Upstream, WebCore::FrameSelection::ShouldCloseTyping::Yes);
frame->editor().insertText(newText, 0);
- frame->editor().setIgnoreSelectionChanges(false);
}
+void WebPage::willInsertFinalDictationResult()
+{
+ Ref frame = CheckedRef(m_page->focusController())->focusedOrMainFrame();
+ if (frame->selection().isNone())
+ return;
+
+ m_ignoreSelectionChangeScopeForDictation = makeUnique<IgnoreSelectionChangeForScope>(frame);
+}
+
+void WebPage::didInsertFinalDictationResult()
+{
+ m_ignoreSelectionChangeScopeForDictation = nullptr;
+ scheduleFullEditorStateUpdate();
+}
+
void WebPage::requestAutocorrectionData(const String& textForAutocorrection, CompletionHandler<void(WebAutocorrectionData)>&& reply)
{
Ref frame = CheckedRef(m_page->focusController())->focusedOrMainFrame();
Modified: trunk/Tools/ChangeLog (293029 => 293030)
--- trunk/Tools/ChangeLog 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Tools/ChangeLog 2022-04-19 19:00:12 UTC (rev 293030)
@@ -1,3 +1,22 @@
+2022-04-19 Wenson Hsieh <[email protected]>
+
+ [iOS] Text selection flickers when inserting text using dictation
+ https://bugs.webkit.org/show_bug.cgi?id=239467
+ rdar://91895659
+
+ Reviewed by Aditya Keerthi.
+
+ Add an API test to verify that -willInsertFinalDictationResult and -didInsertFinalDictationResult correctly
+ suppresses intermediate editor UI updates.
+
+ * TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm:
+ (-[EditorStateObserver initWithWebView:]):
+ (-[EditorStateObserver _webView:editorStateDidChange:]):
+ (TestWebKitAPI::setUpEditableWebViewAndWaitForInputSession):
+ (TestWebKitAPI::TEST):
+ * TestWebKitAPI/cocoa/TestWKWebView.h:
+ * TestWebKitAPI/ios/UIKitSPI.h:
+
2022-04-19 Jonathan Bedard <[email protected]>
[ews-build.webkit.org] Resolve conflicting Mixin inheritance
Modified: trunk/Tools/TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm (293029 => 293030)
--- trunk/Tools/TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Tools/TestWebKitAPI/Tests/ios/UIWKInteractionViewProtocol.mm 2022-04-19 19:00:12 UTC (rev 293030)
@@ -32,6 +32,7 @@
#import "TestWKWebView.h"
#import "UIKitSPI.h"
#import "UserInterfaceSwizzler.h"
+#import <WebKit/WKUIDelegatePrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <wtf/RetainPtr.h>
@@ -83,6 +84,32 @@
@end
+@interface EditorStateObserver : NSObject <WKUIDelegatePrivate>
+- (instancetype)initWithWebView:(WKWebView *)webView;
+@property (nonatomic, readonly) NSUInteger changeCount;
+@end
+
+@implementation EditorStateObserver {
+ __weak WKWebView *_webView;
+}
+
+- (instancetype)initWithWebView:(WKWebView *)webView
+{
+ if (!(self = [super init]))
+ return nil;
+
+ webView.UIDelegate = self;
+ _changeCount = 0;
+ return self;
+}
+
+- (void)_webView:(WKWebView *)webView editorStateDidChange:(NSDictionary *)editorState
+{
+ _changeCount++;
+}
+
+@end
+
namespace TestWebKitAPI {
TEST(UIWKInteractionViewProtocol, SelectTextWithCharacterGranularity)
@@ -180,7 +207,7 @@
EXPECT_WK_STREQ("DIV", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.tagName"]);
}
-TEST(UIWKInteractionViewProtocol, TextInteractionCanBeginInExistingSelection)
+static std::pair<RetainPtr<TestWKWebView>, RetainPtr<TestInputDelegate>> setUpEditableWebViewAndWaitForInputSession()
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 320)]);
auto inputDelegate = adoptNS([TestInputDelegate new]);
@@ -194,7 +221,12 @@
[webView synchronouslyLoadTestPageNamed:@"editable-responsive-body"];
TestWebKitAPI::Util::run(&didStartInputSession);
+ return { WTFMove(webView), WTFMove(inputDelegate) };
+}
+TEST(UIWKInteractionViewProtocol, TextInteractionCanBeginInExistingSelection)
+{
+ auto [webView, inputDelegate] = setUpEditableWebViewAndWaitForInputSession();
auto contentView = [webView textInputContentView];
BOOL allowsTextInteractionOutsideOfSelection = [contentView textInteractionGesture:UIWKGestureLoupe shouldBeginAtPoint:CGPointMake(50, 50)];
EXPECT_TRUE(allowsTextInteractionOutsideOfSelection);
@@ -206,6 +238,26 @@
EXPECT_TRUE(allowsTextInteractionInsideSelection);
}
+TEST(UIWKInteractionViewProtocol, SuppressSelectionChangesDuringDictation)
+{
+ auto [webView, inputDelegate] = setUpEditableWebViewAndWaitForInputSession();
+ auto contentView = [webView textInputContentView];
+ [contentView selectAll:nil];
+ [contentView insertText:@"Hello world"];
+ [webView waitForNextPresentationUpdate];
+
+ auto observer = adoptNS([[EditorStateObserver alloc] initWithWebView:webView.get()]);
+ [contentView willInsertFinalDictationResult];
+ [contentView replaceDictatedText:@"Hello world" withText:@""];
+ [contentView insertText:@"Foo"];
+ [contentView insertText:@" "];
+ [contentView insertText:@"Bar"];
+ [contentView didInsertFinalDictationResult];
+ [webView waitForNextPresentationUpdate];
+ EXPECT_WK_STREQ("Foo Bar", [webView contentsAsString]);
+ EXPECT_EQ(1U, [observer changeCount]);
+}
+
} // namespace TestWebKitAPI
#endif
Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h (293029 => 293030)
--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h 2022-04-19 19:00:12 UTC (rev 293030)
@@ -34,7 +34,7 @@
@protocol UITextInputInternal;
@protocol UITextInputMultiDocument;
@protocol UITextInputPrivate;
-@protocol UIWKInteractionViewProtocol;
+@protocol UIWKInteractionViewProtocol_Staging_91919121;
#endif
@interface WKWebView (AdditionalDeclarations)
@@ -50,7 +50,7 @@
@interface WKWebView (TestWebKitAPI)
#if PLATFORM(IOS_FAMILY)
-@property (nonatomic, readonly) UIView <UITextInputPrivate, UITextInputInternal, UITextInputMultiDocument, UIWKInteractionViewProtocol, UITextInputTokenizer> *textInputContentView;
+@property (nonatomic, readonly) UIView <UITextInputPrivate, UITextInputInternal, UITextInputMultiDocument, UIWKInteractionViewProtocol_Staging_91919121, UITextInputTokenizer> *textInputContentView;
- (NSArray<_WKTextInputContext *> *)synchronouslyRequestTextInputContextsInRect:(CGRect)rect;
#endif
@property (nonatomic, readonly) NSUInteger gpuToWebProcessConnectionCount;
Modified: trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h (293029 => 293030)
--- trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h 2022-04-19 18:54:09 UTC (rev 293029)
+++ trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h 2022-04-19 19:00:12 UTC (rev 293030)
@@ -224,6 +224,7 @@
- (void)updateSelectionWithExtentPoint:(CGPoint)point withBoundary:(UITextGranularity)granularity completionHandler:(void (^)(BOOL selectionEndIsMoving))completionHandler;
- (void)selectWordForReplacement;
- (BOOL)textInteractionGesture:(UIWKGestureType)gesture shouldBeginAtPoint:(CGPoint)point;
+- (void)replaceDictatedText:(NSString *)oldText withText:(NSString *)newText;
- (NSArray<NSTextAlternatives *> *)alternativesForSelectedText;
@property (nonatomic, readonly) NSString *selectedText;
@@ -350,4 +351,10 @@
+ (instancetype)sharedPolicyDecider;
@end
+@protocol UIWKInteractionViewProtocol_Staging_91919121 <UIWKInteractionViewProtocol>
+@optional
+- (void)willInsertFinalDictationResult;
+- (void)didInsertFinalDictationResult;
+@end
+
#endif // PLATFORM(IOS_FAMILY)