Chrome插件开发

1. 前言

chrome extensions官方文档

2. 初识Chrome插件

2.1. 什么是Chrome插件, 他能干什么?

Chrome插件是为chrome浏览器添加功能的小程序, chrome插件可以获取网页内容并可以操作网页内容, 如爬取网页数据(网络爬虫), 自动点击(定时点击或刷新), 修改网页内容(如更改网页文字或图片)等.

2.2. 为什么是Chrome插件而不是其它浏览器插件?

首先Chrome是使用最多的浏览器, 其次国内许多浏览器是基于Chrome内核开发的, Chrome插件可以用于许多其它浏览器上, 如360, 百度浏览器上.

2.3. 学习Chrome插件开发需要哪些知识储备?

只需要html, css和javascript的基础知识即可. 我们还可以选择使用jQuery之类的JavaScript库, 让开发变得更简单.

2.4. 本课程的主要内容

本教程将教你如何开发, 调试和部署Chrome插件. 学习中将边开发边学习概念而不是先对所有概念进行介绍.

  • Hello World
  • Browser Action
  • Page Action
  • 非Browser Action类型的插件或Page Action类型的插件的其它知识
  • 调试
  • 部署

3. Chrome插件的文件结构

在我们正式开始开发插件前, 我们先来了解一下一个Chrome插件的构成, 通常一个chrome插件包含以下几个部分:

css
img
js
manifest.json
popup.html

  • manifest.json必须, 是插件的配置文件, 包含插件名称, 版本号, 图标, 权限等信息.
  • *.html: 用于向用户展示信息并与用户交互的界面, 如插件的设置界面等.
  • js/*.js: 用于实现插件的逻辑功能, 并不要求必须放在js文件夹下.
  • service worker 处理和侦听浏览器事件。有很多类型的事件,例如导航到新页面、删除书签或关闭选项卡。它可以使用所有Chrome API,但不能直接与网页内容交互;这属于内容脚本的工作。
  • img/*.png: 插件需要的图片, 如插件图标等, 文件类型不必是png, 文件夹非必须.
  • css/*.css: 插件中用到的css, 如对网页样式进行修改时用到的css文件. 文件夹非必须.

4. 第一个chrome插件helloworld

现在, 我们来创建我们的第一个Chrome插件HelloWorld. 本课程我们使用vs code作为编辑器, 您也可以使用其它编辑器. 主要目录结构如下.

4.1. 创建helloworld

1
2
3
4
5
HelloWorld
|-- img
|-- logo.png
|-- manifest.json
|-- popup.html

1, 新建插件文件夹HelloWorld
2, 编写manifest.json文件

manifest.json文件内容, 更多关于manifest.json文档字段说明, 请查看manifest文件说明文档
也可以查看官方的helloworld示例,
完整代码可在github上下载 - tutorial.hello-world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"manifest_version": 3,
"name": "helloWorld",
"version": "1.0",
"description": "hello world 插件",
"icons": {
"128": "img/logo.png",
"48": "img/logo.png",
"16": "img/logo.png"
},
"action": {
"default_popup": "popup.html",
"default_icon": "img/logo.png"
}
}

注意如果chrome版本较旧, 比如Chrome 88之前的版本, 则需要将不支持manifest v3, 需要按照manifest v2的格式配置manifest.json文件.

popup.html

1
2
3
4
5
6
7
8
9
10
<html>
<head>
<title> Hello world </title>
<meta charset="utf-8">
</head>
<body>
<h1 id="message"> 你好 </h1>
</body>
</html>

注意: 最好指定编码, 否则可能出现乱码的情况.

这样一个helloword插件的代码部分就完成了, 下面我们讲解如何将其加载进Chrome.

4.2. 使用HelloWorld插件

点击Chrome右上角用户偏好设置=>扩展程序=>管理扩展程序, 进入插件管理界面.

或者直接在浏览器地址栏输入chrome://extensions/进入插件管理界面.

要安装自己编写的浏览器插件, 首先将右上角的开发者模式开启. 开启开发者模式后, 界面上方会出现”加载已解压的扩展程序”按钮. 点击该按钮, 选择我们helloword插件所在目录进行加载.

加载完成后在扩展, 点击右上角”扩展程序按钮”就可以看到helloworld插件了, 点击helloworld插件就能看到”你好”信息了.

如果插件加载过程中存在任何问题, 在扩展程序管理页面对应的plugin上会有”错误”按钮, 点击”错误”可以看到即可看到错误的详细信息. 获得错误详情后即可进行除错处理. 前提是要打开开发者模式.

5. Javascript的基本用法

首先引入javascript, 修改后的popup.html文件如下所示.

popup.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<html>
<head>
<title> Hello world </title>
<meta charset="utf-8">
<script src="js/jquery.js" />
<script src="js/popup.js" />
</head>
<body>
<h1 id="message"> 你好 </h1>
<input id="input1" type="text"> </input>
</body>
</html>

popup.js的内容如下

1
2
3
4
5
6
7
8
9

$(function(){
$('#input1').keyup(
function(){
$('#message').text('你好, '+ $('#input1').val());
}
)
})

这样当我们在输入框输入信息的时候, message标题栏就会跟随输入发生变化. 可以参考上节内容加载该插件并测试.

6. 数据存储

6.1. 获取存储权限

首先, 如果我们要使用本地存储, 则需要在manifest.json中添加storage权限

1
2
3
4
5

"permissions": [
"storage"
]

6.2. 从存储读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

$(function(){
chrome.storage.sync.get('total', function(bugget){
var totalAmount = 0;
if(bugget.total) {
totalAmount = parseFloat(bugget.total);
}
$("#total").text(totalAmount);
}
$('addBtn').click(function (){
chrome.storage.sync.get('total', function(bugget){
var totalAmount = 0;
if(bugget.total) {
totalAmount = parseFloat(bugget.total);
}
var amount = $('#amount').val();
if(amount) {
totalAmount += parseFloat(amount);
chrome.storage.sync.set('total':totalAmount)
}

$("#total").text(totalAmount);
$("#amount").val("")
});
})
})

从上面的例子可以看到通过chrome.storage即可获取本地存储, 通过storage提供的一些方法, 我们即可操纵数据.

7. Options Page的用法

Options Page用于提供user interface给使用插件的用户设置必要的参数.

首先在manifest.json文件中指定Options page

1
2
3
4
5
6
{
"name": "My extension",
...
"options_page": "options.html",
...
}

options.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<html>
<head>
<title> Budget manager options </title>
<meta charset="utf-8">
<script src="js/jquery.js" />
<script src="js/options.js" />
</head>
<body>
<h1> 预算管理选项 </h1>
<h2> 预算限制: <input type="text" id="limit"></h2>
<input type="submit" id="setlimit" value="保存限制">
<input type="submit" id="resetTotal" value="清楚总金额">
</body>
</html>

详细示例可以参考官方文档Give users options

8. 添加右键菜单

通过开发Chrome插件可以自定义浏览器的右键菜单,主要是通过chrome.contextMenusAPI实现,右键菜单可以出现在不同的上下文,比如普通页面、选中的文字、图片、链接,等等,如果有同一个插件里面定义了多个菜单,Chrome会自动组合放到以插件名字命名的二级菜单里.

首先需要获取访问右键菜单的权限, 修改manifest.json, 添加如下内容.

1
2
3
4
5

"permissions": [
"contextMenus"
]

另外我们需要定义一个service_worker, service worker能获得更多操纵浏览器行为的权限, 其中包括管理右键菜单.

定义service_worker的方式如下, 在manifest.json中定义service_worker的入口脚本.

1
2
3
4
5

"background": {
"service_worker": "js/service-worker.js"
},

在js/service-worker.js即可调用chrome的API创建右键菜单项, 代码如下. 参考Chrome extensions api examples contextMenus

1
2
3
4
5
6
7
8
9
10
11
12
13

chrome.contextMenus.create(
{
id: "10086",
type: "normal",
title: "test menu item",
contexts: ["page", "selection"]
},
function(){
console.log("what can I help you?")
}
)

说明: 只有获取到contextMenus权限, 才可调用chrome.contextMenus.create, 如果未获取到权限chrome.contextMenus将返回undefined
菜单的id必须是唯一的, 否则会与其它子菜单冲突.
contextMenu type有四种类型, 分别为: nomal, checkbox, radio, separator
contextMenu contexts有多种类型, 用于控制子菜单在何种情况显现, 具体类型参考文档type-ContextType
必须传入回调函数, 否则会报错
API的详细说明, 请参考Extensions - API reference - chrome.contextMenus

9. 如何与页面交互

前面我们讲了popup page, options page以及service_worker, 这些都需要用户的操作才能与网页进行交互, 如果要在用户打开的网页自动注入事件或改变网页行为, 我们需要插入content_scripts.

要引入content_scripts, 我们需要修改manifest.json, 添加对应的content_scripts. 以下就是一个content_scripts配置示例

1
2
3
4
5
6
7
8
9
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery.js", "js/content_script.js"],
"css": ["css/style.css"],
"run_at": "document_end",
"all_frames": false
}

说明: 一段我们配置了content_scripts,并且我们的插件被成功加载到浏览器中, 浏览器在打开任何页面时都会检查, 是否要注入入(Inject)插件的content_scripts的部分到网页;
浏览器通过matches属性判断是否注入, 通过run_at属性控制何时注入, 通过js,css属性控制注入哪些内容;
通过all_frames控制是在匹配页面的所有frame中运行还是只在最上层的frame中运行。缺省是false,也就是只在最上层frame中运行;
对于js属性, 其是一个数组, 脚本在数组中的位置决定了脚本的加载顺序, 所有被依赖的脚本需要放在靠前的位置.
关于run_at的详细说明: 控制content script注入的时机。可以是document_start, document_end或者document_idle。缺省时是document_idle。如果是document_start, 文件将在所有CSS加载完毕,但是没有创建DOM并且没有运行任何脚本的时候注入。如果是document_end,则文件将在创建完DOM之后,但还没有加载类似于图片或frame等的子资源前立刻注入。如果是document_idle,浏览器会在document_end和发出window.onload事件之间的某个时机注入。具体的时机取决与文档加载的复杂度,为加快页面加载而优化。注意: 在document_idle的情况下,content script不一定会接收到window.onload事件,因为它有可能在事件发出之后才加载。在大多数情况下, 在content script中监听onload事件是不必要的,因为浏览器会确保在DOM创建完成后才执行它。 如果一定要在window.onload的时候运行,可以通过document.readyState属性来检查onload事件是否已经发出。

10. 页面之间的通信

Chrome extension messaging

以上是chrome插件的结构简图, 从图中我们可以看出, chrome插件主要由三部分组成: Background script (service worker), Content script, Popup.

其中service worker是在后台运行的, 通常一个插件只会有一个service_worker的实例在运行. 而content script会有多个实例同时在运行, 这取决与用户打开了多少个与插件相匹配的页面. 而popup会在用户点击插件时打开, 关闭时退出, 或者在被service_worker或content script触发时进行创建.

10.1. Background script (service worker)

service worker是插件的事件处理程序;它包含对插件很重要的浏览器事件的侦听器。它处于休眠状态,直到事件被触发,然后执行所指示的逻辑;通过配置可以实现它只在需要时加载,在空闲时卸载以节省内存, 通过配置也可以实现常驻内存。service worker可以访问所有Chrome API,只要它在manifest.json中声明了所需的权限。

10.2. Content script

Content script是在网页上下文中运行的脚本。通过使用标准的文档对象模型(DOM),他们能够读取浏览器访问的网页的详细信息,对其进行更改,并将信息传递给插件的其它部件。

10.3. Popup

当用户单击工具栏中扩展的操作按钮时,将显示操作的弹出窗口。弹出窗口可以包含您喜欢的任何HTML内容,并将自动调整大小以适应其内容。

10.4. 页面间通信

插件中的不同组件可以使用消息传递进行通信。任何一方都可以监听从另一端发送的消息,并在同一频道上进行响应。

在本教程中,我们将重点关注Content script和service worker如何进行通信。

10.5. Content script和service worker之间通信

content script发送消息给service worker并通过Promise的Then方法处理返回结果.

1
2
3
4
5
6
7
8
9
10
11
12

chrome.runtime
.sendMessage({
searchingWord: searchingKeyWord,
})
.then((wordInfo) => {
document.write(
'word: ' + wordInfo.word + '<br>' + 'translation: ' + wordInfo.translation

);
}

service worker 在onMessage通道上, 一旦接收到消除, 就会调用处理函数进行处理, 处理玩的结果通过sendResponse发送给消息发送方.

注意通过fetch返回的对象是Response类型, 这种类型不是标准的json对象, 所以必须将其转换为json对象才能正确的传递给content script.
即以下示例中的.then((response) => response.json())部分, 即是将Response对象转换为json object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
fetchData(
"https://api.example.com/" +
request.searchingWord
)
.then((response) => response.json())
.then((res) => {
sendResponse(res);
});
return true;
});

function fetchData(url) {
return fetch(url, {
method: "GET",
cache: "default",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
referrerPolicy: "no-referrer-when-downgrade",
});
}

11. 发布插件到谷歌市场

11.1. 注册google开发者账号

点击链接进入谷歌市场注册页面

11.2. 提交Chrome插件文件包

注册开发者身份成功后,我们就可以将CRX文件打包提交了。在开发者信息中心,选择添加新项。首次发布项目之前,您必须支付 US5.00的一次性开发者注册费。谷歌收取此费用的目的是对开发者帐户进行验证,并为用户提供更好的保护,以防他们受到欺骗性活动的侵害。支付注册费后,您最多可以发布 20 项内容。谷歌使用 Google 电子钱包来处理US5.00的一次性开发者注册费。谷歌收取此费用的目的是对开发者帐户进行验证,并为用户提供更好的保护,以防他们受到欺骗性活动的侵害。支付注册费后,您最多可以发布20项内容。谷歌使用Google电子钱包来处理US5.00付款。如果您之前未使用过 Google 电子钱包,会被要求您提供结算信息。

11.3. 编辑Chrome插件信息

我们在访问谷歌应用商店的插件chrome扩展程序上有一些文字的介绍信息,我们开发人员在上传成功后,需要在开发者信息中心中编辑插件的基本信息。
填写完信息,点击提交审核即可。

12. 相关阅读

chrome插件开发中Chrome storage API使用详解

13. 参考文档

谷歌插件开发-Chrome-exts-cli

【干货】Chrome插件(扩展)开发全攻略

最新版 V3 chrome 插件开发~ demo + 坑

作者

鹏叔

发布于

2023-07-25

更新于

2024-07-10

许可协议

评论