前言
什么是 Chrome 插件
严格来讲,我们正在说的东西应该叫 Chrome 扩展(Chrome Extension),真正意义上的 Chrome 插件是更底层的浏览器功能扩展,可能需要对浏览器源码有一定掌握才有能力去开发。鉴于 Chrome 插件的叫法已经习惯,本文也全部采用这种叫法,但读者需深知本文所描述的 Chrome 插件实际上指的是 Chrome 扩展。
Chrome 插件是一个用 Web 技术开发、用来增强浏览器功能的软件,它其实就是一个由 HTML、CSS、JS、图片等资源组成的一个.crx 后缀的压缩包.
另外,其实不只是前端技术,Chrome 插件还可以配合 C++ 编写的 dll 动态链接库实现一些更底层的功能(NPAPI),比如全屏幕截图。
由于安全原因,Chrome 浏览器 42 以上版本已经陆续不再支持 NPAPI 插件,取而代之的是更安全的 PPAPI。
学习 Chrome 插件开发有什么意义
增强浏览器功能,轻松实现属于自己的“定制版”浏览器,等等。
Chrome 插件提供了很多实用 API 供我们使用,包括但不限于:
- 书签控制;
- 下载控制;
- 窗口控制;
- 标签控制;
- 网络请求控制,各类事件监听;
- 自定义原生菜单;
- 完善的通信机制;
为什么是 Chrome 插件而不是 Firefox 插件
- Chrome 占有率更高,更多人用;
- 开发更简单;
- 应用场景更广泛,Firefox 插件只能运行在 Firefox 上,而 Chrome 除了 Chrome 浏览器之外,还可以运行在所有 webkit 内核的国产浏览器,比如 360 极速浏览器、360 安全浏览器、搜狗浏览器、QQ 浏览器等等;
- 除此之外,Firefox 浏览器也对 Chrome 插件的运行提供了一定的支持;
学习资料
推荐查看官方文档,虽然是英文,但是全且新,国内的中文资料都比较旧(注意以下全部需要翻墙):
-
Chrome 插件官方文档主页
-
Chrome 插件官方示例
-
manifest 清单文件
-
permissions 权限
-
chrome.xxx.api 文档
-
模糊匹配规则语法详解
相关开源项目:
基础知识
开发与调试
Chrome 插件没有严格的项目结构要求,只要保证本目录有一个 manifest.json 即可,也不需要专门的 IDE,普通的 web 开发工具即可。
从右上角菜单→ 更多工具→ 扩展程序可以进入 插件管理页面,也可以直接在地址栏输入 chrome://extensions 访问。

勾选 开发者模式 即可以文件夹的形式直接加载插件,否则只能安装 .crx 格式的文件。Chrome 要求插件必须从它的 Chrome 应用商店安装,其它任何网站下载的都无法直接安装,所以,其实我们可以把 crx 文件解压,然后通过开发者模式直接加载。
开发中,代码有任何改动都必须重新加载插件,只需要在插件管理页按下 Ctrl+R 即可,以防万一最好还把页面刷新一下。
替换 npm 源—使用淘宝镜像
淘宝团队提供了一个官方的 npm 镜像服务,几乎是实时同步 npm 官方库。
- 执行以下命令替换淘宝镜像:
npm config set registry ``https://registry.npmmirror.com
- 验证是否生效:
npm config get registry
输出应为:
https://registry.npmmirror.com/
cnpm:加速下载第三方包(可选)
cnpm 是 npm 的中国镜像,帮助解决在中国大陆由于网络问题导致的 npm 包下载缓慢的问题。使用 cnpm 可以大幅提高安装速度。安装和使用 cnpm 的步骤如下:
1. 安装 cnpm
可以通过 npm 来安装 cnpm。打开终端或命令提示符,然后运行以下命令:
npm install -g cnpm --registry=https:<em>//registry.npmmirror.com</em>
这条命令会全局安装 cnpm,并设置包的下载源为 https://registry.npmmirror.com,这是一个 npm 的中国镜像源。
2. 使用 cnpm
安装完 cnpm 后,你可以像使用 npm 一样使用 cnpm。
# 例如,安装一个包, 这里的 [package_name] 是你想要安装的 npm 包名。
cnpm install [package_name]
# 安装依赖,等同于 npm install
cnpm i原生开发和使用框架。初学来说,先熟悉原生开发。
核心介绍
插件结构示例
my-extension/
├── manifest.json ← 插件配置文件(必须)
├── background.js ← 后台脚本
├── content.js ← 注入到网页的脚本
├── popup.html ← 弹出页面
├── popup.js ← 弹出页面逻辑
├── popup.css ← 弹出页面样式
├── icons/ ← 插件图标
└── options.html / .js / .css← 设置页(可选)
manifest.json
这是一个 Chrome 插件最重要也是必不可少的文件,用来配置所有和插件相关的配置,必须放在根目录。其中,manifest_version、name、version 3 个是必不可少的,description 和 icons 是推荐的。
下面给出的是一些常见的配置项,均有中文注释,完整的配置文档请戳这里。
{
// 清单文件的版本,这个必须写,而且必须是3
"manifest_version": 3,
// 插件的名称
"name": "demo",
// 插件的版本
"version": "1.0.0",
// 插件描述
"description": "简单的Chrome扩展demo",
// 图标,一般偷懒全部用一个尺寸的也没问题
"icons":
{
"16": "img/icon.png",
"48": "img/icon.png",
"128": "img/icon.png"
},
// 会一直常驻的后台服务脚本,V3 中只能使用 service_worker
"background":
{
// V3中不再支持"page",只能使用service_worker
"service_worker": "js/background.js"
},
// 浏览器右上角图标设置,browser_action、page_action、app统一使用action字段
"action":
{
"default_icon": "img/icon.png",
// 图标悬停时的标题,可选
"default_title": "这是一个示例Chrome插件",
"default_popup": "popup.html"
},
// 需要直接注入页面的JS
"content_scripts":
[
{
// "<all_urls>" 表示匹配所有地址
"matches": ["<all_urls>"],
// 多个JS按顺序注入
"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
// JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式
"css": ["css/custom.css"],
// 代码注入的时间
"run_at": "document_start"
},
{
"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
"js": ["js/show-image-content-size.js"]
}
],
// 权限申请
"permissions":
[
"contextMenus",
"tabs",
"notifications",
"storage",
"scripting"
],
// host权限必须单独列出
"host_permissions": [
"http://*/*",
"https://*/*"
],
// 插件中可以被网页访问的资源列表
"web_accessible_resources": [
{
"resources": ["js/inject.js"],
"matches": ["<all_urls>"]
}
],
// 插件主页
"homepage_url": "https://www.baidu.com",
// 覆盖浏览器默认页面,V3 中仍然支持
"chrome_url_overrides":
{
"newtab": "newtab.html"
},
// 插件设置页
"options_page": "options.html",
// V3仍支持新版UI设置页
"options_ui":
{
"page": "options.html",
"open_in_tab": true
},
// 地址栏关键字
"omnibox": { "keyword" : "go" },
// 默认语言
"default_locale": "zh_CN",
// 开发者工具面板页面
"devtools_page": "devtools.html"
}content-scripts
所谓 content-scripts,其实就是 Chrome 插件中向页面注入脚本的一种形式(虽然名为 script,其实还可以包括 css 的),借助 content-scripts 我们可以实现通过配置的方式轻松向指定页面注入 JS 和 CSS(如果需要动态注入,可以参考下文),最常见的比如:广告屏蔽、页面 CSS 定制,等等。
示例配置:
{
// 需要直接注入页面的JS
"content_scripts":
[
{
//"matches": ["http://*/*", "https://*/*"],
// "<all_urls>" 表示匹配所有地址
"matches": ["<all_urls>"],
// 多个JS按顺序注入
"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
// JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式
"css": ["css/custom.css"],
// 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
"run_at": "document_start"
}
],
}特别注意,如果没有主动指定 run_at 为 document_start(默认为 document_idle),下面这种代码是不会生效的:
document.addEventListener('DOMContentLoaded', function()
{
console.log('我被执行了!');
});content-scripts 和原始页面共享 DOM,但是不共享 JS,如要访问页面 JS(例如某个 JS 变量),只能通过 injected js 来实现。content-scripts 不能访问绝大部分 chrome.xxx.api,除了下面这 4 种:
- chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
- chrome.i18n
- chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
- chrome.storage
其实看到这里不要悲观,这些 API 绝大部分时候都够用了,非要调用其它 API 的话,你还可以通过通信来实现让 background 来帮你调用(关于通信,后文有详细介绍)。
好了,Chrome 插件给我们提供了这么强大的 JS 注入功能,剩下的就是发挥你的想象力去玩弄浏览器了。
background
后台(姑且这么翻译吧),是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在 background 里面。
background 的权限非常高,几乎可以调用所有的 Chrome 扩展 API(除了 devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置 CORS。
经过测试,其实不止是 background,所有的直接通过
chrome-extension://id/xx.html这种方式打开的网页都可以无限制跨域。
配置中,background 可以通过 page 指定一张网页,也可以通过 scripts 直接指定一个 JS,Chrome 会自动为这个 JS 生成一个默认的网页:
{
// 会一直常驻的后台JS或后台页面
"background":
{
// 2种指定方式,如果指定JS,那么会自动生成一个背景页
"page": "background.html"
//"scripts": ["js/background.js"]
},
}需要特别说明的是,虽然你可以通过 chrome-extension://xxx/background.html 直接打开后台页,但是你打开的后台页和真正一直在后台运行的那个页面不是同一个,换句话说,你可以打开无数个 background.html,但是真正在后台常驻的只有一个,而且这个你永远看不到它的界面,只能调试它的代码。
popup
popup 是点击 browser_action 或者 page_action 图标时打开的一个小窗口网页,焦点离开网页就立即关闭,一般用来做一些临时性的交互。
popup 可以包含任意你想要的 HTML 内容,并且会自适应大小。可以通过 default_popup 字段来指定 popup 页面,也可以调用 setPopup() 方法。
配置方式:
{
"browser_action":
{
"default_icon": "img/icon.png",
// 图标悬停时的标题,可选
"default_title": "这是一个示例Chrome插件",
"default_popup": "popup.html"
}
}需要特别注意的是,由于单击图标打开 popup,焦点离开又立即关闭,所以 popup 页面的生命周期一般很短,需要长时间运行的代码千万不要写在 popup 里面。
在权限上,它和 background 非常类似,它们之间最大的不同是生命周期的不同,popup 中可以直接通过 chrome.extension.getBackgroundPage() 获取 background 的 window 对象。
event-pages
这里顺带介绍一下 event-pages,它是一个什么东西呢?鉴于 background 生命周期太长,长时间挂载后台可能会影响性能,所以 Google 又弄一个 event-pages,在配置文件上,它与 background 的唯一区别就是多了一个 persistent 参数:
{
"background":
{
"scripts": ["event-page.js"],
"persistent": false
},
}它的生命周期是:在被需要时加载,在空闲时被关闭,什么叫被需要时呢?比如第一次安装、插件更新、有 content-script 向它发送消息,等等。
除了配置文件的变化,代码上也有一些细微变化,个人这个简单了解一下就行了,一般情况下 background 也不会很消耗性能的。
injected-script
这里的 injected-script 是我给它取的,指的是通过 DOM 操作的方式向页面注入的一种 JS。为什么要把这种 JS 单独拿出来讨论呢?又或者说为什么需要通过这种方式注入 JS 呢?
这是因为 content-script 有一个很大的“缺陷”,也就是无法访问页面中的 JS,虽然它可以操作 DOM,但是 DOM 却不能调用它,也就是无法在 DOM 中通过绑定事件的方式调用 content-script 中的代码(包括直接写 onclick 和 addEventListener 2 种方式都不行),但是,“在页面上添加一个按钮并调用插件的扩展 API”是一个很常见的需求,那该怎么办呢?其实这就是本小节要讲的。
在 content-script 中通过 DOM 方式向页面注入 inject-script 代码示例:
// 向页面注入JS
function injectCustomJs(jsPath)
{
jsPath = jsPath || 'js/inject.js';
var temp = document.createElement('script');
temp.setAttribute('type', 'text/javascript');
// 获得的地址类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
temp.src = chrome.extension.getURL(jsPath);
temp.onload = function()
{
// 放在页面不好看,执行完后移除掉
this.parentNode.removeChild(this);
};
document.head.appendChild(temp);
}你以为这样就行了?执行一下你会看到如下报错:
Denying load of chrome-extension://efbllncjkjiijkppagepehoekjojdclc/js/inject.js. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.
意思就是你想要在 web 中直接访问插件中的资源的话必须显示声明才行,配置文件中增加如下:
{
// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
"web_accessible_resources": ["js/inject.js"],
}至于 inject-script 如何调用 content-script 中的代码,后面我会在专门的一个消息通信章节详细介绍。
homepage_url
开发者或者插件主页设置