如何感知 WebKit 页面切换

2021-10-25 10:34:40 浏览数 (1)

背景

通常在 WKWebView 打开一个页面,收到页面数据时,代理方法可感知这个时机:

代码语言:javascript复制
-webView:didCommitNavigation:
复制代码

但若是改变页面 hash (也就是位置标识符"#") 打开另一页面时,这个代理方法不会调用,也没有合适的回调接口。

从表现上说,改变 hash 会产生网页历史栈,safari 也会产生历史记录,这种场景应该是有和 -webView:didCommitNavigation: 相对应的回调,官方没做好一致性,需通过 WebKit 源码进一步探索感知方式。

源码分析

断点查看触发-webView:didCommitNavigation:调用栈如下:

代码语言:javascript复制
-> UIProcess 进程
-[AnyInstance webView:didCommitNavigation:]
WebKit::NavigationState::NavigationClient::didCommitNavigation
(IPC) WebKit::WebPageProxy::didCommitLoadForFrame

-> WebContent 进程
(IPC) Messages::WebPageProxy::DidCommitLoadForFrame
WebKit::WebFrameLoaderClient::dispatchDidCommitLoad
WebCore::FrameLoader::dispatchDidCommitLoad
WebCore::FrameLoader::receivedFirstData
复制代码

在第一次收到页面数据后,会进行网页历史栈等状态的处理,最后抛给公开代理。对于改变页面 hash 打开另一页面场景,是在同一个 Document,FrameLoader 作为专门处理页面加载的地方,应该是有处理目标页面是否是同一 Document 的代码分支,扫描一下就找到了一个可疑的函数:

代码语言:javascript复制
void FrameLoader::loadItem(...) {
    ...
    if (sameDocumentNavigation)
        loadSameDocumentItem(item);
    else
        loadDifferentDocumentItem(item, fromItem, loadType, MayAttemptCacheOnlyLoadForFormSubmissionItem, shouldTreatAsContinuingLoad);
}
复制代码

追溯函数调用链,发现-webView:didCommitNavigation:调用栈正是来自else分支,查看if分支的处理,最终会通过一个 IPC 消息发送到 APP 进程,在 APP 进程代码实现如下:

代码语言:javascript复制
void WebPageProxy::didSameDocumentNavigationForFrame(...) {
    ...
    m_navigationClient->didSameDocumentNavigation(...);
    ...
}
void NavigationState::NavigationClient::didSameDocumentNavigation(...) {
    ...
    [static_cast<id <WKNavigationDelegatePrivate>>(navigationDelegate.get()) _webView:m_navigationState->m_webView navigation:wrapper(navigation) didSameDocumentNavigation:toWKSameDocumentNavigationType(navigationType)];
}
复制代码

这里的navigationDelegate就关联了 WKWebView 的公开代理 navigationDelegate,而这个代理方法在私有代理方法列表躺着:

代码语言:javascript复制
typedef NS_ENUM(NSInteger, _WKSameDocumentNavigationType) {
    _WKSameDocumentNavigationTypeAnchorNavigation,
    _WKSameDocumentNavigationTypeSessionStatePush,
    _WKSameDocumentNavigationTypeSessionStateReplace,
    _WKSameDocumentNavigationTypeSessionStatePop,
} WK_API_AVAILABLE(macos(10.10), ios(8.0));

@protocol WKNavigationDelegatePrivate <WKNavigationDelegate>
...
- (void)_webView:(WKWebView *)webView navigation:(WKNavigation *)navigation didSameDocumentNavigation:(_WKSameDocumentNavigationType)navigationType;
...
@end
复制代码

粗略分析下源码,当 navigationType 这个枚举是 _WKSameDocumentNavigationTypeAnchorNavigation 时就表示完成了这次改变 hash 的页面切换。实现这个私有代理从源码来看是无副作用的,MR 记录在这里:bugs.webkit.org/show_bug.cg… 。

结论

所以只需要在 WKWebView 的 navigationDelegate 所属类下面实现 _webView:navigation:didSameDocumentNavigation: 方法就能捕获到改变 hash 的页面切换的操作了,和 -webView:didCommitNavigation: 配对可完整感知 WKWebView 的页面切换完成时机。

0 人点赞