在 iOS 15.2 WebKit 支持了 COOP/COEP 响应头,满足 Cross-Origin-Opener-Policy: same-origin / Cross-Origin-Embedder-Policy: require-corp 时则 SharedArrayBuffer 可用,参考官方文档。
WASM 线程基于 Web Worker 实现,多线程数据共享若使用postMessage方案性能会非常差,为此它强依赖 SharedArrayBuffer 来实现高效数据共享。
然而在对 WebKit 进行网络拦截后,SharedArrayBuffer 竟然不可用了,需要挖一下原因。
提前放一下结论简图:
正常流程分析
以 JS 层为入口
JS 报错信息是:can’t find variable: SharedArrayBuffer,说明构造函数都没有挂载上,标准函数window.crossOriginIsolated检查发现当前也不是跨域隔离环境。
而此时 COOP/COEP 响应头都满足了跨域隔离的要求,说明出问题的点在底层,而非 JS 代码维度能解决的。
看一下window.crossOriginIsolated的底层实现:
ExceptionOr<bool> DOMWindow::crossOriginIsolated() const {
…
return localThis->crossOriginIsolated();
}
bool LocalDOMWindow::crossOriginIsolated() const {
…
return ScriptExecutionContext::crossOriginMode() == CrossOriginMode::Isolated;
}
再看一下在JSGlobalObject::init中挂载 SharedArrayBuffer 构造函数的代码:
if (Options::useSharedArrayBuffer())
putDirectWithoutTransition(vm, vm.propertyNames->SharedArrayBuffer, m_sharedArrayBufferStructure.constructor(this), static_cast<unsigned>(PropertyAttribute::DontEnum));
可见,核心落在了两个函数:
ScriptExecutionContext::crossOriginMode()
Options::useSharedArrayBuffer()
Web 进程逻辑
在 Web 进程 IPC 入口处发现:
if (xpc_dictionary_get_bool(initializerMessage, "enable-shared-array-buffer")) {
JSC::Options::initialize();
JSC::Options::AllowUnfinalizedAccessScope scope;
JSC::Options::useSharedArrayBuffer() = true;
optionsChanged = true;
}
在 Web 进程initializeWebProcess函数发现:
ScriptExecutionContext::setCrossOriginMode(parameters.crossOriginMode);
并且这些值在 Web 进程期间是没有改变的,所以需要找到初始化 Web 进程的逻辑。
UI 进程逻辑
在ProcessLauncher::finishLaunchingProcess函数发现:
if (m_client->shouldEnableSharedArrayBuffer())
xpc_dictionary_set_bool(bootstrapMessage.get(), "enable-shared-array-buffer", true);
经查m_client就是熟悉的 WebProcessProxy 类,其实现为:
bool shouldEnableSharedArrayBuffer() const final {
// m_crossOriginMode 默认是 Shared
return m_crossOriginMode == WebCore::CrossOriginMode::Isolated;
}
排查后发现只有一个链路可以让 WebProcess 支持CcrossOrignMode::Isolated:
void WebPageProxy::triggerBrowsingContextGroupSwitchForNavigation(…) {
…
if (browsingContextGroupSwitchDecision == BrowsingContextGroupSwitchDecision::NewIsolatedGroup)
processForNavigation = m_legacyMainFrameProcess->protectedProcessPool()->createNewWebProcess(protectedWebsiteDataStore().ptr(), lockdownMode, WebProcessProxy::IsPrewarmed::No, CrossOriginMode::Isolated);
else
processForNavigation = m_legacyMainFrameProcess->protectedProcessPool()->processForRegistrableDomain(protectedWebsiteDataStore(), responseDomain, lockdownMode, protectedConfiguration());
…
}
满足browsingContextGroupSwitchDecision == BrowsingContextGroupSwitchDecision::NewIsolatedGroup条件,会新建支持CrossOriginMode::Isolated的 WebProcess 去打开页面。
Network 进程逻辑
最后找到核心逻辑:
static BrowsingContextGroupSwitchDecision toBrowsingContextGroupSwitchDecision(…) {
…
if (currentCoopEnforcementResult->crossOriginOpenerPolicy.value == CrossOriginOpenerPolicyValue::SameOriginPlusCOEP)
return BrowsingContextGroupSwitchDecision::NewIsolatedGroup;
return BrowsingContextGroupSwitchDecision::NewSharedGroup;
}
看到熟悉的 COOP/COEP 响应头判定:
CrossOriginOpenerPolicy obtainCrossOriginOpenerPolicy(const ResourceResponse& response)
…
auto ensureCOEP = [&coep, &response]() -> CrossOriginEmbedderPolicy& {
if (!coep)
// 这是从响应header里面找数据
coep = obtainCrossOriginEmbedderPolicy(response, nullptr);
return *coep;
};
…
if (policyString->string() == "same-origin"_s) {
auto& coep = ensureCOEP();
if (coep.value == CrossOriginEmbedderPolicyValue::RequireCORP || (headerName == HTTPHeaderName::CrossOriginOpenerPolicyReportOnly && coep.reportOnlyValue == CrossOriginEmbedderPolicyValue::RequireCORP))
value = CrossOriginOpenerPolicyValue::SameOriginPlusCOEP;
else
value = CrossOriginOpenerPolicyValue::SameOrigin;
} else if (policyString->string() == "same-origin-allow-popups"_s)
value = CrossOriginOpenerPolicyValue::SameOriginAllowPopups;
…
当满足条件时,会触发前面的 UI 进程逻辑,从而让 Web 进程支持跨域隔离环境。
前面的触发只会来自于 Network 进程的 NetworkResourceLoader 网络请求回包链路。
结论
而当 WebKit 网络拦截使用的 WKURLSchemeHandler 方案,网页的网络请求会直接 IPC 到 UI 进程实现,跨过 Network 进程的链路,所以无法触达前面的 COOP/COEP 判定链路。
也不能怪社区工程师没有去兼容 WKURLSchemeHandler 链路,因为原则上说这并不是拿给开发者去实现 HTTP 请求的。
得益于 WKURLSchemeHandler 设计是基于 WKWebViewConfiguration 实例的,各个 WKWebView 实例可做到互不干扰,所以想使用 Sharedarraybuffer 最简单方案就是使用纯净的 WKWebView。