WKWebView支持代码focus唤起键盘

0x00 背景

iOS H5 Hybrid应用中,让对应的input或TextArea获得focus状态,自动唤起系统键盘,方便用户直接输入,是一种比较常见的场景。

而在iOS的移动端Web页中,这一操作是有限制的。

苹果为了安全,在iOS的WebView里,需要用户实际点击到input输入区域后,才会触发聚焦并自动唤起键盘。使用js代码来设置聚焦(input.focus)默认是不生效的。

0x01 处理方法

在UIWebView里,系统提供了一个keyboardDisplayRequiresUserAction属性,这个属性默认为YES,也就是说键盘的出现必须要用户交互。

在UIWebView里,我们可以把keyboardDisplayRequiresUserAction设置为NO,这样就可以通过js代码来自动聚焦并唤起键盘了。

注:UIWebView已经被苹果废弃了,APP也不再使用UIWebView了。

到了WKWebView里,苹果为了强安全,取消了这个属性(在Public的 API里已经找不到了),默认键盘的出现必须要用户交互。

我们知道,WKWebViewd的内核是WebKit的,而WebKit是开源的,WebKit源码:

https://trac.webkit.org/browser

https://github.com/WebKit/webkit

有了源码,就有可能定位源码里的控制逻辑,通过runtime的方式,动态hook WebKit,实现类似 UIWebView中keyboardDisplayRequiresUserAction = NO 的行为。

通过Google和查看WebKit的内核源码,最新的代码里控制这一逻辑的方法签名是:
_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:

注意:不同的iOS系统版本,这一方法签名可能是不一样的,所以需要对iOS系统版本做兼容。

兼容到最新的iOS13.5.1的hack参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@interface CVTWebViewHack : NSObject

+ (void)allowDisplayingKeyboardWithoutUserAction;

@end

------------------------------------------------------

#import <objc/runtime.h>

@implementation CVTWebViewHack

+ (void)allowDisplayingKeyboardWithoutUserAction {
Class class = NSClassFromString(@"WKContentView");
NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) {
SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:");
Method method = class_getInstanceMethod(class, selector);
IMP original = method_getImplementation(method);
IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
});
method_setImplementation(method, override);
} else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
Method method = class_getInstanceMethod(class, selector);
IMP original = method_getImplementation(method);
IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
});
method_setImplementation(method, override);
} else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
Method method = class_getInstanceMethod(class, selector);
IMP original = method_getImplementation(method);
IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
});
method_setImplementation(method, override);
} else {
SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
Method method = class_getInstanceMethod(class, selector);
IMP original = method_getImplementation(method);
IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
});
method_setImplementation(method, override);
}
}

@end

0x02 风险分析

由于是hook了系统的私有方法,当iOS系统升级时,有失效的风险,代码实现上需保证即使找不到方法签名也不能引起程序崩溃。
由于hook的私有方法签名字符串,有可能在AppStore审核期间被拒(2020年5~6月实测上架不会被拒绝)。

0x03 授人以渔,iOS新系统适配

假设未来的iOS14版本,苹果又调整了下面这个方法签名:
_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:
最简单的办法就是到这里 https://trac.webkit.org/browser, 根据旧的方法签名关键字去查询变更记录,找到新的方法签名和iOS系统版本后,调整为新的方法签名。

webkit source

欢迎大家到我的公众号留言交流

公众号