// // 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 public init() { // ── 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) 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() }