权限模型:permissions、host_permissions、activeTab 的最小化原则

权限是扩展与用户之间的契约。装扩展时弹出的那几行警告,直接决定用户装不装;上架审核时,权限申请是否与功能匹配,直接决定过不过审。这一篇讲清 MV3 权限体系的结构与最小化设计的实操方法。

三类权限

清单 内容 授予时机
permissions API 权限(storage、tabs 等) 安装时,触发警告
host_permissions 站点匹配模式 安装时,触发警告
optional_permissions / optional_host_permissions 上述两类的可选版 运行时经用户确认

声明示例:

{
  "permissions": ["storage", "activeTab", "scripting"],
  "optional_permissions": ["downloads"],
  "host_permissions": ["https://api.myservice.com/*"],
  "optional_host_permissions": ["https://*/*"]
}

permissions 里是来自固定清单的字符串,见官方 permissions-list:https://developer.chrome.com/docs/extensions/reference/permissions-list 。host_permissions 用 match 模式描述站点范围,语法与 content_scripts.matches 相同。optional 系列的意义在于把敏感授权推迟到真正需要的那一刻,用 chrome.permissions.request 弹窗征求用户同意,而不是安装时一股脑全要。

host_permissions 与 permissions 的分工

很多 API 权限和站点权限是叠加关系。典型例子:

  • tabs 权限解锁读取标签页的 url、title、favIconUrl 四个敏感属性;host_permissions 也能达到同样效果,且额外允许对该站注入脚本;
  • scripting 权限只是入场券,真正注入哪些站点由 host_permissions 或 activeTab 决定;
  • 扩展上下文里的跨域 fetch 需要目标站点的 host 权限。

所以写 manifest 时要区分两问:我要用什么 API(permissions),以及 我要碰哪些站点(host_permissions)。

activeTab:最小化的王牌

activeTab 是官方为「点击才干活」类扩展准备的最优解:用户主动触发扩展(点图标、点右键菜单项、按快捷键、接受 omnibox 建议)时,扩展临时获得对当前标签页的站点权限;用户离开该页面或关闭标签页,授权即收回。它不触发任何安装警告。

回顾第一章的 Hello Ext:permissions 里只有 activeTab 和 scripting,安装时用户几乎无感,但功能完整可用。对比一下反例——同样是「点图标改页面颜色」,如果声明 host_permissions 为 "<all_urls>",安装对话框会警告「读取和更改您在所有网站上的数据」,多数用户会犹豫。功能相同,信任成本天差地别。

activeTab 的边界也要记住:对 chrome:// 等受限页面无效;授权只覆盖触发时刻所在的标签页;跨页面持续监控类需求用不了它。

最小化设计的操作流程

按顺序问自己四个问题:

  1. 这个功能能否只在「用户主动触发」时工作?能,用 activeTab,别申请 host_permissions。
  2. 需要的站点范围能否收窄到具体域名?把 "<all_urls>" 换成具体模式,警告会从「所有网站」降为具体站点描述。
  3. 敏感权限能否延迟授予?能,放进 optional,配合运行时 request。
  4. 每个 permission 是否都有不可替代的功能对应?逐条自查,删掉「顺手加上」的。

官方还提供了权限警告的可视化自查工具:chrome://extensions 页面加载扩展后可查看实际警告文案;开发时也建议通读官方的 permission warnings 指南,了解哪些权限组合会触发哪些警告文案。

审核视角

权限与功能不匹配是商店审核的高频拒绝理由。申请了 "<all_urls>" 却只做样式微调、要了 downloads 却从不碰下载,都会被人工审核盯上。审核细节与整改清单在第五章 《审核被拒》 篇展开,这里先立住原则:权限清单就是功能清单的镜像,多一行都是债。

host 模式写法易错,正则基础不牢的话参考 正则实战。官方权限总览文档:https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions

运行时申请权限:optional 的正确用法

optional 权限的价值在「延迟到需要时」。典型流程是:设置页里用户开启某功能 → 扩展检测到缺少权限 → 弹出授权对话框 → 用户同意后功能生效,拒绝则优雅降级:

async function enableDownloadFeature() {
  const granted = await chrome.permissions.request({ permissions: ["downloads"] });
  if (granted) {
    startFeature();
  } else {
    showTip("未授予下载权限,该功能不可用");
  }
}

配套的 API 一组四件:permissions.contains(查询)、request(申请,必须在用户手势内调用)、remove(主动交还)、onAdded / onRemoved(监听变化)。用 remove 实现的「用户可随时撤回授权」是隐私友好设计,商店审核对此有好感。

host 权限同样支持 optional(optional_host_permissions),适合「用户添加自定义站点规则」的产品形态:安装时零警告,用户每添加一个站点、授权一个站点,控制权始终在用户手里。

警告文案的预览方法

写完 manifest 想知道用户会看到什么警告,三个途径:一是 chrome://extensions 加载扩展后点「详情」查看权限列表;二是开发者仪表板上传时自动生成警告预览;三是本地写个临时脚本调用 chrome.permissions.getAll() 打印当前授权。警告文案由 Chrome 根据权限自动生成,开发者无法自定义文案,但可以通过调整权限组合改变文案等级——例如 tabs 权限会触发「读取您的浏览记录」警告,而 activeTab 完全无警告,功能相近、观感天壤。

权限与架构的联动

权限清单会反向约束架构设计,提前想清楚能省返工:

  1. 想避免 "<all_urls>",就把功能设计成「点击触发」(activeTab 可覆盖)或「白名单站点」(用户按站点授权);
  2. 想避免 tabs 警告,就不读标签页的 url/title——按 title 分类的功能得改成按 domain 分组;
  3. 想做「全网可用」的工具,考虑「按需注入」架构:扩展常驻但只在用户调用时向当前页注入,而不是声明式注入所有页面。

这些设计取舍在功能规划期做,比上架被拒后再改便宜十倍。

权限演进的真实轨迹

一个扩展的权限清单会随产品成长变化,了解典型轨迹有助于规划:起步期用 activeTab + storage,零警告上线;成长期加了跨站点功能,被迫引入 host_permissions,警告出现、转化率下降;成熟期开始做权限瘦身——把功能重构成按需授权,警告收窄,转化率回升。这个「先膨胀后瘦身」的曲线几乎人人经历,区别只在于有没有主动管理。

瘦身的具体动作可以量化:列出当前 permissions 与 host_permissions 全集,逐条标注「哪个功能在用、最近调用频率」,30 天未调用的权限进入删除候选。chrome.permissions.getAll() 配合日志就能拿到调用数据。权限清单跟代码一样需要定期重构,「顺手申请」的惯性只有靠周期性审计来打破。

与审核政策的对照

权限设计与商店政策的对应关系值得在开发期就对照(而非提审时才发现):政策要求权限与功能匹配——你的每条 host_permissions 都应能指出具体功能;要求数据收集最小化——能本地计算的别上传;要求用户可控——敏感授权给 optional 与撤销入口。这三条与本章的最小化原则一脉相承,做到位了,审核环节基本不会在权限上出问题。

host 模式收窄时经常要写域名与路径的模式串,正则工具箱见 正则实战。

本章由 WorkBuddy 原创整理,API 行为依据 Chrome 扩展官方文档(Manifest V3)。