我遇到了一个非常棘手的技术问题,我希望周围可能有一些 Webkit 专家。我正在为客户开发 iOS 应用程序。大多数应用程序是在 UIWebView 控制器中提供的 HTML5 内容。
大约一周前,QA 团队开始报告应用程序崩溃。上周我们每天收到大约 1 份崩溃报告。不幸的是,它们是那种无法确定一致地重现崩溃的明确步骤的崩溃。奇怪的是,其中一些崩溃报告一直在使用旧版本的 iOS 代码库——这些代码已经成功运行了几个月,而没有人注意到这种崩溃行为。
但所有崩溃情况的共同点是,它们都是针对提供最新版本 HTML Web 应用程序页面的更新后端运行的。所以看起来我们在服务器端做了一些新的事情,触发了 iOS 代码中的某些东西崩溃。
崩溃日志非常一致。这是符号化的日志:
0 WebCore 0x33147ab0 WebCore::FrameLoader::cancelledError(WebCore::ResourceRequest const&) const + 4
1 WebCore 0x33070fbe WebCore::ResourceLoader::init(WebCore::ResourceRequest const&) + 166
2 WebCore 0x33070e66 WebCore::SubresourceLoader::startLoading() + 14
3 WebCore 0x33070c4e WebCore::ResourceLoadScheduler::servePendingRequests(WebCore::ResourceLoadScheduler::HostInformation*, WebCore::ResourceLoadPriority) + 46
4 WebCore 0x33076508 WebCore::ResourceLoadScheduler::servePendingRequests(WebCore::ResourceLoadPriority) + 36
5 WebCore 0x32fd38c8 WebCore::ThreadTimers::sharedTimerFiredInternal() + 92
(大多数讨论 WebCore 中的崩溃的问题都建议您在 dealloc 方法中将 webview.delegate 设置为 nil。这似乎不是我们的问题)。
现在我有一个理论(我稍后会谈到),但我没有明确的证据。因此,我从 webkit.org 获取了源代码,并试图阅读足够多的内容以了解 WebKit 在崩溃时正在做什么。我认为我使用的 WebKit 源版本与 iOS 设备中的版本不同(我们在 5.0.1 和 5.1.1 设备中看到了这一点),关键方法似乎引用了下载资源(比如CSS 和图像),但似乎涉及到一个空 URL,所以我们最终调用了 cancelledError 方法。
然后 FrameLoader 执行此操作:
ResourceError FrameLoader::cancelledError(const ResourceRequest& request) const
{
ResourceError error = m_client->cancelledError(request);
error.setIsCancellation(true);
return error;
}
正是在这种方法中,应用程序崩溃了:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00000008
这向我表明 m_client 没有指向有效的东西。
现在,我确实有一个关于正在发生的事情的理论,只是基于直觉和间接证据。
我们的 UIWebView 有一个委托,它评估正在加载到 Web 视图中的 URL。在某些情况下,我们决定在单独的 ViewController 中启动新的 URL,如下所示:
- (BOOL)webView:(UIWebView *)source shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
...
if ([self.popupStrategy shouldPopupURL:[request URL] fromCurrent:[source.request URL]]) {
PopupTransitionViewController *popController = [self createPopupController:request];
... push it onto the navigation controller ...
}
...
}
导致此弹出策略返回 true 的关键条件之一发生在跨域链接期间。也就是说,如果用户点击链接/图标并且该链接的目标由第三方站点托管,则应用程序会在不同的 ViewController 中启动新内容(出于各种原因,包括能够获得跨域链接上的良好原生转换)。
几周前发生的服务器端更改之一是链接 href 已更新——该链接现在调用我们的主服务器,该服务器发回 HTTP 重定向,将客户端发送到第三方站点。
我在这种情况下看到的是我们的 popupStrategy 被调用了两次。第一次,它评估我们主服务器的 URL,第二次,它评估第三方 URL。在第二种情况下,该策略告诉 UIWebView 在新的 ViewController 中加载请求。我的想法是,Webkit 代码中的某些内容并不总是这样,并且通过一些奇怪的时间或诸如此类的东西,这在某种程度上导致了崩溃。
我之所以坚持这个理论,是因为它基于以前在我们的服务器代码库中不存在的新 Web 加载行为,这很容易符合症状。我对 Webkit 代码的阅读是,在引用的一些方法中,Webkit 在看到跨域请求时似乎正在做一些特殊的处理。但是崩溃不可能在提示时重现,所以我们没有更多的事情要做。但如果这个理论是正确的,我知道一个合理的解决办法。
我希望有人对 Webkit 的内部有一定的了解并可以建议:
a) Webkit 堆栈跟踪对这一理论的支持程度如何?
b)我得到的堆栈跟踪是否有任何其他人可以看到的见解?