页面没报错就不是白屏?我把五种结果拆开了

0 阅读6分钟

window.onerror 没有上报,不等于用户看到了正常页面。脚本可以不报错,但接口一直挂起、关键组件没有挂载,或者 WebView 只剩一张空背景;反过来,页面也可能抛出一个非关键异常,但主要内容仍然可见、可操作。

因此,白屏不是某一种 JavaScript 异常,而是“用户在一段时间内没有看到有效内容”的可用性问题。本文把视觉空白、关键元素、错误事件和时间阈值合并成一个确定性分类器,并用 7 项本地测试验证 confirmedsuspectedrecoveredexcludedhealthy 五种结果。

一、错误事件和白屏不是同一个集合

浏览器错误信号、性能信号和视觉结果回答的是不同问题。把任何一个信号当成白屏真相,都会制造盲区。

信号能回答什么单独使用的盲区
error同步脚本错误、部分资源加载失败没报错也可能没有有效内容
unhandledrejection未处理的 Promise 拒绝不代表主页面一定不可用
PerformanceObserver性能时间线中的指定事件有绘制不等于业务内容可用
关键元素业务主体是否已经出现选择器失效会造成误判
视觉空白用户画面是否长期接近空白大面积留白、骨架屏可能误报

MDN 对 error 的说明明确区分了同步脚本错误与未处理 Promise:后者会触发 unhandledrejection。这已经说明“只接一个 window.onerror”连异常面都覆盖不全,更不用说没有异常但内容始终未出现的白屏。

二、把布尔告警改成五种结果

生产监控最好不要只输出 isBlank: true/false。同样是“超过阈值仍空白”,是否存在导航失败、是否最终恢复、是否属于已知过渡页,处理方式完全不同。

状态最小判定推荐动作
confirmed视觉空白超阈值、关键元素未出现,并有终止性错误立即告警,关联版本与错误证据
suspected只有单一异常信号,或证据尚不完整采样保留,等待更多信号或人工复核
recovered曾经超阈值空白,但关键元素随后出现统计恢复耗时,排查慢启动
excluded命中已知过渡页、测试页或白底设计页不计入主指标,但记录排除规则版本
healthy关键元素在阈值内出现,且没有终止性错误进入正常性能统计

这里的五类是本文的工程建模,不是浏览器标准。它的价值在于让告警、发布门禁和排障动作对应到可解释证据,而不是把所有异常压成一个数字。

三、视觉检测解决“代码根本没跑”的盲区

经过脱敏整理的一份历史 WebView APM 资料采用过端侧截图分析:容器启动后周期采样画面,根据像素纯度判断有效内容是否出现。历史方案记录了 100ms 采样、Android 95%、iOS 98%、超过 5 秒判白屏、最长 10 秒等参数。

这些数字只属于当时的设备、页面和容器条件,不能直接复制到新项目。新系统至少要重新验证深色模式、骨架屏、大面积留白页面、Canvas/视频、采样开销和隐私边界。端侧最好只计算特征并立即释放位图,不保存或上传完整截图。

四、浏览器侧要收集互补信号

浏览器侧可以把同步错误、资源失败和未处理 Promise 分开采集,再由业务代码明确标记关键内容是否就绪。资源错误通常需要在捕获阶段监听,避免只盯着 window.onerror 的五参数回调。

window.addEventListener("error", (event) => {
  const target = event.target
  const isResource = target instanceof HTMLScriptElement
    || target instanceof HTMLLinkElement
    || target instanceof HTMLImageElement

  reportSignal(isResource ? "resource_error" : "script_error")
}, true)

window.addEventListener("unhandledrejection", () => {
  reportSignal("unhandled_rejection")
})

PerformanceObserver 可以观测受支持的性能条目,但它是性能时间线入口,不是白屏裁判。生产代码应先检查 PerformanceObserver.supportedEntryTypes,并把“首次绘制”“关键元素出现”“业务可交互”保留为不同时间点。

五、先统一事件,再做确定性分类

各端信号最终应归一成一个不含截图和敏感信息的观察对象。算法版本、阈值和排除规则必须进入事件,否则阈值调整前后的白屏率无法直接比较。

const observation = {
  route: "/checkout",
  visualBlankMs: 6200,
  thresholdMs: 5000,
  keyElementSeen: false,
  terminalError: true,
  excluded: false,
  algorithmVersion: "visual-v3+signals-v1",
}

terminalError 不应等于“出现任意 JS 错误”。它应由导航失败、主文档 HTTP 失败、关键资源不可恢复或 WebView 进程异常等明确条件归并;非关键埋点报错最多提供疑似证据。

六、分类器的关键是拒绝单信号定罪

本地实验使用一个纯函数固定判定顺序:先处理排除项,再识别恢复,随后才确认白屏;只要证据不完整,就降级为 suspected

export function classifyObservation(observation) {
  if (observation.excluded) return { state: "excluded" }

  const crossed = observation.visualBlankMs > observation.thresholdMs
  if (crossed && observation.keyElementSeen) return { state: "recovered" }
  if (crossed && !observation.keyElementSeen && observation.terminalError) {
    return { state: "confirmed" }
  }
  if (crossed || !observation.keyElementSeen || observation.terminalError) {
    return { state: "suspected" }
  }
  return { state: "healthy" }
}

阈值比较使用严格大于,因为历史文字口径是“超过 5 秒”。这类边界必须写进测试,避免不同端产生不一致统计。

七、7 项测试实际验证了什么

实验运行在 Node.js v22.19.0,使用内置 node:test,没有安装第三方依赖。7 项测试覆盖长期空白加终止错误、只有长期空白、超时后恢复、已知排除页、快速可见、页面可见但脚本报错,以及阈值相等的边界。

tests 7
pass 7
fail 0

{"confirmed":1,"suspected":2,"recovered":1,"excluded":1,"healthy":1}

这些结果只证明示例规则与测试一致,不证明真实像素算法有效,也没有测量 WebView 截图开销、误报率或线上白屏下降幅度。

八、指标要能推动发布决策

有了可解释状态,可以继续统计确认白屏率、超阈值恢复率、疑似事件确认率、关键元素就绪时间 P50/P75/P95,并按 App、WebView、系统、网络、页面和资源包版本分组。

发布系统可以先在灰度组比较新旧版本:若确认白屏率显著上升,或某个离线包版本集中触发 confirmed,暂停全量并回滚。阈值本身不能凭感觉设置,应来自页面样本、用户等待预算和设备性能分层。

九、上线前检查表与验证边界

  • 同时采集视觉、关键元素、导航/HTTP、资源、脚本和 Promise 信号;
  • 任意单一错误不会直接升级为确认白屏;
  • 五种分类结果和阈值边界都有测试;
  • 事件包含算法版本、页面版本和资源包版本;
  • 端侧不上传完整截图,不记录用户敏感内容;
  • 发布门禁使用确认白屏率和样本量,不被单个偶发事件触发;
  • 每次调整采样区、阈值或分类规则都重新标定。

来源:

验证边界 本文核验了 MDN 对错误事件、未处理 Promise 与 PerformanceObserver 的说明,并完成零依赖分类实验;历史截图参数来自脱敏资料,只作为方法来源和测试样例。本文没有采集真实用户画面,没有验证阈值、像素算法、WebView 开销、误报率或线上改善幅度,新项目必须重新标定。