blob: 8a18405ff890935a7769519a871a5ee352752c10 [file] [log] [blame]
/*
* 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
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#if PLATFORM(IOS_FAMILY)
#import "PlatformUtilities.h"
#import "TestCocoa.h"
#import "TestInputDelegate.h"
#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import "UserInterfaceSwizzler.h"
#import <WebKit/WKPreferencesRefPrivate.h>
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/_WKTextInputContext.h>
#import <wtf/RetainPtr.h>
namespace TestWebKitAPI {
class TextInteractionForScope {
public:
TextInteractionForScope(const RetainPtr<TestWKWebView>& webView, const RetainPtr<_WKTextInputContext>& textInputContext)
: m_webView { webView }
, m_textInputContext { textInputContext }
{
[m_webView _willBeginTextInteractionInTextInputContext:m_textInputContext.get()];
}
~TextInteractionForScope()
{
[m_webView _didFinishTextInteractionInTextInputContext:m_textInputContext.get()];
[m_webView waitForNextPresentationUpdate];
}
private:
RetainPtr<TestWKWebView> m_webView;
RetainPtr<_WKTextInputContext> m_textInputContext;
};
} // namespace TestWebKitAPI
static bool didScroll;
@interface TextInteractionScrollDelegate : NSObject <UIScrollViewDelegate>
@end
// Ideally this would ensure that the focused element is actually in view.
// For the purposes of the tests in this file it is enough to know whether
// a scroll occurred.
@implementation TextInteractionScrollDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
didScroll = true;
}
@end
@implementation TestWKWebView (SynchronousTextInputContext)
- (NSArray<_WKTextInputContext *> *)synchronouslyRequestTextInputContextsInRect:(CGRect)rect
{
__block bool finished = false;
__block RetainPtr<NSArray<_WKTextInputContext *>> result;
[self _requestTextInputContextsInRect:rect completionHandler:^(NSArray<_WKTextInputContext *> *contexts) {
result = contexts;
finished = true;
}];
TestWebKitAPI::Util::run(&finished);
return result.autorelease();
}
- (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
namespace TestWebKitAPI {
static NSString *applyStyle(NSString *HTMLString)
{
return [@"<style>body { margin: 0; } iframe { border: none; }</style><meta name='viewport' content='initial-scale=1'>" stringByAppendingString:HTMLString];
}
static NSString *applyIframe(NSString *HTMLString)
{
return applyStyle([NSString stringWithFormat:@"<iframe src=\"data:text/html,%@\" style='position: absolute; top: 200px;'>", [applyStyle(HTMLString) stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]]);
}
TEST(RequestTextInputContext, Simple)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSArray<_WKTextInputContext *> *contexts;
// Basic inputs.
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[0].boundingRect);
[webView synchronouslyLoadHTMLString:applyStyle(@"<textarea style='width: 100px; height: 100px;'></textarea>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[0].boundingRect);
[webView synchronouslyLoadHTMLString:applyStyle(@"<div contenteditable style='width: 100px; height: 100px;'></div>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[0].boundingRect);
// Read only inputs; should not be included.
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;' readonly>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(0UL, contexts.count);
[webView synchronouslyLoadHTMLString:applyStyle(@"<textarea style='width: 100px; height: 100px;' readonly>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(0UL, contexts.count);
// Inputs outside the requested rect; should not be included.
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(100, 100, 100, 100)];
EXPECT_EQ(0UL, contexts.count);
// Inputs scrolled outside the requested rect; should not be included.
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'><br><div style='width: 100px; height: 10000px;'></div>")];
[webView stringByEvaluatingJavaScript:@"window.scrollTo(0, 5000)"];
[webView waitForNextPresentationUpdate];
EXPECT_EQ(5000, [[webView objectByEvaluatingJavaScript:@"pageYOffset"] floatValue]);
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(0UL, contexts.count);
// Inputs scrolled into the requested rect.
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px; position: absolute; top: 5000px;'><br><div style='width: 100px; height: 10000px;'></div>")];
[webView stringByEvaluatingJavaScript:@"window.scrollTo(0, 5000)"];
[webView waitForNextPresentationUpdate];
EXPECT_EQ(5000, [[webView objectByEvaluatingJavaScript:@"pageYOffset"] floatValue]);
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[0].boundingRect);
// Multiple inputs.
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'><br/><input type='text' style='width: 50px; height: 50px;'><br/><input type='text' style='width: 50px; height: 50px;'>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(3UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 100, 50, 50), contexts[0].boundingRect);
EXPECT_EQ(CGRectMake(0, 50, 50, 50), contexts[1].boundingRect);
EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[2].boundingRect);
// Nested <input>-inside-contenteditable.
[webView synchronouslyLoadHTMLString:applyStyle(@"<div contenteditable style='width: 100px; height: 100px;'><input type='text' style='width: 50px; height: 50px;'></div>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(2UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[0].boundingRect);
EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[1].boundingRect);
}
// Consider moving this to TestWKWebView if it could be useful to other tests.
static void webViewLoadHTMLStringAndWaitForAllFramesToPaint(TestWKWebView *webView, NSString *htmlString)
{
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:[NSBundle.mainBundle.bundleURL URLByAppendingPathComponent:@"TestWebKitAPI.resources"]];
TestWebKitAPI::Util::run(&didFireDOMLoadEvent);
[webView waitForNextPresentationUpdate];
}
TEST(RequestTextInputContext, Iframe)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSArray<_WKTextInputContext *> *contexts;
webViewLoadHTMLStringAndWaitForAllFramesToPaint(webView.get(), applyIframe(@"<input type='text' style='width: 50px; height: 50px;'>"));
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 200, 50, 50), contexts[0].boundingRect);
webViewLoadHTMLStringAndWaitForAllFramesToPaint(webView.get(), applyIframe(@"<textarea style='width: 100px; height: 100px;'></textarea>"));
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 200, 100, 100), contexts[0].boundingRect);
webViewLoadHTMLStringAndWaitForAllFramesToPaint(webView.get(), applyIframe(@"<div contenteditable style='width: 100px; height: 100px;'></div>"));
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 200, 100, 100), contexts[0].boundingRect);
}
TEST(RequestTextInputContext, ViewIsEditable)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body></body>")];
[webView _setEditable:YES];
EXPECT_GE([webView synchronouslyRequestTextInputContextsInRect:[webView bounds]].count, 1UL);
}
static CGRect squareCenteredAtPoint(float x, float y, float length)
{
return CGRectMake(x - length / 2, y - length / 2, length, length);
}
TEST(RequestTextInputContext, NonCompositedOverlap)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSArray<_WKTextInputContext *> *contexts;
[webView synchronouslyLoadTestPageNamed:@"editable-region-composited-and-non-composited-overlap"];
// Search rect fully contained in non-composited <div>.
contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(270, 150, 10, 10)];
EXPECT_EQ(0UL, contexts.count);
contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(170, 150, 10, 10)];
EXPECT_EQ(0UL, contexts.count);
// Search rect overlaps both the non-composited <div> and the editable element.
contexts = [webView synchronouslyRequestTextInputContextsInRect:squareCenteredAtPoint(270, 150, 200)];
EXPECT_EQ(1UL, contexts.count);
}
TEST(RequestTextInputContext, CompositedOverlap)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSArray<_WKTextInputContext *> *contexts;
[webView synchronouslyLoadTestPageNamed:@"editable-region-composited-and-non-composited-overlap"];
// Search rect fully contained in composited <div>.
contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(270, 400, 10, 10)];
EXPECT_EQ(0UL, contexts.count);
contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(170, 400, 10, 10)];
EXPECT_EQ(0UL, contexts.count);
// Search rect overlaps both the composited <div> and the editable element.
contexts = [webView synchronouslyRequestTextInputContextsInRect:squareCenteredAtPoint(270, 400, 200)];
EXPECT_EQ(1UL, contexts.count);
}
TEST(RequestTextInputContext, RectContainsEditableNonRootChild)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body contenteditable='true' style='width: 500px; height: 500px'><div style='width: 200px; height: 200px; background-color: blue'>Hello World</div></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(10, 10, 20, 20)];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 500, 500), contexts[0].boundingRect);
}
TEST(RequestTextInputContext, RectContainsNestedEditableNonRootChild)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body contenteditable='true' style='width: 500px; height: 500px'><div style='width: 200px; height: 200px; background-color: blue'><div style='width: 100px; height: 100px; background-color: yellow' contenteditable='true'>Hello World</div></div></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(10, 10, 20, 20)];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 500, 500), contexts[0].boundingRect);
}
TEST(RequestTextInputContext, RectContainsInnerEditableNonRootChild)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body contenteditable='true' style='width: 500px; height: 500px'><div style='width: 200px; height: 200px; background-color: blue' contenteditable='false'><div style='width: 100px; height: 100px; background-color: yellow' contenteditable='true'>Hello World</div></div><p>Body</p></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(10, 10, 20, 20)];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[0].boundingRect);
}
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, FocusAfterNavigation)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
// 1. Load initial page and save off the text input context for the input element on it.
[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];
// 2. Load a new page.
[webView synchronouslyLoadHTMLString:@"<body></body>"];
// 3. Focus the input element in the old page.
EXPECT_NULL([webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
}
TEST(RequestTextInputContext, CaretShouldNotMoveInAlreadyFocusedField)
{
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);
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
// Place the caret the end of the field; should succeed becausse field is not already focused.
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]);
// Try to place the caret at the start of the field; should fail since field is already focused.
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:boundingRect.origin]);
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, CaretShouldNotMoveInAlreadyFocusedField2)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
[webView _setInputDelegate:inputDelegate.get()];
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' id='input' value='hello world' style='width: 100px; height: 50px;'>")];
// Use JavaScript to place the caret after the 'h' in the field.
[webView evaluateJavaScriptAndWaitForInputSessionToChange:@"input.focus()"];
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
[webView stringByEvaluatingJavaScript:@"input.setSelectionRange(1, 1)"];
EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
// Use -focusTextInputContext: to place the caret at the beginning of the field; no change because the field is already focused.
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(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
}
TEST(RequestTextInputContext, PlaceCaretInNonAssistedFocusedField)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
[webView _setInputDelegate:inputDelegate.get()];
[webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' id='input' value='hello world' style='width: 100px; height: 50px;'>")];
// Use JavaScript to place the caret after the 'h' in the field.
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyDisallow; }];
[webView stringByEvaluatingJavaScript:@"input.focus()"];
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
[webView stringByEvaluatingJavaScript:@"input.setSelectionRange(1, 1)"];
EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
// Use -focusTextInputContext: to place the caret at the beginning of the field; the caret should move.
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
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, FocusTextFieldThenProgrammaticallyReplaceWithTextAreaAndFocusTextArea)
{
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' style='width: 50px; height: 50px;'>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> originalInput = contexts[0];
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:originalInput.get() placeCaretAt:[originalInput boundingRect].origin]);
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
// Replace the <input> with a <textarea> with script; the <input> should no longer be focusable.
[webView objectByEvaluatingJavaScript:@"document.body.innerHTML = '<textarea>'"];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> textArea = contexts[0];
EXPECT_NULL([webView synchronouslyFocusTextInputContext:originalInput.get() placeCaretAt:[originalInput boundingRect].origin]);
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:textArea.get() placeCaretAt:[textArea boundingRect].origin]);
EXPECT_WK_STREQ("TEXTAREA", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
}
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, FocusFieldWithPaddingAndPlaceCaretAtEnd)
{
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; padding: 20px'>", 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);
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";
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];
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(static_cast<int>(exampleTextLength), [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
EXPECT_EQ(static_cast<int>(exampleTextLength), [[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"]);
}
TEST(RequestTextInputContext, FocusingAssistedElementShouldNotScrollPage)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
[webView _setInputDelegate:inputDelegate.get()];
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><div id='editable' style='position: fixed; width: 100%; height: 50px; background-color: blue' contenteditable='true'></div></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
// Focus the field using JavaScript and scroll the page.
[webView evaluateJavaScriptAndWaitForInputSessionToChange:@"editable.focus()"];
EXPECT_WK_STREQ("DIV", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
[webView stringByEvaluatingJavaScript:@"window.scrollTo(0, 2000)"];
EXPECT_EQ(2000, [[webView objectByEvaluatingJavaScript:@"window.scrollY"] intValue]);
// Focus the field using -focusTextInputContext; page should not scroll.
RetainPtr<_WKTextInputContext> editableElement = contexts[0];
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:editableElement.get() placeCaretAt:[editableElement boundingRect].origin]);
EXPECT_WK_STREQ("DIV", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
EXPECT_EQ(2000, [[webView objectByEvaluatingJavaScript:@"window.scrollY"] intValue]);
}
TEST(RequestTextInputContext, TextInteraction_FocusingReadOnlyElementShouldScrollToReveal)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
[webView stringByEvaluatingJavaScript:@"input.readOnly = true"];
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
// Focus the field using -focusTextInputContext; scroll view should scroll to reveal the focused element.
{
TextInteractionForScope scope { webView, inputElement };
EXPECT_NULL([webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_FALSE(didScroll);
}
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
EXPECT_TRUE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_FocusElementInDetachedDocument)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAllowUniversalAccessFromFileURLs:YES];
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
webViewLoadHTMLStringAndWaitForAllFramesToPaint(webView.get(), applyIframe(@"<input type='text' id='input' style='width: 100px; height: 50px;'>"));
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.tagName"]);
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
// Save a reference to the framed document (to prevent its destruction when its frame is removed)
// and remove the frame.
[webView stringByEvaluatingJavaScript:@"g_framedDocument = document.querySelector('iframe').contentDocument; document.querySelector('iframe').remove()"];
{
TextInteractionForScope scope { webView, inputElement };
EXPECT_NULL([webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
}
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"g_framedDocument.activeElement.tagName"]);
EXPECT_FALSE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_FocusingElementShouldScrollToReveal)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
// Scroll view should scroll to reveal the focused element.
{
TextInteractionForScope scope { webView, inputElement };
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_FALSE(didScroll);
}
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
EXPECT_TRUE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_FocusingElementMultipleTimesShouldScrollToReveal)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
// Scroll view should scroll to reveal the focused element.
{
TextInteractionForScope scope { webView, inputElement };
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((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_FALSE(didScroll);
}
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
EXPECT_TRUE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_FocusDefocusDisableFocusAgainShouldNotScrollToReveal)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
{
TextInteractionForScope scope { webView, inputElement };
// 1. Focus
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
// 2. Defocus
[webView stringByEvaluatingJavaScript:@"input.blur()"];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
// 3. Disable
[webView stringByEvaluatingJavaScript:@"input.disabled = true"];
// 4. Focus again; focused element should be unchanged and scroll view should not scroll.
EXPECT_NULL([webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
}
EXPECT_FALSE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_FocusDefocusFocusAgainShouldScrollToReveal)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
{
TextInteractionForScope scope { webView, inputElement };
// 1. Focus
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
// 2. Defocus
[webView stringByEvaluatingJavaScript:@"input.blur()"];
EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
// 3. Focus again; focused element should change and scroll view should scroll to reveal focused element.
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
EXPECT_FALSE(didScroll);
}
EXPECT_TRUE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_FocusingAssistedElementShouldNotScrollToReveal)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
[webView _setInputDelegate:inputDelegate.get()];
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
[webView evaluateJavaScriptAndWaitForInputSessionToChange:@"input.focus()"];
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
// Focus the field using -focusTextInputContext; scroll view should not scroll to reveal focused element.
{
TextInteractionForScope scope { webView, inputElement };
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
}
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
EXPECT_FALSE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_FocusingNonAssistedFocusedElementScrollsToReveal)
{
IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
[webView _setInputDelegate:inputDelegate.get()];
[webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyDisallow; }];
[webView stringByEvaluatingJavaScript:@"input.focus()"]; // Will not start input assistance
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
didScroll = false;
auto scrollDelegate = adoptNS([[TextInteractionScrollDelegate alloc] init]);
[webView scrollView].delegate = scrollDelegate.get();
// Focus the field using -focusTextInputContext; scroll view should scroll to reveal the focused element.
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
{
TextInteractionForScope scope { webView, inputElement };
// Will start input assistance
EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
EXPECT_FALSE(didScroll);
}
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
EXPECT_TRUE(didScroll);
}
TEST(RequestTextInputContext, TextInteraction_HighlightSelectedText)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
[webView _setInputDelegate:inputDelegate.get()];
[webView synchronouslyLoadHTMLString:applyStyle(@"<input id='input' value='hello'>")];
NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> inputElement = contexts[0];
[inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
[webView evaluateJavaScriptAndWaitForInputSessionToChange:@"input.focus()"];
[webView stringByEvaluatingJavaScript:@"input.setSelectionRange(0, 5)"];
EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
{
TextInteractionForScope scope { webView, inputElement };
[webView waitForNextPresentationUpdate];
EXPECT_EQ(1UL, [[webView selectionViewRectsInContentCoordinates] count]);
}
}
} // namespace TestWebKitAPI
#endif // PLATFORM(IOS_FAMILY)