Permissions API

深入理解 Permissions API:查询和管理浏览器权限状态、navigator.permissions.query、以及各平台权限的实际应用。

Permissions API

一、Permissions API 概述

1.1 什么是 Permissions API

Permissions API 提供了一种查询浏览器 API 权限状态的一致方式。在 Web 应用需要判断是否能使用某个功能(如定位、摄像头、通知)时,传统的做法是直接尝试使用然后处理错误。Permissions API 让这个过程更加声明式和可控。

// 查询权限状态
const result = await navigator.permissions.query({ name: 'geolocation' });
console.log(result.state); // 'granted' | 'denied' | 'prompt'

1.2 权限状态

状态 含义 用户操作
granted 已授权 用户明确允许
denied 已拒绝 用户明确拒绝或系统自动拒绝
prompt 待询问 需要向用户请求

1.3 支持的权限

API Permission Name
Geolocation geolocation
摄像头/麦克风 camera, microphone
通知 notifications
剪贴板 clipboard-read, clipboard-write
推送通知 push
屏幕捕获 display-capture

二、核心 API

2.1 查询权限

// 查询定位权限
const geolocation = await navigator.permissions.query({
  name: 'geolocation'
});

console.log(geolocation.state); // 'granted' | 'denied' | 'prompt'

// 查询通知权限
const notifications = await navigator.permissions.query({
  name: 'notifications'
});

2.2 监听权限变化

const permission = await navigator.permissions.query({ name: 'geolocation' });

permission.addEventListener('change', () => {
  console.log('Permission changed to:', permission.state);
});

// 也可以使用 onchange
permission.onchange = () => {
  console.log('Permission state changed');
};

2.3 Worker 中的使用

// Permissions API 也可以在 Worker 中使用
navigator.permissions.query({ name: 'push' }).then((result) => {
  console.log(result.state);
});

三、实际应用

3.1 定位权限检查

async function checkLocationPermission() {
  const result = await navigator.permissions.query({
    name: 'geolocation'
  });

  if (result.state === 'granted') {
    // 直接使用定位
    navigator.geolocation.getCurrentPosition((pos) => {
      console.log('Location:', pos.coords);
    });
  } else if (result.state === 'denied') {
    console.log('Location permission denied');
  } else {
    // 'prompt' - 需要请求权限
    console.log('Need to request location permission');
  }

  // 监听状态变化
  result.addEventListener('change', () => {
    console.log('Location permission changed:', result.state);
  });
}

3.2 摄像头/麦克风权限

async function checkMediaPermission() {
  const camera = await navigator.permissions.query({ name: 'camera' });
  const mic = await navigator.permissions.query({ name: 'microphone' });

  console.log('Camera:', camera.state);
  console.log('Microphone:', mic.state);

  // 监听变化
  camera.addEventListener('change', () => {
    if (camera.state === 'denied') {
      showCameraDeniedMessage();
    }
  });
}

3.3 优雅的权限检查工具

class PermissionManager {
  static async check(permissionName) {
    try {
      const result = await navigator.permissions.query({ name: permissionName });
      return {
        state: result.state,
        granted: result.state === 'granted',
        denied: result.state === 'denied',
        prompt: result.state === 'prompt',
        onChange: (callback) => {
          result.addEventListener('change', () => callback(result.state));
        }
      };
    } catch (error) {
      console.warn(`Permission ${permissionName} not supported`);
      return null;
    }
  }

  static async checkMultiple(permissions) {
    return Promise.all(
      permissions.map(p => this.check(p))
    );
  }
}

// 使用
const { granted } = await PermissionManager.check('geolocation');
if (granted) {
  navigator.geolocation.getCurrentPosition(handleSuccess);
}

四、权限撤销

4.1 没有撤销 API

重要:Permissions API 只提供查询,没有提供撤销权限的方法。曾经提议的 permissions.revoke() 方法已被移除。

// ❌ 不存在
navigator.permissions.revoke({ name: 'geolocation' }); // 错误

4.2 手动撤销

用户需要通过浏览器设置手动撤销权限:

浏览器 操作
Chrome 设置 → 隐私和安全 → 位置信息 → 管理例外/清除授权
Firefox 设置 → 隐私与安全 → 权限
Safari 系统偏好设置 → 隐私与安全

4.3 引导用户

async function ensurePermission(permissionName) {
  const result = await navigator.permissions.query({ name: permissionName });

  if (result.state === 'granted') {
    return true;
  }

  if (result.state === 'denied') {
    // 提示用户手动开启
    showSettingsInstruction(permissionName);
    return false;
  }

  // prompt 状态 - 需要触发请求
  return await requestPermission(permissionName);
}

五、安全与隐私

5.1 透明性原则

// ✅ 只在真正需要时才请求权限
async function onUserClickShareButton() {
  // 检查权限状态
  const result = await navigator.permissions.query({ name: 'geolocation' });

  if (result.state === 'prompt') {
    // 用户主动触发时才请求
    const position = await new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(resolve, reject);
    });
    shareLocation(position);
  } else if (result.state === 'granted') {
    // 直接使用
    navigator.geolocation.getCurrentPosition(shareLocation);
  }
}

5.2 权限策略

// 在 Permissions Policy 中声明
// <iframe allow="geolocation" src="...">
// 或者 HTTP 头
// Permissions-Policy: geolocation=(self)

六、面试高频问题

Q: Permissions API 可以用来申请权限吗?

回答要点:不能。Permissions API 只提供查询权限状态和监听变化的能力。实际申请权限需要调用对应的 API(如 navigator.geolocation.getCurrentPosition())来触发浏览器原生的权限对话框。

Q: 权限被拒绝后如何处理?

回答要点:根据拒绝的原因处理——如果是用户明确拒绝,通常需要引导用户到浏览器设置手动开启;如果是权限未定义,说明浏览器不支持该权限。需要提供替代方案或降级体验。

Q: Permissions API 支持所有浏览器 API 吗?

回答要点:不是的。Permissions API 支持的权限是有限的,主要包括 Geolocation、Camera、Microphone、Notifications、Clipboard、Push 等。部分 API 如 storage 权限尚未标准化。查询前应检查 API 是否支持。


参考资料

延展阅读