现象:大厅 H5 能正确拿到定位,子游戏拿不到。已静态排除「子游戏未注册 handler」与「回调错发给大厅」两个猜测(BridgeBus 是 per-WebView 实例、 handlers 是实例状态,反向 call 捕获各自的 bridge,物理上不可能串台; 下载真实子游戏 zip 比对后确认 H5 侧大厅/子游戏逻辑完全对称)。 为定位真正的失败层,在各组件边界加日志(纯诊断,不改任何行为): - BridgeBus 加 label(lobby / subGame)区分来源,记录 ← H5 调用、 → H5 反向 call、native handler 未注册、evaluateJavaScript 失败 - sendToJS 改为返回 'ok'/'no-bridge',H5 侧 bridge 未就绪导致的静默丢包 现在会打错误日志(原实现 `if (window.X)` 直接丢弃,完全不可见) - LocationService 记录 requestOnce 序号 / shared manager id / requestLocation 的 BOOL 返回值 / completionBlock 是否回来 / stop() 调用 —— 高德文档明确 requestLocation 返回 NO 时 completionBlock 永不调用, 当前代码忽略该返回值会让 continuation 永挂,H5 连 errorCode 12 都收不到 - H5ErrorRelay 补 console.warn 中继,让 WVJB「H5 侧无对应 handler」的 warn 能出现在 Xcode console 契约影响:无。仅新增日志与可选 label 参数,桥接接口名 / 字段名 / 数据结构 均未变动(docs/H5-Native-Contract.md §3.1[20]/ §3.2[6]不变)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMPCLfsuxvwgMotzsb67QH
119 lines
5.5 KiB
Swift
119 lines
5.5 KiB
Swift
//
|
||
// BridgedWebView.swift
|
||
// ylgamehall
|
||
//
|
||
// WKWebView + WebViewJavascriptBridge.js 注入 + BridgeBus 一体封装。
|
||
// 详见 docs/H5-Native-Implementation-Design.md §3.3 / 契约 §4.1。
|
||
//
|
||
|
||
import UIKit
|
||
import WebKit
|
||
|
||
/// 内嵌 H5 的 WebView 组件,已挂好桥与必要配置。WebContainerViewController(Phase 1.10)
|
||
/// 通过把本 view 嵌入自己的内容区,再加上 16:9 letterbox / 生命周期管理。
|
||
@MainActor
|
||
public final class BridgedWebView: UIView {
|
||
|
||
public let webView: WKWebView
|
||
public let bridge: BridgeBus
|
||
|
||
/// - Parameter label: 容器标识(`lobby` / `subGame`),仅用于桥诊断日志区分来源,
|
||
/// 不参与任何契约行为。
|
||
public init(label: String = "webview") {
|
||
// ── WKWebViewConfiguration(契约 §4.1)─────────────────
|
||
let configuration = WKWebViewConfiguration()
|
||
configuration.defaultWebpagePreferences.allowsContentJavaScript = true
|
||
configuration.preferences.javaScriptCanOpenWindowsAutomatically = false
|
||
configuration.preferences.minimumFontSize = 10
|
||
configuration.allowsInlineMediaPlayback = true
|
||
// H5 背景音自播:默认 .all 要求该 WKWebView 实例有 user gesture 才放行媒体,
|
||
// 大厅页用户点击过没问题;子游戏 push 后立即 loadFileURL 自播背景音被静默拦截,
|
||
// 表现为"子游戏 webview 没声音"。设空集让所有媒体都不要求 user gesture。
|
||
configuration.mediaTypesRequiringUserActionForPlayback = []
|
||
configuration.processPool = SharedProcessPool.shared
|
||
|
||
// ── 自定义 URL scheme 注册(Phase 1.F)────────────────────
|
||
// 大厅 + 子游戏的 H5 通过 `ylgame://h5/...` 加载,由 AppSchemeHandler 反向
|
||
// 映射到沙盒文件。理由(详见 docs/H5-Native-Implementation-Design.md §17):
|
||
// - file:// origin 是 null,XHR/fetch 受限;自定义 scheme 是稳定 origin
|
||
// - 大厅 + 所有子游戏共用 `ylgame://h5` 单 host → same-origin →
|
||
// 共享 localStorage(与现状 file:// 等价)
|
||
// - 为未来 Cocos 子游戏 H5 适配铺路
|
||
configuration.setURLSchemeHandler(
|
||
AppSchemeHandler.shared,
|
||
forURLScheme: AppScheme.scheme
|
||
)
|
||
|
||
// ── 在 documentStart 注入 WebViewJavascriptBridge.js ─────
|
||
// .atDocumentStart 保证 H5 业务代码运行时 window.WebViewJavascriptBridge 已就绪
|
||
if let url = Bundle.main.url(forResource: "WebViewJavascriptBridge",
|
||
withExtension: "js"),
|
||
let source = try? String(contentsOf: url, encoding: .utf8) {
|
||
let userScript = WKUserScript(
|
||
source: source,
|
||
injectionTime: .atDocumentStart,
|
||
forMainFrameOnly: true
|
||
)
|
||
configuration.userContentController.addUserScript(userScript)
|
||
} else {
|
||
// 编译期保证文件存在;运行时若缺,留 print 便于排查
|
||
print("[BridgedWebView] ERROR: WebViewJavascriptBridge.js 未在 Bundle 找到")
|
||
}
|
||
|
||
// ── H5 错误中继(Phase 9.2,原则 A 零修改 H5)─────────────
|
||
// 把 console.error / window.onerror / unhandledrejection 通过独立
|
||
// 的 webkit.messageHandlers.h5error 桥到原生 print,开发期减少盲点。
|
||
H5ErrorRelay.shared.install(into: configuration.userContentController)
|
||
|
||
// ── 创建 WKWebView + BridgeBus ─────────────────────────
|
||
let webView = WKWebView(frame: .zero, configuration: configuration)
|
||
#if DEBUG
|
||
if #available(iOS 16.4, *) {
|
||
webView.isInspectable = true
|
||
}
|
||
#endif
|
||
let bridge = BridgeBus(webView: webView,
|
||
controller: configuration.userContentController,
|
||
label: label)
|
||
|
||
self.webView = webView
|
||
self.bridge = bridge
|
||
|
||
super.init(frame: .zero)
|
||
|
||
// ── ScrollView 配置(契约 §4.1,禁手势滚动 / 弹性 / 自动 inset)──
|
||
let scroll = webView.scrollView
|
||
scroll.bounces = false
|
||
scroll.isScrollEnabled = false // ⚠️ 漏则 H5 上下滑动失控(契约硬约束)
|
||
scroll.contentInsetAdjustmentBehavior = .never
|
||
scroll.showsVerticalScrollIndicator = false
|
||
scroll.showsHorizontalScrollIndicator = false
|
||
|
||
// ── 布局:WebView 撑满 self ─────────────────────────────
|
||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||
addSubview(webView)
|
||
NSLayoutConstraint.activate([
|
||
webView.topAnchor.constraint(equalTo: topAnchor),
|
||
webView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||
webView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||
webView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||
])
|
||
|
||
backgroundColor = .black
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
public required init?(coder: NSCoder) {
|
||
fatalError("BridgedWebView does not support init(coder:)")
|
||
}
|
||
}
|
||
|
||
// MARK: - 跨 WebView 共享的进程池
|
||
|
||
/// 大厅 / 子游戏 / 弹层共用同一 WKProcessPool,共享 Cookie / 资源缓存,
|
||
/// 启动加速。详见 Design §3.5。
|
||
@MainActor
|
||
public enum SharedProcessPool {
|
||
public static let shared = WKProcessPool()
|
||
}
|