blob: 70b47119959548398d1093ffe55ab53f09d1efd1 [file] [log] [blame]
/*
* Copyright (C) 2019 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"
#import "PlatformUtilities.h"
#import "TestCocoa.h"
#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import <WebKit/WKPreferencesRefPrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WebKit.h>
#import <WebKit/_WKTextInputContext.h>
#import <wtf/RetainPtr.h>
@implementation WKWebView (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();
}
- (BOOL)synchronouslyFocusTextInputContext:(_WKTextInputContext *)context
{
__block bool finished = false;
__block bool success = false;
[self _focusTextInputContext:context completionHandler:^(BOOL innerSuccess) {
success = innerSuccess;
finished = true;
}];
TestWebKitAPI::Util::run(&finished);
return success;
}
@end
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(WebKit, RequestTextInputContext)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
WKPreferencesSetThreadedScrollingEnabled((WKPreferencesRef)[configuration preferences], false);
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 frame]];
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 frame]];
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 frame]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[0].boundingRect);
// Basic inputs inside subframe.
[webView synchronouslyLoadHTMLString:applyIframe(@"<input type='text' style='width: 50px; height: 50px;'>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 200, 50, 50), contexts[0].boundingRect);
[webView synchronouslyLoadHTMLString:applyIframe(@"<textarea style='width: 100px; height: 100px;'></textarea>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 200, 100, 100), contexts[0].boundingRect);
[webView synchronouslyLoadHTMLString:applyIframe(@"<div contenteditable style='width: 100px; height: 100px;'></div>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 200, 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 frame]];
EXPECT_EQ(0UL, contexts.count);
[webView synchronouslyLoadHTMLString:applyStyle(@"<textarea style='width: 100px; height: 100px;' readonly>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
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: 5000px;'></div>")];
#if PLATFORM(MAC)
[webView objectByEvaluatingJavaScript:@"window.scrollTo(0, 5000);"];
#else
[webView scrollView].contentOffset = CGPointMake(0, 5000);
#endif
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
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>")];
#if PLATFORM(MAC)
[webView objectByEvaluatingJavaScript:@"window.scrollTo(0, 5000);"];
#else
[webView scrollView].contentOffset = CGPointMake(0, 5000);
#endif
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
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 frame]];
EXPECT_EQ(3UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[0].boundingRect);
EXPECT_EQ(CGRectMake(0, 50, 50, 50), contexts[1].boundingRect);
EXPECT_EQ(CGRectMake(0, 100, 50, 50), contexts[2].boundingRect);
// Nested <input>-inside-contenteditable. Only the contenteditable is considered.
[webView synchronouslyLoadHTMLString:applyStyle(@"<div contenteditable style='width: 100px; height: 100px;'><input type='text' style='width: 50px; height: 50px;'></div>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
EXPECT_EQ(1UL, contexts.count);
EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[0].boundingRect);
}
TEST(WebKit, DISABLED_FocusTextInputContext)
{
RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
WKPreferencesSetThreadedScrollingEnabled((WKPreferencesRef)[configuration preferences], false);
RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
NSArray<_WKTextInputContext *> *contexts;
[webView synchronouslyLoadHTMLString:applyStyle(@"<input id='test' type='text' style='width: 50px; height: 50px;'>")];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> originalInput = contexts[0];
EXPECT_TRUE([webView synchronouslyFocusTextInputContext:originalInput.get()]);
EXPECT_WK_STREQ("test", [webView objectByEvaluatingJavaScript:@"document.activeElement.id"]);
// The _WKTextInputContext should still work even after another request.
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
EXPECT_TRUE([contexts[0] isEqual:originalInput.get()]);
EXPECT_TRUE([webView synchronouslyFocusTextInputContext:originalInput.get()]);
// Replace the <input> with a <textarea> with script; the <input> should no longer be focusable.
[webView objectByEvaluatingJavaScript:@"document.body.innerHTML = '<textarea id=\"area\">';"];
contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
EXPECT_EQ(1UL, contexts.count);
RetainPtr<_WKTextInputContext> textArea = contexts[0];
EXPECT_FALSE([textArea isEqual:originalInput.get()]);
EXPECT_FALSE([webView synchronouslyFocusTextInputContext:originalInput.get()]);
EXPECT_TRUE([webView synchronouslyFocusTextInputContext:textArea.get()]);
EXPECT_WK_STREQ("area", [webView objectByEvaluatingJavaScript:@"document.activeElement.id"]);
// Destroy the <textarea> by navigating away; we can no longer focus it.
[webView synchronouslyLoadHTMLString:@""];
EXPECT_FALSE([webView synchronouslyFocusTextInputContext:textArea.get()]);
}