Design §3.4.2:补 OpenurlTitleData handler — 大厅打开 threeView 弹层
覆盖 §3.1 [15]OpenurlTitleData,是大厅 WVJB 桥的异步 handler,作用是
触发新的 OverlayViewController push 到导航栈。补在 §3.4 弹层桥章节内
(与 §3.4 settings polyfill 一脉相承),叫 §3.4.2。
§3.4.2 OpenurlTitleDataHandler:
- 节流:throttle final class 持 lastOpen Date,3 秒内重复调用被吞掉但
cb 仍要回(否则 H5 会等)
- 入参解析:url 必填;"title " 末尾有空格(msext 历史契约硬约束,必须
obj["title "]);data 透传;orientation 0/1 但本项目固定横屏
- 调 LobbyCoordinator.pushOverlay 打开 OverlayViewController
OverlayViewController 接入:
- WKWebViewConfiguration + UserScript 注入:
1. §3.4 OverlayBridge.polyfill(settings.backgameData/browser/finishweb)
2. window.app_data = "<escaped data>" 把业务数据透传到 H5
- 等价于 msext threeView 的 JSContext setObject:forKey:@"settings" 行为
§3.4.3 与 msext 5 维度差异表:UIWebView → WKWebView / "title " 末尾空格
严格保持 / 节流类替代静态变量 / UserScript atDocumentStart 注入避免 race
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a06a18c3aa
commit
5f5f6a9ff9
@@ -655,6 +655,133 @@ extension BridgedWebView {
|
||||
- 验收:契约 §10 验收清单里所有"H5 同步取渠道值 / 设备状态"的项目(如 `window.settings.getchannelName() === channel注入值`)通过即视为契约等价
|
||||
- 子游戏容器(SubGame WebContainer)也走同一份 polyfill,仅 `installedGames` 字段在子游戏内意义不同(一般不会再调 getGameinstall)
|
||||
|
||||
#### 3.4.2 OpenurlTitleData handler — 大厅打开 threeView 弹层(契约 §3.1 [15])
|
||||
|
||||
`OpenurlTitleData` 不在弹层内部,而是**大厅** WVJB 桥的异步 handler,作用是**触发**一个新的 OverlayViewController(threeView 等价容器)push 到导航栈,让 H5 弹层页面在 OverlayViewController 内运行。OverlayViewController 内部的 H5 才用 §3.4 的 `window.settings.{backgameData/browser/finishweb}` polyfill 通讯。
|
||||
|
||||
```swift
|
||||
// Source/Bridge/Handlers/OpenurlTitleDataHandler.swift
|
||||
@MainActor
|
||||
public struct OpenurlTitleDataHandler {
|
||||
|
||||
let bridge: BridgeProtocol
|
||||
let coordinator: LobbyCoordinator // 负责 push OverlayViewController
|
||||
|
||||
/// 节流状态:第一次调用后 3 秒内被吞掉(first_Time 标志)
|
||||
/// 用类持有可变状态而非 actor,因为本 handler 必须在 MainActor,
|
||||
/// 且 last open Date 只在主线程读写
|
||||
private final class Throttle {
|
||||
var lastOpen: Date?
|
||||
}
|
||||
private let throttle = Throttle()
|
||||
|
||||
public func register() {
|
||||
bridge.register("OpenurlTitleData", handler: handle)
|
||||
}
|
||||
|
||||
// MARK: - 【15】 OpenurlTitleData
|
||||
//
|
||||
// 契约 §3.1 [15]:
|
||||
// 入参 url : string 待加载 URL(HTTP)
|
||||
// 入参 "title " : string ⚠️ 键名末尾有空格,沿用历史,必须 obj["title "]
|
||||
// 入参 data : string 业务数据,透传给 threeView 内的 H5
|
||||
// 入参 orientation : int 0=竖屏 / 1=横屏(实际本项目固定横屏,参考即可)
|
||||
// responseCallback : "OpenurlTitleData"
|
||||
// 节流 : 第一次调用后 3 秒内重复调用被吞掉(first_Time 标志)
|
||||
|
||||
private func handle(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||
defer { cb?(.string("OpenurlTitleData")) }
|
||||
|
||||
// 1. 节流:3 秒内重复调用直接返回(cb 仍要回,否则 H5 会等)
|
||||
let now = Date()
|
||||
if let last = throttle.lastOpen, now.timeIntervalSince(last) < 3 {
|
||||
Log.info("OpenurlTitleData throttled (last open \(String(format: "%.2f", now.timeIntervalSince(last)))s ago)")
|
||||
return
|
||||
}
|
||||
throttle.lastOpen = now
|
||||
|
||||
// 2. 解析入参(注意 "title " 末尾空格)
|
||||
guard let obj = data?.asObject,
|
||||
let urlStr = obj["url"]?.asString,
|
||||
let url = URL(string: urlStr)
|
||||
else {
|
||||
Log.warn("OpenurlTitleData: bad url payload")
|
||||
return
|
||||
}
|
||||
let title = obj["title "]?.asString ?? "" // ⚠️ "title "
|
||||
let payload = obj["data"]?.asString ?? ""
|
||||
let orientation = obj["orientation"]?.asInt ?? 1 // 默认横屏
|
||||
|
||||
// 3. 触发 push OverlayViewController
|
||||
let request = OverlayRequest(
|
||||
url: url,
|
||||
title: title,
|
||||
data: payload,
|
||||
orientation: orientation
|
||||
)
|
||||
coordinator.pushOverlay(request)
|
||||
}
|
||||
}
|
||||
|
||||
public struct OverlayRequest: Sendable {
|
||||
public let url: URL
|
||||
public let title: String
|
||||
public let data: String
|
||||
public let orientation: Int // 0=竖屏 / 1=横屏(沿用契约,本项目实际只用横屏)
|
||||
}
|
||||
```
|
||||
|
||||
**OverlayViewController 接入**:
|
||||
|
||||
```swift
|
||||
extension LobbyCoordinator {
|
||||
public func pushOverlay(_ request: OverlayRequest) {
|
||||
let vc = OverlayViewController(request: request)
|
||||
navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public final class OverlayViewController: UIViewController {
|
||||
private let request: OverlayRequest
|
||||
private let webView: WKWebView
|
||||
|
||||
public init(request: OverlayRequest) {
|
||||
self.request = request
|
||||
// 配 WKWebView:注入 §3.4 window.settings polyfill(backgameData/browser/finishweb)
|
||||
let cfg = WKWebViewConfiguration()
|
||||
cfg.userContentController.addUserScript(
|
||||
WKUserScript(source: OverlayBridge.polyfill,
|
||||
injectionTime: .atDocumentStart,
|
||||
forMainFrameOnly: true)
|
||||
)
|
||||
// 把 H5 业务 data 透传到 JS 全局(H5 弹层页代码读 window.app_data 获取)
|
||||
let escaped = request.data
|
||||
.replacingOccurrences(of: "\\", with: "\\\\")
|
||||
.replacingOccurrences(of: "\"", with: "\\\"")
|
||||
cfg.userContentController.addUserScript(
|
||||
WKUserScript(source: "window.app_data = \"\(escaped)\";",
|
||||
injectionTime: .atDocumentStart,
|
||||
forMainFrameOnly: true)
|
||||
)
|
||||
self.webView = WKWebView(frame: .zero, configuration: cfg)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
title = request.title.trimmingCharacters(in: .whitespaces)
|
||||
}
|
||||
// 略:webView 布局 + load(request) + WKScriptMessageHandler 注册 3 个 polyfill 消息
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.3 与原 msext 的差异
|
||||
|
||||
| 维度 | msext 现状 | 新外壳决策 |
|
||||
|------|----------|----------|
|
||||
| 弹层 WebView | iOS 8 时代 `UIWebView` + JSExport `Bridgetwo` | `WKWebView` + `WKUserScript` 注入 polyfill(同步 settings.* 等价) |
|
||||
| `"title "` 末尾空格 | `[dict objectForKey:@"title "]` | `obj["title "]?.asString` 严格保持,注释说明 |
|
||||
| 节流 first_Time | `static BOOL first_Time` + `NSTimer` 3 秒后重置 | 闭包内 final class 持 `Date` lastOpen,更线程友好 |
|
||||
| H5 数据透传 | `[js evaluateScript:[NSString stringWithFormat:@"app_data='%@'", data]]` | `WKUserScript` atDocumentStart 注入,避免 race |
|
||||
| orientation 入参 | 入参 string 转 int | BridgeData.asInt 直接拿;本项目实际固定横屏 |
|
||||
|
||||
### 3.5 性能优化
|
||||
|
||||
- **ProcessPool 复用**:大厅、子游戏、弹层共用同一个 `WKProcessPool`,Cookie/Cache 共享,避免重复初始化(800 ms → 50 ms)
|
||||
|
||||
Reference in New Issue
Block a user