Diff
Modified: trunk/Source/WebKit/ChangeLog (258872 => 258873)
--- trunk/Source/WebKit/ChangeLog 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Source/WebKit/ChangeLog 2020-03-23 21:00:01 UTC (rev 258873)
@@ -1,3 +1,38 @@
+2020-03-23 Daniel Bates <[email protected]>
+
+ Support inserting text or dictation alternative by simulating keyboard input
+ https://bugs.webkit.org/show_bug.cgi?id=209380
+ <rdar://problem/59445102>
+
+ Reviewed by Darin Adler.
+
+ As a workaround for sites the implement their own editing system (e.g. facebook.com)
+ add a new insertion option that makes the insertion having a passing resemblance
+ of a person typing. The resemblance is achieved by dispatching DOM events with type
+ "keydown", "keyup", and "change".
+
+ * Shared/Cocoa/InsertTextOptions.cpp:
+ (IPC::ArgumentCoder<WebKit::InsertTextOptions>::encode):
+ (IPC::ArgumentCoder<WebKit::InsertTextOptions>::decode):
+ Encode and decode the new option.
+
+ * Shared/Cocoa/InsertTextOptions.h: Default the new option, shouldSimulateKeyboardInput,
+ to false to keep our current behavior.
+ * UIProcess/ios/WKContentViewInteraction.mm:
+ (-[WKContentView _shouldSimulateKeyboardInputOnTextInsertion]): Added. Returns NO when
+ building without USE(TEXT_INTERACTION_ADDITIONS) to keep the current behavior.
+
+ (-[WKContentView insertText:]):
+ (-[WKContentView insertText:alternatives:style:]):
+ Set the shouldSimulateKeyboardInput option.
+
+ * WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
+ (WebKit::WebPage::insertDictatedTextAsync):
+ * WebProcess/WebPage/WebPage.cpp:
+ (WebKit::WebPage::insertTextAsync):
+ If shouldSimulateKeyboardInput is false then do what we do now. Otherwise, dispatch a DOM event
+ of type "keydown" and perform the insertion. Then dispatch DOM events of type "keyup" and "change".
+
2020-03-23 Darin Adler <[email protected]>
Change TextIterator::rangeLength to not require a live range
Modified: trunk/Source/WebKit/Shared/Cocoa/InsertTextOptions.cpp (258872 => 258873)
--- trunk/Source/WebKit/Shared/Cocoa/InsertTextOptions.cpp 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Source/WebKit/Shared/Cocoa/InsertTextOptions.cpp 2020-03-23 21:00:01 UTC (rev 258873)
@@ -33,6 +33,7 @@
encoder << options.registerUndoGroup;
encoder << options.suppressSelectionUpdate;
encoder << options.processingUserGesture;
+ encoder << options.shouldSimulateKeyboardInput;
encoder << options.editingRangeIsRelativeTo;
}
@@ -45,6 +46,8 @@
return WTF::nullopt;
if (!decoder.decode(options.processingUserGesture))
return WTF::nullopt;
+ if (!decoder.decode(options.shouldSimulateKeyboardInput))
+ return WTF::nullopt;
if (!decoder.decode(options.editingRangeIsRelativeTo))
return WTF::nullopt;
return options;
Modified: trunk/Source/WebKit/Shared/Cocoa/InsertTextOptions.h (258872 => 258873)
--- trunk/Source/WebKit/Shared/Cocoa/InsertTextOptions.h 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Source/WebKit/Shared/Cocoa/InsertTextOptions.h 2020-03-23 21:00:01 UTC (rev 258873)
@@ -34,6 +34,7 @@
bool registerUndoGroup { false };
bool suppressSelectionUpdate { false };
bool processingUserGesture { false };
+ bool shouldSimulateKeyboardInput { false };
EditingRangeIsRelativeTo editingRangeIsRelativeTo { EditingRangeIsRelativeTo::EditableRoot };
};
Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (258872 => 258873)
--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2020-03-23 21:00:01 UTC (rev 258873)
@@ -4710,6 +4710,15 @@
_page->executeEditCommand("deleteBackward"_s);
}
+- (BOOL)_shouldSimulateKeyboardInputOnTextInsertion
+{
+#if USE(TEXT_INTERACTION_ADDITIONS)
+ return [self _shouldSimulateKeyboardInputOnTextInsertionInternal];
+#else
+ return NO;
+#endif
+}
+
// Inserts the given string, replacing any selected or marked text.
- (void)insertText:(NSString *)aStringValue
{
@@ -4717,7 +4726,7 @@
WebKit::InsertTextOptions options;
options.processingUserGesture = [keyboard respondsToSelector:@selector(isCallingInputDelegate)] && keyboard.isCallingInputDelegate;
-
+ options.shouldSimulateKeyboardInput = [self _shouldSimulateKeyboardInputOnTextInsertion];
_page->insertTextAsync(aStringValue, WebKit::EditingRange(), WTFMove(options));
}
@@ -4731,7 +4740,10 @@
BOOL isLowConfidence = style == UITextAlternativeStyleLowConfidence;
auto textAlternatives = adoptNS([[NSTextAlternatives alloc] initWithPrimaryString:aStringValue alternativeStrings:alternatives isLowConfidence:isLowConfidence]);
WebCore::TextAlternativeWithRange textAlternativeWithRange { textAlternatives.get(), NSMakeRange(0, aStringValue.length) };
- _page->insertDictatedTextAsync(aStringValue, { }, { textAlternativeWithRange }, { });
+
+ WebKit::InsertTextOptions options;
+ options.shouldSimulateKeyboardInput = [self _shouldSimulateKeyboardInputOnTextInsertion];
+ _page->insertDictatedTextAsync(aStringValue, { }, { textAlternativeWithRange }, WTFMove(options));
}
}
Modified: trunk/Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm (258872 => 258873)
--- trunk/Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm 2020-03-23 21:00:01 UTC (rev 258873)
@@ -38,6 +38,7 @@
#import <WebCore/DictionaryLookup.h>
#import <WebCore/Editor.h>
#import <WebCore/EventHandler.h>
+#import <WebCore/EventNames.h>
#import <WebCore/FocusController.h>
#import <WebCore/FrameView.h>
#import <WebCore/HTMLConverter.h>
@@ -197,8 +198,17 @@
if (options.registerUndoGroup)
send(Messages::WebPageProxy::RegisterInsertionUndoGrouping { });
+ RefPtr<Element> focusedElement = frame.document() ? frame.document()->focusedElement() : nullptr;
+ if (focusedElement && options.shouldSimulateKeyboardInput)
+ focusedElement->dispatchEvent(Event::create(eventNames().keydownEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
+
ASSERT(!frame.editor().hasComposition());
frame.editor().insertDictatedText(text, dictationAlternativeLocations, nullptr /* triggeringEvent */);
+
+ if (focusedElement && options.shouldSimulateKeyboardInput) {
+ focusedElement->dispatchEvent(Event::create(eventNames().keyupEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
+ focusedElement->dispatchEvent(Event::create(eventNames().changeEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
+ }
}
void WebPage::accessibilityTransferRemoteToken(RetainPtr<NSData> remoteToken)
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (258872 => 258873)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2020-03-23 21:00:01 UTC (rev 258873)
@@ -5343,7 +5343,11 @@
if (options.registerUndoGroup)
send(Messages::WebPageProxy::RegisterInsertionUndoGrouping());
-
+
+ RefPtr<Element> focusedElement = frame.document() ? frame.document()->focusedElement() : nullptr;
+ if (focusedElement && options.shouldSimulateKeyboardInput)
+ focusedElement->dispatchEvent(Event::create(eventNames().keydownEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
+
if (!frame.editor().hasComposition()) {
// An insertText: might be handled by other responders in the chain if we don't handle it.
// One example is space bar that results in scrolling down the page.
@@ -5350,6 +5354,11 @@
frame.editor().insertText(text, nullptr, replacesText ? TextEventInputAutocompletion : TextEventInputKeyboard);
} else
frame.editor().confirmComposition(text);
+
+ if (focusedElement && options.shouldSimulateKeyboardInput) {
+ focusedElement->dispatchEvent(Event::create(eventNames().keyupEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
+ focusedElement->dispatchEvent(Event::create(eventNames().changeEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
+ }
}
void WebPage::hasMarkedText(CompletionHandler<void(bool)>&& completionHandler)
Modified: trunk/Tools/ChangeLog (258872 => 258873)
--- trunk/Tools/ChangeLog 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Tools/ChangeLog 2020-03-23 21:00:01 UTC (rev 258873)
@@ -1,3 +1,28 @@
+2020-03-23 Daniel Bates <[email protected]>
+
+ Support inserting text or dictation alternative by simulating keyboard input
+ https://bugs.webkit.org/show_bug.cgi?id=209380
+ <rdar://problem/59445102>
+
+ Reviewed by Darin Adler.
+
+ Add tests to ensure that DOM events are dispatched on insertion when shouldSimulateKeyboardInput
+ is enabled.
+
+ I also added a convenience assertion function, EXPECT_NS_EQUAL, that can
+ compare NSObjects so long as they implement -isEqual and -description. I
+ make use of this to compare the actual array of fired DOM events types to
+ an expected array.
+
+ * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+ * TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
+ (TestWebKitAPI::shouldSimulateKeyboardInputOnTextInsertionOverride):
+ (TestWebKitAPI::TEST):
+ * TestWebKitAPI/cocoa/TestCocoa.h:
+ (TestWebKitAPI::Util::assertNSObjectsAreEqual): Added.
+ (EXPECT_NS_EQUAL): Added.
+ * TestWebKitAPI/ios/insert-text.html: Added.
+
2020-03-23 Kate Cheney <[email protected]>
Add checks for app-bound navigations when evaluating user style sheets
Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (258872 => 258873)
--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj 2020-03-23 21:00:01 UTC (rev 258873)
@@ -984,6 +984,7 @@
CE3524FA1B1443890028A7C5 /* input-focus-blur.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CE3524F51B142BBB0028A7C5 /* input-focus-blur.html */; };
CE449E1121AE0F7200E7ADA1 /* WKWebViewFindString.mm in Sources */ = {isa = PBXBuildFile; fileRef = CE449E1021AE0F7200E7ADA1 /* WKWebViewFindString.mm */; };
CE4D5DE71F6743BA0072CFC6 /* StringWithDirection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE4D5DE51F6743BA0072CFC6 /* StringWithDirection.cpp */; };
+ CE6D0EE32426B932002AD901 /* insert-text.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CE6D0EE22426B8ED002AD901 /* insert-text.html */; };
CE6E81A020A6935F00E2C80F /* SetTimeoutFunction.mm in Sources */ = {isa = PBXBuildFile; fileRef = CE6E819F20A6935F00E2C80F /* SetTimeoutFunction.mm */; };
CE6E81A420A933D500E2C80F /* set-timeout-function.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CE6E81A320A933B800E2C80F /* set-timeout-function.html */; };
CE78705F2107AB980053AC67 /* MoveOnlyLifecycleLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE78705D2107AB8C0053AC67 /* MoveOnlyLifecycleLogger.cpp */; };
@@ -1374,6 +1375,7 @@
5110FCF61E01CD83006F8D0B /* IndexUpgrade.sqlite3 in Copy Resources */,
2EFF06CD1D8A429A0004BB30 /* input-field-in-scrollable-document.html in Copy Resources */,
CE3524FA1B1443890028A7C5 /* input-focus-blur.html in Copy Resources */,
+ CE6D0EE32426B932002AD901 /* insert-text.html in Copy Resources */,
57F56A5C1C7F8CC100F31D7E /* IsNavigationActionTrusted.html in Copy Resources */,
C9B4AD2C1ECA6F7F00F5FEA0 /* js-autoplay-audio.html in Copy Resources */,
C99B675D1E39722000FC6C80 /* js-play-with-controls.html in Copy Resources */,
@@ -2578,6 +2580,7 @@
CE50D8C81C8665CE0072EA5A /* OptionSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OptionSet.cpp; sourceTree = "<group>"; };
CE640CA52370A4F300C5CAA4 /* TestCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TestCocoa.h; path = cocoa/TestCocoa.h; sourceTree = "<group>"; };
CE640CA62370A4F300C5CAA4 /* TestCocoa.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = TestCocoa.mm; path = cocoa/TestCocoa.mm; sourceTree = "<group>"; };
+ CE6D0EE22426B8ED002AD901 /* insert-text.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = "insert-text.html"; path = "ios/insert-text.html"; sourceTree = SOURCE_ROOT; };
CE6E819F20A6935F00E2C80F /* SetTimeoutFunction.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SetTimeoutFunction.mm; sourceTree = "<group>"; };
CE6E81A320A933B800E2C80F /* set-timeout-function.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = "set-timeout-function.html"; path = "ios/set-timeout-function.html"; sourceTree = SOURCE_ROOT; };
CE78705C2107AB8C0053AC67 /* MoveOnlyLifecycleLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoveOnlyLifecycleLogger.h; sourceTree = "<group>"; };
@@ -3671,6 +3674,7 @@
isa = PBXGroup;
children = (
0F16BED72304A1D100B4A167 /* composited.html */,
+ CE6D0EE22426B8ED002AD901 /* insert-text.html */,
0F340777230382540060A1A0 /* overflow-scroll.html */,
A1C4FB721BACD1B7003742D0 /* pages.pages */,
CE6E81A320A933B800E2C80F /* set-timeout-function.html */,
Modified: trunk/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm (258872 => 258873)
--- trunk/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm 2020-03-23 21:00:01 UTC (rev 258873)
@@ -30,6 +30,7 @@
#import "IPadUserInterfaceSwizzler.h"
#import "InstanceMethodSwizzler.h"
#import "PlatformUtilities.h"
+#import "TestCocoa.h"
#import "TestInputDelegate.h"
#import "TestWKWebView.h"
#import "UIKitSPI.h"
@@ -37,6 +38,10 @@
#import <WebKitLegacy/WebEvent.h>
#import <cmath>
+@interface WKContentView ()
+- (BOOL)_shouldSimulateKeyboardInputOnTextInsertion;
+@end
+
@interface InputAssistantItemTestingWebView : TestWKWebView
+ (UIBarButtonItemGroup *)leadingItemsForWebView:(WKWebView *)webView;
+ (UIBarButtonItemGroup *)trailingItemsForWebView:(WKWebView *)webView;
@@ -647,6 +652,47 @@
EXPECT_TRUE(UIKeyboardImpl.sharedInstance._shouldSuppressSoftwareKeyboard);
}
+static BOOL shouldSimulateKeyboardInputOnTextInsertionOverride(id, SEL)
+{
+ return YES;
+}
+
+TEST(KeyboardInputTests, InsertTextSimulatingKeyboardInput)
+{
+ InstanceMethodSwizzler overrideShouldSimulateKeyboardInputOnTextInsertion { NSClassFromString(@"WKContentView"), @selector(_shouldSimulateKeyboardInputOnTextInsertion), reinterpret_cast<IMP>(shouldSimulateKeyboardInputOnTextInsertionOverride) };
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+ [inputDelegate setFocusStartsInputSessionPolicyHandler:[&](WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+ [webView _setInputDelegate:inputDelegate.get()];
+
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"insert-text" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+ [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+ [[webView textInputContentView] insertText:@"hello"];
+ EXPECT_NS_EQUAL((@[@"keydown", @"beforeinput", @"input", @"keyup", @"change"]), [webView objectByEvaluatingJavaScript:@"firedEvents"]);
+}
+
+#if USE(DICTATION_ALTERNATIVES)
+
+TEST(KeyboardInputTests, InsertDictationAlternativesSimulatingKeyboardInput)
+{
+ InstanceMethodSwizzler overrideShouldSimulateKeyboardInputOnTextInsertion { NSClassFromString(@"WKContentView"), @selector(_shouldSimulateKeyboardInputOnTextInsertion), reinterpret_cast<IMP>(shouldSimulateKeyboardInputOnTextInsertionOverride) };
+
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+ [inputDelegate setFocusStartsInputSessionPolicyHandler:[&](WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+ [webView _setInputDelegate:inputDelegate.get()];
+
+ RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"insert-text" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+ [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+ [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.body.focus()"];
+ [[webView textInputContentView] insertText:@"hello" alternatives:@[ @"helo" ] style:UITextAlternativeStyleNone];
+ EXPECT_NS_EQUAL((@[@"keydown", @"beforeinput", @"input", @"keyup", @"change"]), [webView objectByEvaluatingJavaScript:@"firedEvents"]);
+}
+
+#endif
+
} // namespace TestWebKitAPI
#endif // PLATFORM(IOS_FAMILY)
Modified: trunk/Tools/TestWebKitAPI/cocoa/TestCocoa.h (258872 => 258873)
--- trunk/Tools/TestWebKitAPI/cocoa/TestCocoa.h 2020-03-23 20:53:15 UTC (rev 258872)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestCocoa.h 2020-03-23 21:00:01 UTC (rev 258873)
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 Apple Inc. All rights reserved.
+ * Copyright (C) 2019-2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -25,8 +25,26 @@
#pragma once
+#import "PlatformUtilities.h"
#import "Test.h"
+namespace TestWebKitAPI {
+namespace Util {
+
+template<typename T, typename U>
+static inline ::testing::AssertionResult assertNSObjectsAreEqual(const char* expectedExpression, const char* actualExpression, T *expected, U *actual)
+{
+ if ((!expected && !actual) || [expected isEqual:actual])
+ return ::testing::AssertionSuccess();
+ return ::testing::internal::EqFailure(expectedExpression, actualExpression, toSTD([expected description]), toSTD([actual description]), false /* ignoring_case */);
+}
+
+} // namespace Util
+} // namespace TestWebKitAPI
+
+#define EXPECT_NS_EQUAL(expected, actual) \
+ EXPECT_PRED_FORMAT2(TestWebKitAPI::Util::assertNSObjectsAreEqual, expected, actual)
+
#if USE(CG)
std::ostream& operator<<(std::ostream&, const CGRect&);
Added: trunk/Tools/TestWebKitAPI/ios/insert-text.html (0 => 258873)
--- trunk/Tools/TestWebKitAPI/ios/insert-text.html (rev 0)
+++ trunk/Tools/TestWebKitAPI/ios/insert-text.html 2020-03-23 21:00:01 UTC (rev 258873)
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+let firedEvents = [];
+
+function logEvent(event)
+{
+ firedEvents.push(event.type);
+}
+
+function setupTest()
+{
+ document.body.addEventListener("keydown", logEvent, false);
+ document.body.addEventListener("beforeinput", logEvent, false);
+ document.body.addEventListener("input", logEvent, false);
+ document.body.addEventListener("keyup", logEvent, false);
+ document.body.addEventListener("change", logEvent, false);
+}
+</script>
+</head>
+<body _onload_="setupTest()" contenteditable="true">
+</body>
+</html>