带你从0了解Chrome MV3扩展程序开发

目前的工作一直和浏览器插件打交道,但是也没有很好的总结输出文章,此时chrome也在正全面项manifest v3升级,抛弃manifest v2,官方声称《2024 年 6 月:在稳定发布前弃用 Chrome MV2》

接下来我们来了解如何开发chrome MV3插件吧。

组成部分

清单 manifest.json

扩展程序的清单是唯一且必须具有特定文件名的必需文件:manifest.json。该文件还必须位于相应扩展程序的根目录下。该清单会记录重要元数据、定义资源、声明权限以及标识在后台和网页中运行的文件。

Service Worker

Service Worker 在后台运行并处理浏览器事件,例如移除书签或关闭标签页。它们无权访问 DOM,但针对这种使用情形,您可以将其与屏幕外文档结合起来。

内容脚本

内容脚本在网页环境中运行 JavaScript。

在用户点击扩展程序工具栏图标或使用 Action API 显示弹出式窗口时,执行代码。

开发一个常规的浏览器插件,了解这些部分就可以了。下面我们先来具体介绍一下manifest中的配置吧。

manifest

必须具备的属性(本地开发)

json 复制代码
manifest_version // 用于指定扩展程序使用的清单文件格式版本,目前直接是3
name // 扩展程序的名称,长度 < 45字符
version // 当前扩展程序的版本号

上传浏览器扩展商店必须

json 复制代码
manifest_version // 用于指定扩展程序使用的清单文件格式版本,目前直接是3
name // 扩展程序的名称,长度 < 45字符
version // 当前扩展程序的版本号
description // 用于描述 Chrome 应用店和用户的扩展程序管理页面上的扩展程序,长度 < 132字符
icons // 一个或多个代表您的扩展程序的图标。只会显示在应用商店,扩展程序列表处,不会出现在工具栏上

可选属性

action

定义扩展程序图标在 Google 工具栏中的外观和行为。

js 复制代码
"action": {
    // 图标
    "default_icon": {              
      "16": "icon/icon16.png",  
      "48": "icon/icon48.png",  
      "128": "icon/icon128.png"   
    },
    // 移入时提示的文字,不设置默认是扩展名称
    "default_title": "Click Me"
}

注意如果不设置action属性,在Service Worker和popup脚本中使用chrome.action将会报错。

我们还可以设置角标信息来表示一些状态,只不过只能通过编程的方式设置,不能在manifest中设置默认的。

还可以设置弹出窗口,他可以是任意的html结构,可以直接在manifest中配置action.default_popup路径。内部可以引入自己的css, js等文件。js脚本的执行也是在popup页面中执行的。popup.html只可以引入外链脚本,内联脚本不可以执行,会报错。 但是可以直接在popup.html中写内部样式。

js 复制代码
<head>
  <title>manifest v3测试</title>
  <script src="popup.js"></script>
  <style>
    #searchButton {
      color: red;
    }
  </style>
</head>

<body>
  <button id="searchButton">manifest v3测试</button>
</body>

background

指定包含扩展程序的 Service Worker(充当事件处理程序)的 JavaScript 文件。扩展 Service Worker 在需要时加载,并在其进入休眠状态时取消加载。扩展 Service Worker 无法访问 DOM。

如果background脚本使用了es6模块化,我们还需要配置type: module注意不能使用import()动态导入语法。

js 复制代码
"background": {
  "service_worker": "background.js",
  "type": "module" // 使用es6模块化
},
js 复制代码
// a.js
export const a = 1

// background.js
import {a} from "./a.js"
console.log("a", a)

我们也可以使用importScripts()进行文件导入。

js 复制代码
// a.js
const a = 1
// background.js
// 直接将代码导入到background中, 导入模块无需使用导出关键字
importScripts("./a.js") // undefined
console.log("a", a) // 1

content_scripts

指定在用户打开某些网页时要使用的 JavaScript 或 CSS 文件。内容脚本是在网页环境中运行的文件。主要是操作当前页面,然后和扩展通信。

js 复制代码
"content_scripts": [
    {
      "matches": ["https://*/*", "http://*/*"], // 指定要将此内容脚本注入哪些网页。
      "exclude_matches": ["*://*/*business*"], // 排除匹配的页面
      "exclude_globs": ["*science*"], // 作用在matches规则后,在进行一次过滤。
      "include_globs": ["*nytimes.com/???s/*"], // 作用在matches规则后,在进行一次过滤 。*表示匹配任意长度字符,?表示匹配单独字符
      "css": ["./content/main.css"], // 要注入到匹配页面的 CSS 文件列表。这些元素会按照它们在此数组中出现的顺序进行注入 
      "js": ["./content/content.js"], // 要注入到匹配页面的 JavaScript 文件的列表。系统会按照文件在此数组中出现的顺序注入文件。 相对路径形式引入
      "run_at": "document_idle", // document_idle dom构建完毕或者页面所有资源加载完毕后注入。 document_start css加载解析之后,构建dom和执行js脚本之前注入。 document_end dom构建完毕,资源未加载之前注入。
      "all_frames": false, // 是否会注入到子frame中,注入子frame中也需要匹配matches规则,如果不匹配也不会注入。
      "match_about_blank": false, // 是否注入到about:blank页面
      "match_origin_as_fallback": false, // 脚本是否应注入由匹配的来源创建的页面。例如data:, blob:, about:blank等等,他会查看原网址来确定是否匹配,而不是通过当前网址去匹配matches。
      "world": "ISOLATED" // "ISOLATED" 指定隔离环境,即此扩展程序独有的执行环境。"MAIN" 指定 DOM 的主域,即与托管网页的 JavaScript 共享的执行环境。
    }
],

必须指定匹配规则,否则报错。

当内容脚本注入成功后,可以访问以下api。

js 复制代码
"commands": {
    "a": {
      "suggested_key": {
        "windows": "Alt+C",
        "default": "Alt+C"
      },
      "description": "测试快捷键"
    }
}
JS 复制代码
// popup / background监听
chrome.commands.onCommand.addListener((command) => {
  console.log("command", command); // 这个就是设置的快捷键属性名 a
})

declarative_net_request

定义 declarativeNetRequest API 的静态规则,以允许拦截和修改网络请求。

V3将拦截修改网络请求apichrome.webRequest更改为Declarative Net Request API就是我们可以通过配置来修改和屏蔽网络请求,而不是像v2一样去拦截请求做屏蔽和修改。

js 复制代码
"permissions": ["declarativeNetRequest", "declarativeNetRequestFeedback"],
"declarative_net_request": {
  // 配置资源规则
  "rule_resources": [
    {
      "id": "ruleset_1",
      "enabled": true,
      // 具体操作项。这里也可以指定josn文件路径
      "path": [
        {
          "id": 1,
          "priority": 1,
          // 操作行为
          "action": {
            // 修改请求头
            "type": "modifyHeaders",
            "requestHeaders": [{ "header": "cookie", "operation": "remove" }]
          },
          // 行为条件
          "condition": {
            // 匹配的主机。就是 webRequest 监听器的 urls 选项
            "urlFilter": "|*?no-cookies=1",
            // 指定要屏蔽的资源的类别
            "resourceTypes": ["main_frame"]
          }
        }
      ]
    }
  ]
}

export, import

允许从扩展程序导出和导入资源。

js 复制代码
  "export": {
    // 设置当前扩展资源允许被导入的扩展id,不设置将表示任何扩展都可以导入
    "allowlist": []
  },
  "import": [
    {"id": "扩展id"},
    {
      "id": "扩展id"
      "minimum_version": "0.5" // optional
    }
  ]
  // 访问资源时,可通过导入扩展程序根目录中的预留路径 _modules/SHARED_MODULE_ID 访问。
  <script src="_modules/扩展id/资源路径">

externally_connectable

指定哪些其他页面和扩展程序可以连接到您的扩展程序。声明了哪些扩展程序和网页可以通过 runtime.connectruntime.sendMessage 连接到您的扩展程序。

如果您未在扩展程序的清单中声明 externally_connectable 键,则所有扩展程序都可以连接,但任何网页都无法连接。** ****因此,在更新清单以使用 externally_connectable 时,如果未指定 "ids": ["*"],其他扩展程序将无法连接到您的扩展程序。这可能是意外的后果,因此请谨记。

js 复制代码
"externally_connectable": {
    // 指定允许连接的扩展程序的 ID。如果留空或未指定,则任何扩展程序或应用都无法连接。通配符 "*" 将允许所有扩展程序和应用连接。
    "ids": [
      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
    ],
    // 指定允许连接的网页的网址格式。如果留空或未指定,则任何网页都无法连接。格式不能包含通配符网域,也不能包含(有效)顶级域名的子网域
    "matches": [
      "https://*.google.com/*",
      "*://*.chromium.org/*"
    ],
    // 允许扩展程序使用与其连接的网页的 TLS 通道 ID。
    "accepts_tls_channel_id": false
  },

options_page, options_ui

选项界面中只能引入外部脚本,不能在html中设置内联脚本。

  • options_page 指定 options.html 文件的路径,以将扩展程序用作选项页面。
js 复制代码
options_page: "options.html" // 单独一个tab页面作为选项界面
  • options_ui 指定 HTML 文件的路径,该文件允许用户在 Chrome 扩展程序页面更改扩展程序选项。他主要是配置详细的选项配置。
js 复制代码
"options_ui": {
    "page": "options.html", // 指定选项页面相对于扩展程序根目录的路径。
    "open_in_tab": false // 用于指示是否在新标签页中打开扩展程序的选项页面。如果设为 `false`,该扩展程序的选项页面会嵌入 `chrome://extensions` 中,而不会在新标签页中打开。
},

host_permissions, optional_host_permissions

  • host_permissions 字段用于指定扩展程序需要访问的特定主机或URL模式。这与普通的权限不同,它更为细粒度,允许扩展程序只在特定的网站上执行操作。
  • optional_host_permissions 字段用于列出扩展程序可能需要访问的特定主机或URL模式,但用户可以选择是否授予这些权限。
js 复制代码
// 点击按钮询问扩展是否可以访问该网站
const PERMISSIONS = {origins: ['http://api.stackoverflow.com/']};
document.querySelector('button#enable').addEventListener('click', function() {
  chrome.permissions.contains(PERMISSIONS, function(allowed) { // 先判断是否包含该主机的访问权限
    if (allowed) {
      alert('You already have SO host permission!');
    } else {
      chrome.permissions.request(PERMISSIONS, function(result) {
        if (result) {
          console.log('SO host permission granted!' +
                      'Open the browser action again.');
        }
      });
    }
  });
});

permissions , optional_permissions

  • permissions 指定权限列表,让扩展中可以使用特定的扩展程序 API。
  • optional_permissions 字段用于列出扩展程序可能需要的权限,但用户可以选择是否授予这些权限(这个确认的操作是我们使用代码控制的,而不是安装插件就会询问。)。用户安装扩展程序时,将被提示是否同意这些可选权限。
js 复制代码
// 我们点击按钮去触发询问是否授予权限。
const newPerms = {
  permissions: ['topSites']
};
const button = document.createElement('button');
button.innerText = 'Allow Extension to Access Top Sites';
button.addEventListener('click', () => {
  chrome.permissions.request(newPerms).then((granted) => {
    if (granted) {
      console.log('granted');
      sites_div.innerText = '';
      createTop();
    } else {
      console.log('not granted');
    }
  });
});

必需权限的优点:

  • 减少提示:扩展程序可以提示用户接受所有权限一次,
  • 开发更简单:必要权限必定存在。

可选权限的优点:

  • 安全性更高:由于用户仅启用需要的权限,因此扩展程序运行时的权限会更少。
  • 为用户提供更优质的信息:在用户启用相关功能时,扩展程序可以解释为什么需要特定权限。
  • 升级更轻松:升级扩展程序时,如果升级添加了可选权限而非必需权限,Chrome 不会为用户停用该扩展程序。

web_accessible_resources

定义扩展程序中可供网页或其他扩展程序访问的文件。每个元素都必须包含一个 "resources" 元素以及一个 "matches""extension_ids" 元素。

js 复制代码
{
  "web_accessible_resources": [
    {
      "resources": [ // 每个字符串都包含从扩展程序根目录到给定资源的相对路径。
        "images/*"
      ],
      "matches": [ // 用于指定哪些网站可以访问这组资源。
        "*://*/*"
      ]
    },
    {
      "resources": [
        "style/extension.css",
        "script/extension.js"
      ],
      "extension_ids": [ // 每个字符串都包含可以访问资源的扩展程序的 ID。
        "id"
      ],
      "use_dynamic_url": false // 如果为 true,则仅允许通过动态 ID 访问资源。系统会为每个会话生成一个动态 ID。这意味着,当浏览器重启或扩展程序重新加载时,系统会重新生成该文件。
    }
  ]
}

我们暴露出当前扩展的图片资源,在给定权限的网站 中引入就可以访问。引入的路径chrome-extension://extensionId/资源名称

其他

js 复制代码
author // 指定用于创建扩展程序的帐号的电子邮件地址。
chrome_settings_overrides // 定义所选 Chrome 设置的替换项。
chrome_url_overrides // 定义默认 Chrome 网页的替换项。
content_security_policy // 定义对扩展程序可以使用的脚本、样式和其他资源的限制。
cross_origin_embedder_policy // 指定 Cross-Origin-Embedder-Policy HTTP 标头的值,该标头用于配置在扩展程序页面中嵌入跨源资源。
cross_origin_opener_policy // 指定 Cross-Origin-Opener-Policy HTTP 标头的值,可让您确保顶级扩展程序页面不会与跨源文档共享浏览上下文组。
devtools_page // 定义使用 [DevTools](https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools?hl=zh-cn) API 的页面。
default_locale // 用于定义支持多个语言区域的扩展程序的默认语言。
homepage_url // 用于指定扩展程序首页的网址。一般是代码仓库和官方网站
incognito // 定义扩展程序在无痕模式下的行为。支持的值包括 `"spanning"`、`"split"` 和 `"not_allowed"`。
key // 为各种开发用例指定扩展程序的 ID。
minimum_chrome_version // 定义可安装扩展程序的最低 Chrome 版本。该值必须是现有 Chrome 浏览器版本字符串的子字符串,例如 `"107"` 或 `"107.0.5304.87"`。
oauth2 // 允许使用 OAuth 2.0 安全 ID。此键的值必须是具有 `"client_id"` 和 `"scopes"` 属性的对象。
omnibox // 允许此扩展程序在 Chrome 的地址栏中注册关键字。类似于地址栏输入一些网站的快捷方式。
requirements // 列出使用扩展程序所需的技术。
sandbox // 定义一组扩展程序页面,它们无权访问扩展程序 API 或直接访问非沙盒化页面。
short_name // 包含要在字符空间有限时使用的扩展程序名称的缩写版本。长度上限为 12 个字符。如果未定义,将显示"name"键的截断版本。
side_panel // 标识要在 [sidePanel](https://developer.chrome.com/docs/extensions/reference/api/sidePanel?hl=zh-cn) 中显示的 HTML 文件。
tts_engine // 将扩展程序注册为文字转语音引擎。
update_url // 包含扩展程序的更新页面的网址。
version_name // 一个描述扩展程序版本的字符串。
storage // 声明[托管存储区域](https://developer.chrome.com/docs/extensions/reference/storage?hl=zh-cn#property-managed)的 JSON 架构。

以上就是大致梳理了一下chrome MV3中的一些配置,关键配置都已经详细介绍,如果开发中遇到问题,可以再去查阅官方文档。

参考

往期年度总结

往期文章

专栏文章

相关推荐
浮华似水15 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web4 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇5 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr5 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui