Permissions Policy Violation: unload is not allowed 是什么

Chrome 控制台偶尔出现:

[Violation] Permissions policy violation: unload is not allowed in this document.

不是代码报错,是一条警告。

原因

Chrome 正在逐步废弃 unload 事件,因为注册了 unload 的页面无法进入 BFCache(Back/Forward Cache),会降低导航性能。某些页面策略或 iframe 设置下,unload 会被直接禁止,此时任何注册 unload 的代码都会打印这条 Violation。

常见触发场景:

  • 第三方 SDK(Sentry、埋点、广告脚本)注册了 unload
  • 旧版 Citrix/Enterprise 插件
  • 页面在 <iframe> 里,父页面设置了 Permissions-Policy: unload=()

是否影响功能

通常不影响。[Violation] 只是警告,页面照常运行,unload 回调不会执行而已。如果你的业务逻辑依赖 unload(如发送离开事件),需要改用 visibilitychangepagehide

查看当前页面的 Permissions Policy

方法一:Network 响应头

打开 DevTools → Network,刷新页面,点击 HTML 文档请求 → Response Headers,查找:

Permissions-Policy: unload=()

方法二:控制台查询

// 查看 unload 是否被策略禁止
document.permissionsPolicy?.allowsFeature("unload")

// 查看所有允许的特性
document.permissionsPolicy.allowedFeatures()

// 抓取当前页面响应头
fetch(location.href)
  .then(r => console.log(r.headers.get("Permissions-Policy")))

定位是谁注册了 unload

// 查看 window 上注册了哪些 unload 监听器
getEventListeners(window).unload

// 查看 beforeunload
getEventListeners(window).beforeunload

如果有结果,点击 listener 里的函数名可以跳转到 Sources 对应代码。

也可以在 Sources 全局搜索:

addEventListener("unload"
window.onunload
beforeunload

推荐替代方案

// 替代 unload:页面对用户不可见时触发
document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "hidden") {
        // 发送最后的数据
        navigator.sendBeacon("/api/leave", payload);
    }
});

// 替代 beforeunload(不阻塞 BFCache)
window.addEventListener("pagehide", (e) => {
    if (e.persisted) {
        // 页面进入 BFCache,不是真正关闭
    }
});

navigator.sendBeacon 在页面隐藏时异步发送请求,不阻塞页面关闭,是发送离开事件的首选方案。

Chrome Extension 的本地存储

Chrome 扩展用 chrome.storage.local 存储的数据保存在:

Windows: %LOCALAPPDATA%\Google\Chrome\User Data\Default\Local Extension Settings\<扩展ID>
macOS: ~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/<扩展ID>

目录内容是 LevelDB 格式(.log.ldb 文件),不是纯文本。查看方法:

# 在扩展页面控制台读取(需要 Inspect views 权限)
chrome.storage.local.get(null, console.log)

或用 Python plyvel 解析:

import plyvel

db = plyvel.DB("/path/to/extension/storage", create_if_missing=False)
for k, v in db:
    print(k.decode(errors="replace"))
    print(v.decode(errors="replace"))
db.close()

注意:读取时浏览器需要关闭,否则 LevelDB 文件被锁。