首页 / 浏览器扩展开发入门教程 / 可选权限 optional_permissions

浏览器扩展开发入门教程

可选权限 optional_permissions

本教程共 56 篇 · 第 24 篇 · 更新于 2026-08-13 · 约 7 分钟阅读

可选权限optional_permissionspermissions.requestcontainsremove用户手势运行时申请

本节目标:学完你能用 chrome.permissions.request() 在用户触发功能时按需申请权限,并处理好”授予/拒绝”两种结果,做出不打扰用户的体验。

第 23 章讲的 permissions 和 host_permissions,是在安装时就一次性授予的主权限,会显示在安装警告里。但有些能力只是”进阶功能才用得到”——比如你做个划线笔记,基础功能只用 storage,却有个”显示热门网站”的彩蛋才需要 topSites。这种权限如果硬塞进主权限,用户一安装就被吓退。

可选权限(optional_permissions / optional_host_permissions)就是为这种场景准备的:安装时不申请、不显示警告,等用户真的用到某功能时,再由代码弹窗询问。

24-1 什么是可选权限

可选权限和主权限的区别,核心在”授予时机”。

  • 主权限(permissions / host_permissions):写在清单里,安装那一刻由用户一次性授权,会出现在安装的警告横幅中。
  • 可选权限(optional_permissions / optional_host_permissions):同样写在清单里,但只是”预告”扩展将来可能会要这些权限;安装时不授权、不警告。真正授予发生在运行时,由扩展调用 API 弹窗、用户当场点头才生效。

一句话:主权限是”进门先交证件”,可选权限是”要用某个房间时再单独申请钥匙”。

Note

可选权限不是”偷偷获取权限”的后门。它每一次实际授予,都必须经过用户明确点击确认,浏览器会弹出权限请求框。用户随时可以在扩展管理页收回。

24-2 在清单里声明 optional 系列

可选权限先在清单里”报备”,告诉浏览器”我将来可能要这些”。语法和主权限数组一致,只是换了字段名:

{
  "name": "Very Secure Extension",
  "manifest_version": 3,
  "optional_permissions": [
    "tabs"
  ],
  "optional_host_permissions": [
    "https://www.google.com/"
  ]
}

注意这里分两类:API 级的可选权限放 optional_permissions,网站级的可选宿主权限放 optional_host_permissions。只有在这里声明过的权限,运行时才允许向用户申请;没声明就去 request,会被浏览器拒绝。

Tip

可选权限的范围也别乱写。虽然它不触发安装警告,但运行时弹窗申请的每一项用户都看得到。依旧遵守”最小必要”——只把真正按需才用的放进来。

24-3 运行时申请:chrome.permissions.request()

真正向用户要权限,靠 chrome.permissions.request()。它接收一个描述对象和一个回调:

chrome.permissions.request(
  {
    permissions: ['tabs', 'scripting'],
    origins: ['https://www.google.com/']
  },
  function (granted) {
    // granted 为 true 表示用户授予了权限
    if (granted) {
      // 申请成功,执行依赖该权限的功能
    } else {
      // 用户拒绝,走降级或提示逻辑
    }
  }
);

这里有个极易写错的点:申请宿主权限时,描述对象里用的键是 origins,不是清单里的 optional_host_permissions;API 级权限才用 permissions。对应关系记牢:

清单字段request 里的键放什么
optional_permissionspermissionsAPI 级权限名,如 tabs、scripting
optional_host_permissionsorigins匹配模式,如 https://www.google.com/

回调的 granted 是布尔值:true 表示用户这次授予了(注意是”这次请求里列出的全部”都给了才算 true)。你一定要根据 granted 分流——授予就执行功能,拒绝就别卡住界面。

24-4 用户手势是硬前提

chrome.permissions.request() 有个硬性要求:必须在用户手势(user gesture)内部调用。最常见、最稳妥的手势就是按钮的 click 回调。

chrome.action.onClicked.addListener((event) => {
  // 用户点了工具栏按钮,属于一次明确的用户手势
  chrome.permissions.request(
    {
      permissions: ['tabs', 'scripting'],
      origins: ['https://www.google.com/']
    },
    function (granted) {
      if (granted) {
        // doSomething();
      } else {
        // doSomethingElse();
      }
    }
  );
});

如果你把 request 放在一个定时任务、页面加载完成、或后台自动拉取里调用,浏览器会因为”没有用户手势”而直接不弹窗、视为拒绝。这条规则不是 Chrome 故意刁难,而是为了防止扩展在用户不知情时悄悄要权。

Warning

不要在服务工作者(Service Worker)被事件唤醒后的异步回调里”绕一圈”再 request。手势的上下文会丢失,弹窗不会出来。把 request 放在手势处理函数的最直接路径上。

24-5 检查、释放与查询:contains / remove / getAll

除了申请,你可能还需要知道”某项权限现在到底有没有”,以及”用完了能不能还回去”。

检查是否已授予——用 chrome.permissions.contains(),避免重复弹窗:

chrome.permissions.contains(
  { permissions: ['tabs'], origins: ['https://www.google.com/'] },
  (has) => {
    if (has) {
      // 已有权限,直接走功能
    } else {
      // 还没给,考虑发起 request
    }
  }
);

释放权限——用 chrome.permissions.remove()。用户关掉某个进阶功能时,把对应权限还回去,是”最小权限”的体现:

chrome.permissions.remove(
  { permissions: ['tabs'], origins: ['https://www.google.com/'] },
  (removed) => {
    if (removed) {
      // 已成功释放
    }
  }
);

列出当前所有权限——用 chrome.permissions.getAll(),适合在设置页展示”你已授予本扩展的能力”:

chrome.permissions.getAll((perms) => {
  console.log(perms.permissions); // API 级权限数组
  console.log(perms.origins);     // 宿主权限数组
});
Tip

一个好习惯:功能触发前先 contains 检查一下。已授予就直接跑,没授予再 request。这样老用户不会被反复弹窗,体验顺滑很多。

24-6 把申请时机贴着功能走

可选权限最大的价值,是”用到才问”。机制设计好了,时机更要贴着功能:

  • 在用户点开那个功能时申请,而不是一启动扩展就弹。比如”显示热门网站”按钮被点,才申请 topSites。
  • 一次只问当前功能需要的权限,别借机把一揽子权限都塞进同一次 request。用户看到范围越小,越愿意给。
  • 先跑不需要权限的部分,把 request 留在真正卡点的地方。能不弹窗就不弹窗。

对照第 23 章的判断表:核心功能走主权限,进阶的、偶发的走 optional。两者配合,安装警告最干净,运行时又该有权限时拿得到。

24-7 用户体验的几点提醒

最后把”体验”这件事讲透,因为可选权限本质是为体验服务的:

  • 向用户解释为什么。弹窗前最好先在界面上用一句话说明”需要这项权限是为了 XX”。用户懂了,授予率才高。
  • 被拒要优雅降级。用户拒绝后,别报错、别卡死、别反复弹。关掉该功能或提示”可在设置中稍后开启”即可。
  • 别反复骚扰。contains 检查能挡掉大部分重复请求;对明确拒绝过的权限,不要短时间内再问。
  • 让用户能收回。在设置页提供开关,调用 remove 把权限还回去,用户会更有安全感。

把”检查 + 申请”拼成一个小helper会更顺手。很多扩展会写一个 ensurePermission:先 contains 判断,没有再 request,返回 Promise 供功能代码 await。这样业务侧只管”我要用这个功能”,权限的拿取逻辑被统一收口,也不会到处散落弹窗调用。

function ensurePermission(perms) {
  return new Promise((resolve) => {
    chrome.permissions.contains(perms, (has) => {
      if (has) return resolve(true);
      chrome.permissions.request(perms, (granted) => resolve(granted));
    });
  });
}

还有一个细节要提醒:request 弹窗里的每一项都是可以勾选的。回调为 true 表示本次列出的权限全部授予;用户取消勾选部分项时回调为 false,但勾上的那些仍然生效。所以不要把互不相关的权限捆在同一请求里——用户为了其中一个,可能不得不面对一长串勾选框。把彼此独立的权限拆成各自功能触发时的单独请求,授予率会高得多。

讲完按需申请,下一章我们把视角拉到”安全与过审”:为什么权限要尽量少、商店审核盯哪些点、过度申请又有哪些风险。那就是最小权限原则。