Files
youle_app_ios_v2/ylgamehall/Source/WebView/BridgedWebView.swift
T
joywayerandClaude Opus 5 3f8b382ad3 加桥/定位边界诊断日志,排查子游戏收不到 getlocationinfo
现象:大厅 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
2026-08-07 01:54:18 +08:00

119 lines
5.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// BridgedWebView.swift
// ylgamehall
//
// WKWebView + WebViewJavascriptBridge.js 注入 + BridgeBus 一体封装。
// 详见 docs/H5-Native-Implementation-Design.md §3.3 / 契约 §4.1。
//
import UIKit
import WebKit
/// 内嵌 H5 的 WebView 组件,已挂好桥与必要配置。WebContainerViewControllerPhase 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 是 nullXHR/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()
}