Diff
Modified: trunk/Source/WebKit/ChangeLog (260213 => 260214)
--- trunk/Source/WebKit/ChangeLog 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/ChangeLog 2020-04-16 19:48:02 UTC (rev 260214)
@@ -1,3 +1,41 @@
+2020-04-16 Daniel Bates <[email protected]>
+
+ [iOS] Add a way to focus a text input and place a caret
+ https://bugs.webkit.org/show_bug.cgi?id=210611
+ <rdar://problem/61893062>
+
+ Reviewed by Darin Adler.
+
+ Add some IPI that will be used by code in WebKitAdditions to focus a text input context
+ and place the caret in it. This will replace the existing -focusTextInput SPI, which I
+ will remove in a subsequent commit.
+
+ * UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h:
+ * UIProcess/API/ios/WKWebViewTestingIOS.mm:
+ (-[WKWebView _requestTextInputContextsInRect:completionHandler:]): Fix up code style
+ of signature while I am here.
+ (-[WKWebView _focusTextInputContext:placeCaretAt:completionHandler:]): Added.
+ * UIProcess/WebPageProxy.h:
+ * UIProcess/ios/WKContentViewInteraction.h:
+ * UIProcess/ios/WKContentViewInteraction.mm:
+ (-[WKContentView _isTextInputContextFocused:]): Added.
+ (-[WKContentView _focusTextInputContext:placeCaretAt:completionHandler:]): Added.
+ (-[WKContentView _requestTextInputContextsInRect:completionHandler:]): Fix up code style
+ of signature while I am here.
+ * UIProcess/ios/WebPageProxyIOS.mm:
+ (WebKit::WebPageProxy::focusTextInputContextAndPlaceCaret):
+ * WebProcess/WebPage/WebPage.cpp:
+ (WebKit::WebPage::focusTextInputContext): Use auto now that elementForContext() returns a RefPtr.
+ (WebKit::WebPage::elementForContext const): Have it return a RefPtr instead of a raw
+ pointer so callers don't have to remember to take out a ref of otherwise be mindful
+ of the element's lifetime.
+ * WebProcess/WebPage/WebPage.h:
+ * WebProcess/WebPage/WebPage.messages.in:
+ * WebProcess/WebPage/ios/WebPageIOS.mm:
+ (WebKit::WebPage::removeTextPlaceholder): Use auto now that elementForContext() returns a RefPtr.
+ (WebKit::WebPage::requestDocumentEditingContext): Ditto.
+ (WebKit::WebPage::focusTextInputContextAndPlaceCaret): Added.
+
2020-04-16 Chris Dumez <[email protected]>
Use safeRoundPage() instead of round_page() in SharedMemory
Modified: trunk/Source/WebKit/UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h (260213 => 260214)
--- trunk/Source/WebKit/UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h 2020-04-16 19:48:02 UTC (rev 260214)
@@ -49,7 +49,8 @@
- (void)selectFormAccessoryPickerRow:(int)rowIndex;
- (BOOL)_mayContainEditableElementsInRect:(CGRect)rect;
-- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void(^)(NSArray<_WKTextInputContext *> *))completionHandler;
+- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void (^)(NSArray<_WKTextInputContext *> *))completionHandler;
+- (void)_focusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point completionHandler:(void (^)(UIResponder<UITextInput> *))completionHandler;
- (void)_requestDocumentContext:(UIWKDocumentRequest *)request completionHandler:(void (^)(UIWKDocumentContext *))completionHandler;
- (void)_adjustSelectionWithDelta:(NSRange)deltaRange completionHandler:(void (^)(void))completionHandler;
Modified: trunk/Source/WebKit/UIProcess/API/ios/WKWebViewTestingIOS.mm (260213 => 260214)
--- trunk/Source/WebKit/UIProcess/API/ios/WKWebViewTestingIOS.mm 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/UIProcess/API/ios/WKWebViewTestingIOS.mm 2020-04-16 19:48:02 UTC (rev 260214)
@@ -42,7 +42,7 @@
@implementation WKWebView (WKTestingIOS)
-- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void(^)(NSArray<_WKTextInputContext *> *))completionHandler
+- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void (^)(NSArray<_WKTextInputContext *> *))completionHandler
{
// Adjust returned bounding rects to be in WKWebView coordinates.
auto adjustedRect = [self convertRect:rect toView:_contentView.get()];
@@ -62,6 +62,12 @@
}];
}
+- (void)_focusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point completionHandler:(void (^)(UIResponder<UITextInput> *))completionHandler
+{
+ auto adjustedPoint = [self convertPoint:point toView:_contentView.get()];
+ [_contentView _focusTextInputContext:context placeCaretAt:adjustedPoint completionHandler:completionHandler];
+}
+
- (void)_requestDocumentContext:(UIWKDocumentRequest *)request completionHandler:(void (^)(UIWKDocumentContext *))completionHandler
{
[_contentView requestDocumentContext:request completionHandler:completionHandler];
Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (260213 => 260214)
--- trunk/Source/WebKit/UIProcess/WebPageProxy.h 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h 2020-04-16 19:48:02 UTC (rev 260214)
@@ -713,6 +713,8 @@
#endif
#if PLATFORM(IOS_FAMILY)
+ void focusTextInputContextAndPlaceCaret(const WebCore::ElementContext&, const WebCore::IntPoint&, CompletionHandler<void(bool)>&&);
+
void setShouldRevealCurrentSelectionAfterInsertion(bool);
void insertTextPlaceholder(const WebCore::IntSize&, CompletionHandler<void(const Optional<WebCore::ElementContext>&)>&&);
Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (260213 => 260214)
--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h 2020-04-16 19:48:02 UTC (rev 260214)
@@ -567,7 +567,8 @@
- (WKDrawingCoordinator *)_drawingCoordinator;
#endif
-- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void(^)(NSArray<_WKTextInputContext *> *))completionHandler;
+- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void (^)(NSArray<_WKTextInputContext *> *))completionHandler;
+- (void)_focusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point completionHandler:(void (^)(UIResponder<UITextInput> *))completionHandler;
#if USE(TEXT_INTERACTION_ADDITIONS)
- (void)_willBeginTextInteractionInTextInputContext:(_WKTextInputContext *)context;
Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (260213 => 260214)
--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2020-04-16 19:48:02 UTC (rev 260214)
@@ -5078,8 +5078,41 @@
return CGRectZero;
}
-- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void(^)(NSArray<_WKTextInputContext *> *))completionHandler
+- (BOOL)_isTextInputContextFocused:(_WKTextInputContext *)context
{
+ ASSERT(context);
+ // We ignore bounding rect changes as the bounding rect of the focused element is not kept up-to-date.
+ return self._hasFocusedElement && context._textInputContext.isSameElement(_focusedElementInformation.elementContext);
+}
+
+- (void)_focusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point completionHandler:(void (^)(UIResponder<UITextInput> *))completionHandler
+{
+ ASSERT(context);
+ if (![self becomeFirstResponder]) {
+ completionHandler(nil);
+ return;
+ }
+ if ([self _isTextInputContextFocused:context]) {
+ completionHandler(_focusedElementInformation.isReadOnly ? nil : self);
+ return;
+ }
+ _usingGestureForSelection = YES;
+ auto checkFocusedElement = [weakSelf = WeakObjCPtr<WKContentView> { self }, context = [context copy], completionHandler = makeBlockPtr(completionHandler)] (bool success) {
+ auto strongSelf = weakSelf.get();
+ if (strongSelf)
+ strongSelf->_usingGestureForSelection = NO;
+ if (!strongSelf || !success) {
+ completionHandler(nil);
+ return;
+ }
+ bool focusedAndEditable = [strongSelf _isTextInputContextFocused:context] && !strongSelf->_focusedElementInformation.isReadOnly;
+ completionHandler(focusedAndEditable ? strongSelf.autorelease() : nil);
+ };
+ _page->focusTextInputContextAndPlaceCaret(context._textInputContext, WebCore::IntPoint { point }, WTFMove(checkFocusedElement));
+}
+
+- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void (^)(NSArray<_WKTextInputContext *> *))completionHandler
+{
#if ENABLE(EDITABLE_REGION)
if (!self.webView._editable && !WebKit::mayContainEditableElementsInRect(self, rect)) {
completionHandler(@[ ]);
Modified: trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm (260213 => 260214)
--- trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm 2020-04-16 19:48:02 UTC (rev 260214)
@@ -1539,7 +1539,16 @@
return pageClient().isApplicationVisible();
}
+void WebPageProxy::focusTextInputContextAndPlaceCaret(const ElementContext& context, const IntPoint& point, CompletionHandler<void(bool)>&& completionHandler)
+{
+ if (!hasRunningProcess()) {
+ completionHandler(false);
+ return;
+ }
+ sendWithAsyncReply(Messages::WebPage::FocusTextInputContextAndPlaceCaret(context, point), WTFMove(completionHandler));
+}
+
void WebPageProxy::setShouldRevealCurrentSelectionAfterInsertion(bool shouldRevealCurrentSelectionAfterInsertion)
{
if (hasRunningProcess())
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (260213 => 260214)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp 2020-04-16 19:48:02 UTC (rev 260214)
@@ -6913,7 +6913,7 @@
void WebPage::focusTextInputContext(const WebCore::ElementContext& textInputContext, CompletionHandler<void(bool)>&& completionHandler)
{
- RefPtr<Element> element = elementForContext(textInputContext);
+ auto element = elementForContext(textInputContext);
if (element)
element->focus();
@@ -6928,7 +6928,7 @@
downcast<HTMLTextFormControlElement>(*element).setCanShowPlaceholder(canShowPlaceholder);
}
-Element* WebPage::elementForContext(const WebCore::ElementContext& elementContext) const
+RefPtr<Element> WebPage::elementForContext(const ElementContext& elementContext) const
{
if (elementContext.webPageIdentifier != m_identifier)
return nullptr;
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (260213 => 260214)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h 2020-04-16 19:48:02 UTC (rev 260214)
@@ -647,6 +647,8 @@
void setCanShowPlaceholder(const WebCore::ElementContext&, bool);
#if PLATFORM(IOS_FAMILY)
+ void focusTextInputContextAndPlaceCaret(const WebCore::ElementContext&, const WebCore::IntPoint&, CompletionHandler<void(bool)>&&);
+
bool shouldRevealCurrentSelectionAfterInsertion() const { return m_shouldRevealCurrentSelectionAfterInsertion; }
void setShouldRevealCurrentSelectionAfterInsertion(bool);
@@ -1250,7 +1252,7 @@
void configureLoggingChannel(const String&, WTFLogChannelState, WTFLogLevel);
- WebCore::Element* elementForContext(const WebCore::ElementContext&) const;
+ RefPtr<WebCore::Element> elementForContext(const WebCore::ElementContext&) const;
Optional<WebCore::ElementContext> contextForElement(WebCore::Element&) const;
void startTextManipulations(Vector<WebCore::TextManipulationController::ExclusionRule>&&, CompletionHandler<void()>&&);
Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in (260213 => 260214)
--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in 2020-04-16 19:48:02 UTC (rev 260214)
@@ -121,6 +121,7 @@
SetShouldRevealCurrentSelectionAfterInsertion(bool shouldRevealCurrentSelectionAfterInsertion)
InsertTextPlaceholder(WebCore::IntSize size) -> (Optional<WebCore::ElementContext> placeholder) Async
RemoveTextPlaceholder(struct WebCore::ElementContext placeholder) -> () Async
+ FocusTextInputContextAndPlaceCaret(struct WebCore::ElementContext context, WebCore::IntPoint point) -> (bool success) Async
#endif
SetControlledByAutomation(bool controlled)
Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (260213 => 260214)
--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2020-04-16 19:48:02 UTC (rev 260214)
@@ -129,6 +129,7 @@
#import <WebCore/TextIterator.h>
#import <WebCore/TextPlaceholderElement.h>
#import <WebCore/UserAgent.h>
+#import <WebCore/UserGestureIndicator.h>
#import <WebCore/VisibleUnits.h>
#import <WebCore/WebEvent.h>
#import <wtf/MathExtras.h>
@@ -4045,9 +4046,9 @@
completionHandler(placeholder ? contextForElement(*placeholder) : WTF::nullopt);
}
-void WebPage::removeTextPlaceholder(const WebCore::ElementContext& placeholder, CompletionHandler<void()>&& completionHandler)
+void WebPage::removeTextPlaceholder(const ElementContext& placeholder, CompletionHandler<void()>&& completionHandler)
{
- if (RefPtr<Element> element = elementForContext(placeholder)) {
+ if (auto element = elementForContext(placeholder)) {
RELEASE_ASSERT(is<TextPlaceholderElement>(element));
if (RefPtr<Frame> frame = element->document().frame())
frame->editor().removeTextPlaceholder(downcast<TextPlaceholderElement>(*element));
@@ -4140,7 +4141,7 @@
bool wantsMarkedTextRects = request.options.contains(DocumentEditingContextRequest::Options::MarkedTextRects);
if (auto textInputContext = request.textInputContext) {
- RefPtr<Element> element = elementForContext(*textInputContext);
+ auto element = elementForContext(*textInputContext);
if (!element) {
completionHandler({ });
return;
@@ -4296,6 +4297,44 @@
scheduleFullEditorStateUpdate();
}
+void WebPage::focusTextInputContextAndPlaceCaret(const ElementContext& elementContext, const IntPoint& point, CompletionHandler<void(bool)>&& completionHandler)
+{
+ auto target = elementForContext(elementContext);
+ if (!target) {
+ completionHandler(false);
+ return;
+ }
+
+ ASSERT(target->document().frame());
+ auto targetFrame = makeRef(*target->document().frame());
+
+ targetFrame->document()->updateLayoutIgnorePendingStylesheets();
+
+ // Performing layout could have could torn down the element's renderer. Check that we still
+ // have one. Otherwise, bail out as this function only focuses elements that have a visual
+ // representation.
+ if (!target->renderer()) {
+ completionHandler(false);
+ return;
+ }
+
+ UserGestureIndicator gestureIndicator { ProcessingUserGesture, &target->document() };
+ SetForScope<bool> userIsInteractingChange { m_userIsInteracting, true };
+ bool didFocus = m_page->focusController().setFocusedElement(target.get(), targetFrame);
+
+ // Setting the focused element could tear down the element's renderer. Check that we still have one.
+ if (!didFocus || !target->renderer()) {
+ completionHandler(false);
+ return;
+ }
+ ASSERT(m_focusedElement == target);
+ // The function visiblePositionInFocusedNodeForPoint constrains the point to be inside
+ // the bounds of the target element.
+ auto position = visiblePositionInFocusedNodeForPoint(targetFrame, point, true /* isInteractingWithFocusedElement */);
+ targetFrame->selection().setSelectedRange(Range::create(*targetFrame->document(), position, position).ptr(), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered);
+ completionHandler(true);
+}
+
void WebPage::platformDidScalePage()
{
auto transactionID = downcast<RemoteLayerTreeDrawingArea>(*m_drawingArea).lastCommittedTransactionID();
Modified: trunk/Tools/ChangeLog (260213 => 260214)
--- trunk/Tools/ChangeLog 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Tools/ChangeLog 2020-04-16 19:48:02 UTC (rev 260214)
@@ -1,5 +1,23 @@
2020-04-16 Daniel Bates <[email protected]>
+ [iOS] Add a way to focus a text input and place a caret
+ https://bugs.webkit.org/show_bug.cgi?id=210611
+ <rdar://problem/61893062>
+
+ Reviewed by Darin Adler.
+
+ Add some tests.
+
+ * TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm:
+ (-[TestWKWebView synchronouslyFocusTextInputContext:placeCaretAt:]): Added.
+ (webViewLoadHTMLStringAndWaitForAllFramesToPaint): Use the bundle's TestWebKitAPI.resources directory
+ as the base URL so that we have a valid file URL. Some of the tests will then
+ call -_setAllowUniversalAccessFromFileURLs to allow the main frame access to
+ the unique-origin child frame contents.
+ (TEST):
+
+2020-04-16 Daniel Bates <[email protected]>
+
REGRESSION (r259762): Should always hit test for editable elements if the WKWebView is fully editable
https://bugs.webkit.org/show_bug.cgi?id=210558
<rdar://problem/61798347>
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm (260213 => 260214)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm 2020-04-16 19:44:10 UTC (rev 260213)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm 2020-04-16 19:48:02 UTC (rev 260214)
@@ -32,6 +32,7 @@
#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import <WebKit/WKPreferencesRefPrivate.h>
+#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/_WKTextInputContext.h>
#import <wtf/RetainPtr.h>
@@ -62,6 +63,18 @@
return success;
}
+- (UIResponder<UITextInput> *)synchronouslyFocusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point
+{
+ __block bool finished = false;
+ __block UIResponder<UITextInput> *responder = nil;
+ [self _focusTextInputContext:context placeCaretAt:point completionHandler:^(UIResponder<UITextInput> *textInputResponder) {
+ responder = textInputResponder;
+ finished = true;
+ }];
+ TestWebKitAPI::Util::run(&finished);
+ return responder;
+}
+
@end
static NSString *applyStyle(NSString *HTMLString)
@@ -157,7 +170,7 @@
ASSERT(webView); // Make passing a nil web view a more obvious failure than a hang.
bool didFireDOMLoadEvent = false;
[webView performAfterLoading:[&] { didFireDOMLoadEvent = true; }];
- [webView loadHTMLString:htmlString baseURL:nil];
+ [webView loadHTMLString:htmlString baseURL:[NSBundle.mainBundle.bundleURL URLByAppendingPathComponent:@"TestWebKitAPI.resources"]];
TestWebKitAPI::Util::run(&didFireDOMLoadEvent);
[webView waitForNextPresentationUpdate];
}
@@ -274,4 +287,136 @@
EXPECT_FALSE([webView synchronouslyFocusTextInputContext:textArea.get()]);
}
+TEST(RequestTextInputContext, ReadOnlyField)
+{
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' value='hello world' style='width: 100px; height: 50px;' readonly>")];
+ EXPECT_EQ(0UL, [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]].count);
+}
+
+TEST(RequestTextInputContext, DisabledField)
+{
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' value='hello world' style='width: 100px; height: 50px;' disabled>")];
+ EXPECT_EQ(0UL, [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]].count);
+}
+
+TEST(RequestTextInputContext, FocusFieldAndPlaceCaretAtStart)
+{
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' value='hello world' style='width: 100px; height: 50px;'>")];
+ NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+ EXPECT_EQ(1UL, contexts.count);
+ RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+ EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+ EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
+ EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
+}
+
+TEST(RequestTextInputContext, FocusFieldAndPlaceCaretAtEnd)
+{
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ constexpr char exampleText[] = "hello world";
+ constexpr size_t exampleTextLength = sizeof(exampleText) - 1;
+ [webView synchronouslyLoadHTMLString:applyStyle([NSString stringWithFormat:@"<input type='text' value='%s' style='width: 100px; height: 50px;'>", exampleText])];
+ NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+ EXPECT_EQ(1UL, contexts.count);
+ RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+ CGRect boundingRect = [inputElement boundingRect];
+ CGPoint endPosition = CGPointMake(boundingRect.origin.x + boundingRect.size.width, boundingRect.origin.y + boundingRect.size.height / 2);
+ EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:endPosition]);
+ EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_EQ(static_cast<int>(exampleTextLength), [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
+ EXPECT_EQ(static_cast<int>(exampleTextLength), [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
+}
+
+TEST(RequestTextInputContext, FocusFieldAndPlaceCaretOutsideField)
+{
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ constexpr char exampleText[] = "hello world";
+ [webView synchronouslyLoadHTMLString:applyStyle([NSString stringWithFormat:@"<input type='text' value='%s' style='width: 100px; height: 50px;'>", exampleText])];
+ NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+ EXPECT_EQ(1UL, contexts.count);
+ RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+ auto resetTest = [&] {
+ [webView stringByEvaluatingJavaScript:@"document.activeElement.blur()"];
+ EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ };
+
+ // Point before the field
+ EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:CGPointMake(-1000, -500)]);
+ EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
+ EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
+ resetTest();
+
+ // Point after the field
+ EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:CGPointMake(1000, 500)]);
+ EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_EQ(11, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
+ EXPECT_EQ(11, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
+ resetTest();
+}
+
+TEST(RequestTextInputContext, FocusFieldInFrame)
+{
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ [configuration _setAllowUniversalAccessFromFileURLs:YES];
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ auto testPage = applyStyle([NSString stringWithFormat:@"<input type='text' value='mainFrameField' style='width: 100px; height: 50px;'>%@", applyIframe(@"<input type='text' value='iframeField' style='width: 120px; height: 70px;'>")]);
+ webViewLoadHTMLStringAndWaitForAllFramesToPaint(webView.get(), testPage);
+ NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+ EXPECT_EQ(2UL, contexts.count);
+ RetainPtr<_WKTextInputContext> frameBField = contexts[0];
+
+ EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:frameBField.get() placeCaretAt:[frameBField boundingRect].origin]);
+ EXPECT_WK_STREQ("IFRAME", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.tagName"]);
+ EXPECT_WK_STREQ("iframeField", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.value"]);
+ EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.selectionStart"] intValue]);
+ EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.selectionEnd"] intValue]);
+}
+
+TEST(RequestTextInputContext, SwitchFocusBetweenFrames)
+{
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ [configuration _setAllowUniversalAccessFromFileURLs:YES];
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ auto testPage = applyStyle([NSString stringWithFormat:@"<input type='text' value='mainFrameField' style='width: 100px; height: 50px;'>%@", applyIframe(@"<input type='text' value='iframeField' style='width: 100px; height: 50px;'>")]);
+ webViewLoadHTMLStringAndWaitForAllFramesToPaint(webView.get(), testPage);
+ NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+ EXPECT_EQ(2UL, contexts.count);
+ // Note that returned contexts are in hit-test order.
+ RetainPtr<_WKTextInputContext> frameAField = contexts[1];
+ RetainPtr<_WKTextInputContext> frameBField = contexts[0];
+
+ EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.tagName"]);
+ EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:frameAField.get() placeCaretAt:[frameAField boundingRect].origin]);
+ EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_WK_STREQ("mainFrameField", [webView stringByEvaluatingJavaScript:@"document.activeElement.value"]);
+ EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.tagName"]);
+
+ EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:frameBField.get() placeCaretAt:[frameBField boundingRect].origin]);
+ EXPECT_WK_STREQ("IFRAME", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+ EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.tagName"]);
+ EXPECT_WK_STREQ("iframeField", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.value"]);
+}
+
#endif // PLATFORM(IOS_FAMILY)