package browser // 本文件是**对端自由文本出口**的收敛点:有三处会把页面/浏览器自己说的话原样交给我们, // 它们出镜(进 error ⇒ 日志 + 模型可见视图)之前必须在这里过一道。 // // 三处**不是同一类**,因此守卫也不同(口径见 internal/runtime/trace/errortoken.go // 的「按字段类别判定」): // // - `Page.navigate` 的 `errorText` 是**协议枚举**:值域是 Chrome 的 net error 码 // (`net::ERR_NAME_NOT_RESOLVED` 之类),短、标识符形状 ⇒ 用 trace.ErrorToken 守: // 像枚举就保留,散文一律丢弃; // - `Runtime.evaluate` 的 `exceptionDetails.text` 是**页面自由文本**:页面的 JS 可以 // throw 任何东西 —— 包括它读到的 DOM 正文、地址栏里带 token 的 URL。⇒ 默认不带 // 全文,按「带上限地带」处理:压平空白 + trace.RedactTextLimited 截断; // - 浏览器自己的 stdout/stderr 预览(launchoutput.go 的 `preview()`)也是自由文本: // 参数被拒时 Chromium 会把**本次 argv** 与地址原话打出来(`chromeOutputPreview`)。 // // 🔴 两类守卫**不能互换**:拿 ErrorToken 去「清洗」自由文本是错的口径(它只会把整段 // 话删掉,不是清洗);拿 RedactTextLimited 去守枚举字段也不对(它不判形状,挡不住 // 散文)。清洗实现仍然只有一份,本文件只做「选哪一道 + 补形状前提」。 import ( "fmt" "strings" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace" ) // maxExceptionTextRunes 是 `exceptionDetails.text` 允许出镜的 rune 上限:**200**。 // // 为什么不复用既有的两个档位: // - **80**(`trace.RedactText` 的摘要档)会把真实的异常消息拦腰截断 —— // `TypeError: Cannot read properties of undefined (reading 'addEventListener')` // 已经 73 rune,再带上半句调用点就过 80,而调用点是唯一的定位线索; // - **4096**(网关日志页 `maxLogMsgRunes` 那一档)是给「运维要读全文」的日志行用的; // 这个字段是**页面自己说的话**,4096 等于允许页面把 4KB 正文塞进一条错误串 —— // 而这条错误串同时进日志与模型可见视图。 // // 200 容得下「异常消息 + 一段调用点」,又把页面可控文本压在「一句话」量级。超限时 // trace.RedactTextLimited 会补 `…`,因此截断本身是看得见的,不会被误读成全文。 const maxExceptionTextRunes = 200 // netErrorNamespace 是 Chrome net error 码的命名空间前缀(`net::ERR_...`)。 // // 为什么要先剥掉它再交给 ErrorToken:ErrorToken 的字符集是 `[A-Za-z0-9_.-]`,**不含 // `:`**,整串直接送进去会把合法的网络错误码一起判掉 —— 而「合法网络错误码形态保留」 // 是这条出口的硬要求(上层与工具都在断言它,见 render_test ③)。这个前缀是**协议 // 常量**,不是从对端抄来的文本,因此剥它是安全的。 const netErrorNamespace = "net::" // netErrorCodePrefix 是 net error 枚举的名字前缀(`ERR_`):所有真实取值都以它开头。 // 于是这一条额外把「恰好是单个令牌的自由文本」(`hunter2` 之类)也挡掉 —— 形状判定 // 仍由 trace.ErrorToken 做,本文件只补这一个前提,不复制第二份守卫实现。 const netErrorCodePrefix = "ERR_" // netErrorCode 只放行「像 net error 枚举」的 errorText:`[net::]ERR_<标识符>`。 // 其余(散文、被回显的 URL、带凭据的文本)返回空串,由调用方按「已隐去」处理。 func netErrorCode(raw string) string { text := strings.TrimSpace(raw) namespace := "" if rest, ok := strings.CutPrefix(text, netErrorNamespace); ok { namespace, text = netErrorNamespace, rest } if !strings.HasPrefix(text, netErrorCodePrefix) { return "" } token := trace.ErrorToken(text) if token == "" { return "" } return namespace + token } // navigateCause 把 `Page.navigate` 的 errorText 变成可出镜的原因子句。 // 调用前提:raw 非空(navigate 只在 errorText 非空时判定为失败)。 // // 不是枚举时一个字符的自由文本都不带,只留**字节数** —— 够判断「对端说了句话」, // 不带那句话(与 cdpError.Error 同一口径)。 func navigateCause(raw string) string { if code := netErrorCode(raw); code != "" { return code } return fmt.Sprintf("网络错误文本已隐去(%d 字节)", len(raw)) } // pageExceptionText 是 `exceptionDetails.text` 出镜前的唯一处理:压平空白(一个换行 // 就能在按行聚合的日志里伪造出「另一条记录」,与 sdklog / judgeReason 同一做法)+ // 凭据形状打码 + 截断(清洗实现只有一份:trace.RedactTextLimited)。 func pageExceptionText(raw string) string { return trace.RedactTextLimited(strings.Join(strings.Fields(raw), " "), maxExceptionTextRunes) } // evaluateCause 把 `Runtime.evaluate` 的 exceptionDetails.text 变成可出镜的原因子句。 func evaluateCause(raw string) string { if text := pageExceptionText(raw); text != "" { return text } // 压平之后什么都不剩(对端只给了空白):如实说「没给文本」,不假装异常无因。 return "页面异常(对端未给文本)" } // chromeOutputPreviewRunes 是浏览器 stdout/stderr 预览出镜时的 rune 上限:**600** // (= 12 行 × 50 rune)。 // // 为什么是这个量级:预览的**行**预算已经是 12 行(chromeOutputMaxLines),50 rune 大致 // 是一个短分句 —— Chromium 的抱怨句式是「原因在前」(`ERROR:...] 参数被拒:...`), // 所以 600 保得住原因那一句,又把「一整块转储」挡在模型可见的错误串之外。 // // 为什么不沿用另外两档:80(`trace.RedactText` 摘要档)连**一行**浏览器日志都放不下; // 4096(`chromeOutputMaxBytes` 缓存档、网关日志页 maxLogMsgRunes)是「运维读全文」的 // 量级,对浏览器可控的字段等于允许把 4KB 正文塞进工具结果。 // // 为什么 600 不会把「到底说了多少」藏起来:本上限只压在**预览正文**上,调用方 // (launchoutput.go)随后仍会追加「已截断,共 N 字节」——读者看到的永远是「这里有 600 // rune 的原话 + 它其实说了 N 字节」,截断与量级都看得见。 const chromeOutputPreviewRunes = 600 // chromeOutputPreview 是浏览器 stdout/stderr 预览出镜前的唯一处理:凭据形状打码 + // 截断(清洗实现只有一份:trace.RedactTextLimited)。它服务**两个**读面 —— 失败日志的 // `chrome_output` 字段与返回给调用方的错误串(launch.go 的 launchOnce),因此收敛在 // preview() 里,两个读面不会判得不一致。 func chromeOutputPreview(text string) string { return trace.RedactTextLimited(text, chromeOutputPreviewRunes) }