# H5 调试指南（Safari Web Inspector）

> **定位**：本文档面向开发者，描述开发期调试 H5（大厅 / 子游戏 / 弹层）网页输出的标准方法。
>
> **适用范围**：仅 Debug 编译；Release 包不受影响（详见 §6 安全性说明）。
>
> **不在本文档范围内**：
> - 桥接接口契约 → `H5-Native-Contract.md`
> - 业务功能验收清单 → `Verification-Checklist.md`
> - 原生 `os.log` 日志查看（直接看 Xcode 底部输出窗口即可）

---

## 1. 原理与适用范围

WebKit 自 iOS 16.4 起把 `WKWebView` 的网页检查器开关收敛到了 `isInspectable` 属性，**默认关闭**。打开后，该 `WKWebView` 加载的页面会被 Mac Safari 的"开发"菜单识别为可调试目标，等价于在浏览器里按 F12 打开 DevTools，能力包括：

- **Console**：`console.log/info/warn/error/trace` 全部可见；可在控制台中直接 `evaluate` JS 表达式
- **Sources**：查看 H5 包内所有 .js / .html / .css 源码；下断点、单步、Watch 表达式
- **Network**：所有 XHR / fetch / 图片 / 字体请求，请求头/响应/Timing 全可见（含 `file://` 本地资源）
- **Elements**：DOM 树、计算后样式、动画检视
- **Storage**：localStorage / sessionStorage / Cookies / IndexedDB
- **Timelines**：CPU / 渲染 / 网络时间线

**与 Xcode 控制台关系**：Safari Web Inspector 看的是 H5 网页里的 console（JS 端运行结果）；Xcode 控制台看的是原生 Swift `print` / `os.log` 输出。两者互补。

---

## 2. 工程内的代码改动（已落地，无需重做）

代码侧已经在两处 `WKWebView` 创建点加好了 `#if DEBUG` 守卫的 inspectable 开关：

- `ylgamehall/Source/WebView/BridgedWebView.swift` ：大厅 / 子游戏共用的 `WKWebView`
- `ylgamehall/Source/WebView/OverlayViewController.swift` ：弹层（msext `threeView` 等价容器）的 `WKWebView`

补丁形态（两处一致）：

```swift
let webView = WKWebView(frame: .zero, configuration: configuration)
#if DEBUG
if #available(iOS 16.4, *) {
    webView.isInspectable = true
}
#endif
```

**重要约束**：

- **`#if DEBUG` 守卫不可去除** —— Release 包绝对不能让生产 IPA 暴露 Web Inspector，避免被外部审阅 JS 源码 / Storage / 注入表达式
- **`@available(iOS 16.4, *)` 兼容守卫不可去除** —— 本项目最低 iOS 15.6，低于 16.4 的设备走 `else` 分支（即不开启 inspectable），新设备才走真路径
- 新增任何第 4 个 `WKWebView` 创建点时，**同样补这 6 行**，否则该 WebView 在 Safari 里看不到

---

## 3. 前置准备

### 3.1 Xcode 端

确认 Scheme Run 配置是 Debug（默认就是）：

- 菜单 **Product → Scheme → Edit Scheme...**（`⌘<`）→ 左侧 **Run** → 右侧 **Info** 标签页
- **Build Configuration = Debug** ✅

### 3.2 Mac Safari 端（一次性）

1. Safari 顶部 → **Safari → 设置（`⌘,`）→ 高级**
2. 勾选最下方 **"在菜单栏中显示开发菜单"**
3. 关闭设置窗口，菜单栏出现"开发"项

### 3.3 真机端（一次性，仅真机需要；模拟器跳过）

iPhone 上：**设置 → Safari → 高级 → 网页检查器 = 开**。

> ⚠️ 这个"网页检查器"开关与 Safari 浏览器自身的网页检查无关，是 iOS 18+ 起 Apple 把 `WKWebView` 的可检视性也纳入了这个开关。**如果不开，Mac Safari 的"开发"菜单里看不到该设备的 H5 页面。**

---

## 4. 标准操作流程

### 4.1 启动 Debug build

1. Xcode 顶部 device picker → 选择目标设备/模拟器
2. `⌘R` 启动
3. 等大厅 H5 加载完成（屏幕上看到大厅 UI）

### 4.2 打开 Web Inspector

Mac Safari 顶部菜单 → **开发 → [设备名] → [页面标题或 URL]** → Web Inspector 浮窗弹出。

- 模拟器：菜单层级类似 `开发 → Simulator – iPhone 16 Pro → file:///.../index.html`
- 真机：`开发 → 你的 iPhone 名字 → file:///.../index.html`

### 4.3 看 console 输出

Web Inspector 顶部 tab → **Console** → 实时显示 H5 端的 `console.*` 输出 + JS 异常堆栈。

支持直接在底部输入框运行表达式，例如：

```js
window.WebViewJavascriptBridge          // 查看桥对象
typeof app_gameid                        // 查看预注入变量
bridge.callHandler('getphoneInfo', null, r => console.log(r))  // 触发桥调用
```

### 4.4 关闭 Web Inspector

直接关掉浮窗即可，不影响 app 运行。下次需要时重新打开。

---

## 5. 多 WebView 场景

本项目同时存在最多 3 个 `WKWebView` 实例：

| 场景 | 创建位置 | 何时存在 |
|------|---------|---------|
| 大厅 | `WebContainerViewController` → `BridgedWebView` | App 全程 |
| 子游戏 | `SubGameViewController` → `BridgedWebView` | push 进子游戏后，pop 销毁 |
| 弹层 | `OverlayViewController` 自建 `WKWebView` | present 显示中，dismiss 销毁 |

Safari "开发" 菜单会把每个**已加载页面**的 `WKWebView` 列为**独立一项**。

### 5.1 关键 gotcha：Develop 菜单不会自动刷新 ⭐

进入子游戏 / 打开弹层之后，**已经展开过的"开发"菜单不会自动刷新**，新出现的 WebView 不会冒出来。

**做法**：

- 子游戏 push / 弹层 present 完成后
- 回 Mac，**先点其它菜单**（如 文件、编辑），让"开发"菜单收起
- 再展开 **开发 → [设备名]** → 此时新 WebView 才会列出来

### 5.2 多条目区分

如果列表里多个条目 `<title>` 相同 / 都显示为 `file://...index.html`，分不清哪个是哪个：

- **鼠标 hover 每个条目** → iPhone/模拟器屏幕上对应的 `WKWebView` 会**高亮蓝色边框**
- 用这个方法定位到目标 WebView，再点击进入

### 5.3 弹层 dismiss 后条目消失

弹层关闭后 `OverlayViewController` 释放，其 `WKWebView` 被回收，菜单条目随之消失。**必须在弹层显示着的时候调试**，关掉就来不及了。

---

## 6. 安全性 / Release 包说明

- `#if DEBUG` 守卫保证只有 Xcode 的 Debug 配置启用 `isInspectable = true`
- 渠道分发 / 企业签包都用 Release 配置 → 编译期就把 inspectable 那 5 行剔除 → 生产 IPA 没有任何"可检视性"信号
- 即便用户的 iPhone 开了"网页检查器"开关、Mac Safari 也开了"开发"菜单，Release 包的 WebView **依然不会出现在"开发"菜单**
- 因此**生产环境无需任何额外清理动作**，提交前也不用手动关任何开关

---

## 7. 常见问题 FAQ

### Q1：Mac Safari "开发"菜单里完全看不到我的设备

按概率排查：

1. **菜单本身没开** → §3.2 把"在菜单栏中显示开发菜单"勾上
2. **真机未开"网页检查器"** → §3.3 设置 → Safari → 高级
3. **真机未连线 / 未信任电脑** → 数据线重插、iPhone 上重新点"信任"
4. **跑的不是 Debug build** → §3.1 Scheme → Run → Build Configuration = Debug
5. **iOS 版本 < 16.4** → 不支持，换个新设备 / 模拟器
6. **Mac 与 iPhone 不在同一 Wi-Fi** → 仅无线调试需要；有线优先
7. **`isInspectable` 补丁丢了** → grep `isInspectable` 应该有 2 处命中，否则被回滚了

### Q2：只看到大厅，子游戏 / 弹层看不到

→ §5.1 关闭"开发"菜单再重开。99% 是这个原因。

### Q3：能看到条目但 Console 是空的

**首要原因：H5 自带的调试总开关出厂是关的** ⭐

`gamehall.zip` → `js/01_SubGame/00_SubGame_Config.js:1-12`：

```js
Game_Config.Debugger={
    isDebugger : false,   // debugger模式下会将所有收发的包输出到控制台(正式发布改为false)
    ...
```

整包 220 处 `console.log`，82 处已被 `//` 注释掉，剩下的**高频日志全部挡在这个开关后面**：

| 位置 | 内容 |
|------|------|
| `js/00_Surface/09_Net.js:47` | `发送数据：...` |
| `js/00_Surface/12_Logic.js:210` | `接收数据：...` |
| `js/00_Surface/12_Logic.js:463 / 1317` | 包体输出 |

没被挡的只剩 `12_Logic.js:784/829/870/921` 的 `Connect:...`，那几行在大厅启动建 websocket 时就打完了，等你挂上 Inspector 早已过去。

**打开方式（不改 H5 一个字节，符合原则 A）** —— 在 Web Inspector 控制台运行：

```js
Game_Config.Debugger.isDebugger = true
```

这是运行时改内存里的对象属性，不碰 `gamehall.zip`、不碰任何文件；杀进程重启即恢复 `false`。

> ⚠️ **不要去改 `Resources/gamehall.zip` 里的这个 `false`** —— 那是 H5 修改，违反原则 A，且会跟着渠道包发出去。

其它可能原因：

- Web Inspector 打开前的旧日志没被记录 → 在 Web Inspector 已经打开的状态下**触发一次** H5 操作（点按钮、切 tab）再看
- Console 顶部过滤器把日志级别过滤掉了 → 确认 All Levels 都选中
- 连错 WebView（3 个实例 title 都是 `gameabc`）→ 敲 `typeof Game_Config`，返回 `"undefined"` 说明不是大厅那个，按 §5.2 用 hover 高亮法重选
- H5 端确实没有 `console.*` 调用（业务在沉默运行，没问题就不输出）

### Q4：Sources 里看不到 .js 源码

- 检查 H5 zip 是否在沙盒里正常解压（`AppDataWriter.swift` 写入的 `app_*.js` 也应出现）
- 如果某文件被打包成 minified / 不带 sourcemap，源码可读性差 —— 这是上游 H5 构建问题，原生侧无能为力

### Q5：Network 标签里看到一堆奇怪的 `wkbridge://__bridge_loaded__` 请求

这是 `WebViewJavascriptBridge` JS 端用 iframe + `src` 协议触发原生回调的机制，**正常行为，不要去掉**。详见 `Resources/JS/WebViewJavascriptBridge.js`。

### Q6：Safari 提示 "Web Inspector cannot connect"

- 重连数据线 / 重启 Safari / 重启 Xcode
- iPhone 16+ 偶发协议握手失败，重启 iPhone 通常恢复

---

## 8. 何时本方法不够用

Safari Web Inspector 是开发期主力，但有它够不到的场景：

- **真机 + 用户已在生产环境**：Release 包没有 inspectable；需要的是事后日志，参考原生侧的崩溃 / 错误上报（项目目前无 crash 监控，依赖用户反馈）
- **断网 / 弱网下的 H5 表现**：用 Mac 上的 Network Link Conditioner，或模拟器 Features → Network Link Conditioner
- **不想开 Safari、只在 Xcode 控制台看 H5 输出**：原生侧的 `H5ErrorRelay`（`Source/Bridge/H5ErrorRelay.swift`）已覆盖，见下面 §8.1
- **断点调试原生 Swift 与 H5 同时跑**：Xcode + Safari Web Inspector 同时打开，两边各自下断点互不影响

### 8.1 H5 console → Xcode 控制台（`H5ErrorRelay`）

`Source/Bridge/H5ErrorRelay.swift` 用 `WKUserScript(.atDocumentStart)` hook 住 console 与全局异常，通过 `webkit.messageHandlers.h5error` 转发到原生。**不改 H5 一行**（原则 A）。

前缀里的 `lobby` / `subGame` 是 `BridgedWebView(label:)`，用来区分同时存在的多个 WebView——也用来反推该在 Safari「开发」菜单里选哪个条目。

| 来源 | Xcode 输出前缀 | 落 `Documents/diag.log` | 构建配置 |
|-------|---------------|------------------------|---------|
| `console.log` | `[H5:lobby console.log]` | ❌ | **仅 Debug** |
| `console.info` | `[H5:lobby console.info]` | ❌ | **仅 Debug** |
| `console.warn` | `[H5:lobby console.warn]` | ✅ | Debug + Release |
| `console.error` | `[H5:lobby console.error]` | ✅ | Debug + Release |
| 未捕获 JS 异常 | `[H5:lobby onerror]` | ✅ | Debug + Release |
| 未处理的 Promise rejection | `[H5:lobby unhandledrejection]` | ✅ | Debug + Release |
| 资源 404（`<script>`/`<img>`/`<audio>`） | `[H5:lobby resourceerror]` | ✅ | Debug + Release |
| WebView 首次/提交后加载失败 | `[WebView:lobby] …加载失败` | ✅ | Debug + Release |
| WebContent 进程终止（白屏） | `[WebView:lobby] ⚠️ WebContent 进程终止` | ✅ | Debug + Release |

### 8.2 仍然抓不到的（已知盲区）

| 场景 | 为什么抓不到 |
|------|------------|
| 被 H5 自己 `try/catch` 吞掉的异常 | 根本不冒到 window。本包里 `12_Logic.js:254` / `gameabc.min.js:2175` 就是 catch 后用 `console.log(e.stack)` 打印 → 只在 Debug 的 `[H5:… console.log]` 里能看到 |
| iframe 内的异常 | `WKUserScript(forMainFrameOnly: true)`，只 hook 主框架 |
| 弹层（`OverlayViewController`）的 JS | 第三方外链，按设计不挂 relay |
| 跨域脚本的异常细节 | 浏览器安全策略统一报 `Script error.`，无行号无堆栈 |
| 原生 handler 内部抛错 | 属原生侧，走 `BridgeBus` 自己的 `diagLog` |

### 8.3 设计要点

- **资源 404 必须开捕获阶段**：`<script>`/`<img>`/`<audio>` 的 error 事件只在元素自身触发且**不冒泡**，`addEventListener('error', fn)` 收不到，必须 `addEventListener('error', fn, true)`。JS 异常的 `ev.target` 是 `window`，据此在同一 listener 内分流两类
- **导航层失败只 log 不自动 reload**：自动恢复是行为变化，msext 没有，不引入
- **`log` / `info` 只在 Debug 注入**：Release 包不该为每条业务日志付一次 JS→Native IPC，也不该把 H5 内部输出暴露给外部审阅。`#if DEBUG` 在编译期就把这段 JS 从 `javaScriptSource` 里摘掉
- **`log` / `info` 走裸 `print` 而非 `diagLog`**：`diagLog` 会同步落盘到 `Documents/diag.log`，而该 sink 无轮转、无大小上限（`BridgeBus.swift:47`）。一旦 §7 Q3 里的 `isDebugger` 被打开，收发包 firehose 灌进去会把设备磁盘吃光
- **对象参数走 `JSON.stringify`**：大厅业务大量使用 `console.log(msg)` / `console.log(res)` 打整包，直接 `String(obj)` 只会得到无用的 `[object Object]`。循环引用时 stringify 抛错 → 回退 `String()` → 再抛则输出 `[unstringifiable]`
- **原始 console 调用被透传**（`origLog.apply(console, arguments)`），所以转发不影响 Safari Web Inspector 里照常看到日志，两条路可以同时用
- **仅 `BridgedWebView`（大厅 + 子游戏）注入**，`OverlayViewController` 是第三方外链，不挂

⚠️ 依然受 §7 Q3 的 `Game_Config.Debugger.isDebugger` 制约：收发包日志被 H5 自己的开关挡着，Xcode 控制台同样看不到。需要时在 Safari 控制台运行 `Game_Config.Debugger.isDebugger = true`。

---

## 9. 修订记录

| 日期 | 内容 |
|------|------|
| 2026-06-24 | 文档建立。落地 `BridgedWebView` / `OverlayViewController` 两处 `isInspectable` 补丁。 |
| 2026-08-08 | 查明「Console 空」的首要原因是 H5 自带 `Game_Config.Debugger.isDebugger = false`，补进 §7 Q3。`H5ErrorRelay` 扩展 `console.log` / `console.info` 转发（Debug only）+ 对象参数 JSON 序列化，新增 §8.1。 |
| 2026-08-08 | 补齐「非 H5 主动 console.error」的报错通路：资源 404（error 事件捕获阶段）、WebView 加载失败 / WebContent 进程终止（两个 VC 的 `WKNavigationDelegate`，此前完全没实现）。输出加 `lobby` / `subGame` 前缀。新增 §8.2 已知盲区、§8.3 设计要点。 |
