前言
本文是一篇关于 Chrome 扩展(插件)开发基本知识的文章(V2,V3 请移步)。
对于现在开发 Chrome 扩展来说,是非常简单的一件事情,其只需要使用 JavaScript 即可开发,并且 Chrome 官方对你如何开发 Chrome 扩展程序并没有严格要求,
只需要在你项目的根目录保证存在 mainfest.json
文件即可,该文件是用来配置所有和插件相关的配置,必须放在根目录,它就相当于 webpack 的入口文件。
快速开始
- 任意位置创建一个项目
- 在项目根目录创建一个名为
manif~~~~est.json
的文件 - 使用任意 IDE 通过 JavaScript 进行项目开发
将项目打包成
.crx
文件安装到 Google 中- Google 中搜索:
chrome://extensions/
打开扩展程序 - 右上角开启开发者模式
- 点击打包扩展程序并将对应的项目文件夹打包。
或直接使用文件夹的形式安装到 Google 中
- Google 中搜索:
chrome://extensions/
打开扩展程序 - 右上角开启开发者模式
- 点击加载已解压的扩展程序并选择对应的项目文件夹
- Google 中搜索:
现在你可以在 Google 中使用你的扩展程序了
manifest.json 字段介绍
❗️ 称呼规定
- contentScripts 在本节中指:使用
content_scripts
字段配置的脚本文件。 - injectJS / inject-*:在 contentScripts 中向指定页面注入的脚本文件。
- popup:使用
browser_action
或page_action
字段的选项default_popup
配置的页面
EN-background | CN
What's the background?
background 通常翻译为:后台。
它是一个常驻的页面,其生命周期是扩展中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭。
所以它的作用的通常是:把需要一直运行的、启动就运行的以及全局代码放里面去执行。
并且 background 的权限非常高,几乎可以调用所有的 Chrome 扩展 API(除了 devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置 CORS
。
经过测试,其实不止是 background,所有的直接通过chrome-extension://id/xx.html
这种方式打开的网页都可以无限制跨域。所以通常我们可以使用 chrome.extension API 的 getUrl() 将项目中的相对路径转为完成的 URL,它会转为这样的格式:chrome-extension://xx.xx
而正也正好使得相对路径所对应的 .js 或 .html 文件可以无限制跨域。
格式
{
"background":{
// page 和 scripts 二选一
"page"?:"background.html",
// 项目中的任意脚本
"scripts"?:[
"background.js",
"xx.js"?,
"..."?
],
// 可选;设置该 background(后台)是否是持久化的,通常是 false(默认值)
"persistent"?: false
}
}
page
:指定一个 .html 文件scripts
:指定一个 .js 文件若你使用 scripts,则 chrome 会自动为该脚本生成一个默认网页
- 使用
page
的好处在于:你可以配置 background(后台)的页面 - 不论是使用
page
或者是scripts
,它们都能搭配使用persistent
;也可以忽略不使用
persistent
,它的默认值为:false
background 在 Chrome 的何处?
- 在 Chrome 搜索:
chrome://extensions/
- 在跳转的页面右上角打开开发者模式,
- 在任意已启用的扩展程序中你可以看见蓝色字体显示的:【背景页】或【background page】字段,
- 点击【背景页】或【background page】字段,就会弹出一个供开发者调试的 background 页面。
而这就是我们在 manifest.json
的 background
字段配置的 .html 或 .js。
note
- 通过点击【背景页】显示的 background 页面(后台页面)和真正在运行的 background 并非同一个,
弹出的后台页面只是供你调试用的,参见:此处
- 只有处于开发者模式下且启用了的扩展程序才能查看 【background page】
- 通常来说
background
字段选项persistent
的值通常为 false,参见:此处 - 若你不主动查看 background.js,则即使它报错导致程序崩溃,它也并不会主动提示任何信息。
page_action | browser_action | app(discard)
介绍
manifest.json
中存在以下三个字段:
- page_action
- browser_action
- app
但是这三个字段只能选择,也必须选择 1 个使用,不可选择多个,否则 Chrome 将会加载失败。
EN-page_action
当某些特定页面打开才显示的图标z
{
"page_action": {
"default_popup": "html/pageaction.html",
"default_title": "鼠标移动到扩展程序时将提示的信息",
"default_icon": "img/sds.png",
// 也可以选择多张图片,可以为不同的比例设置不同的图片,将自动应用!
"default_icon": {
"16": "img/sds.png",
"32": "img/sds.png",
"48": "img/sds.png",
"128": "img/sds.png"
}
}
}
Tips:
For the best visual impact, follow these guidelines:
- Do use page actions for features that make sense for only a few pages.
- Don't use page actions for features that make sense for most pages. Use browser actions instead.
- Don't constantly animate your icon. That's just annoying.
EN-browser_action
其用处类似于 page_action
字段,但是 browser_action
可以显示在任何页面!
{
"browser_action": {
"default_popup": "html/browseraction.html",
"default_title": "鼠标移动到扩展程序时将提示的信息",
"default_icon": "img/icon.png",
// 也可以选择多张图片,可以为不同的比例设置不同的图片,将自动应用!
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
}
}
}
app(已被弃用)
page_action 和 browser_action 有什么用?
page_action
和 browser_action
两个字段用来设置扩展程序在用户的”眼中”是什么样子的。
即:设置扩展程序【向外展示的图标(头像)】、【点击扩展程序弹出的页面】以及【鼠标移动到扩展程序时弹出的提示】是什么。
在扩展程序界面(chrome://extensions/
)显示的图标是icons
字段配置的。
请看下图:
(what-is-use-browser-action)
以上图片各个弹出框在 page_action
或 browser_action
字段中的对应选项如下所示:
browser_action
所在的弹出框则是"default_popup"
选项配置的(popup)。鼠标移动到扩展程序时将提示的信息
弹出框则是由:"default_title"
选项配置。- 红色圆圈括起来的“礼物盒“就是
"default_icon"
选项配置的;
popup
概念
popup
是点击 browser_action
或者 page_action
图标时打开的一个小窗口网页,焦点离开网页就立即关闭,一般用来做一些临时性的交互,
对于 browser_action
和 page_action
来说,default_popup 选项无疑是非常重要的,它是配置 popup 的关键。
你可以在 page_action 和 browser_action 有什么用? 一节中找到有关于 popup 的描述。
在 popup
可以包含任意你想要的 HTML 内容,并且会自适应大小。这意味着你完全可以在 popup 中加载一些指定的脚本文件,来执行一些命令。
如何设置 popup?
有两种方法:
default_popup
字段来指定 popup(推荐)- 调用
setPopup()
方法。
popup 的权限
在权限上,它和 background 非常类似——几乎可以调用所有的 Chrome 扩展 API(除了 devtools),而且它可以无限制跨域;
它们之间最大的不同是生命周期的不同,popup中可以直接通过 chrome.extension.getBackgroundPage()
获取 background 的 window 对象。
EN-content_scripts | CN
配置选项格式
{
// 需要直接注入页面的JS
"content_scripts":
[
{
// 指定注入的页面地址(必要)
"matches": [
""?, // 表示匹配所有地址,配置了这个就没必须要配置其他的
"http://www.whyhw.com/" // 指定注入的页面,注:最后面的 '/' 是必要的
],
// 多个 JS 按顺序注入(可选)
"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
// 多个 CSS 按顺序注入(可选)
"css": ["css/custom.css","css/xx.css"],
// 代码注入的时间(可选)
"run_at": "document_start"
},
// 这里仅仅是为了演示content-script可以配置多个规则
{
"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
"js": ["js/show-image-content-size.js"]
}
],
}
### What's the content_scripts?
contentScripts 是在网页上下文中运行的文件。通过使用标准的[文档对象模型](http://www.w3.org/TR/DOM-Level-2-HTML/)(DOM),contentScripts 能够读取浏览器访问的网页的详细信息,对其进行更改并将信息传递给其父扩展。
`manifest.json` 的 `content_scripts` 字段的作用是:把**脚本**和 **CSS** 注入到**指定**页面。
contentScripts 的缺陷及解决方法
缺陷
contentScripts 虽然运行在指定网页的上下文中,可以访问指定页面的 DOM,但是却无法访问指定页面的其他脚本文件;指定页面的 DOM 也无法主动调用 contentScripts 中的代码。
=> 比如:
const cs = () => console.log('contentScripts')
- 当我们执行 a.html 并点击其中的按钮时,
并不会在控制台打印 contentScripts,而是会报错,cs 函数未定义
这是由于 contentScripts 不会被真正注入到页面中(它不会存在于页面),导致无法在 DOM 中主动通过绑定事件的方式调用 content-script
中的代码,
包括直接写 onclick
和addEventListener
2种方式都不行;但是,“在页面上(a.html)添加一个按钮并调用指定插件的扩展API”是一个很常见的需求,
比如:你想为一个指定域名的网站设置指定扩展,此时:你可以让网站”配合“你,从而为它定制出一个独特的 Chrome 扩展。
所以这就需要一个解决方法,详见:contentScripts 缺陷的解决方法
contentScripts 缺陷的解决方法
解决方法的理论也很简单,即:在 contentScripts 中,通过某种方式向指定页面注入一个 / 多个脚本文件(_或是其他的一些什么_),
这样你就可以在页面上主动的调用注入到页面的脚本或访问页面中的其他脚本,因为此时,你注入的脚本文件就属于指定页面了,而并非单纯的如同 contentScripts 中那样,是运行在网页的上下文中。
如:
// contentScripts
// 向页面注入 inject.js
function injectCustomJs(jsPath) {
jsPath = jsPath || 'js/inject.js';
var tempScript = document.createElement('script');
tempScript.setAttribute('type', 'text/javascript');
tempScript.src = chrome.extension.getURL(jsPath);
document.head.appendChild(tempScript); // 将指定 js 注入(添加)指定页面中
// 当注入的脚本加载完毕后移除它
tempScript.onload = function () {
this.parentNode.removeChild(this);
};
}
/**
* HTML被完全加载以及解析时,触发该事件
* 这时候,我们才将之注入到页面,否则会因为 HTML 未加载,脚本就注入完成,导致 DOM 中绑定的事件无效。
*/
document.addEventListener('DOMContentLoaded', () => {injectCustomJs()})
// inject.js
const cs = () => console.log('inject.js')
在做完往如上配置后,还必须在 manifest.json
中显示配置:
{
// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
"web_accessible_resources": ["js/inject.js"], // 在这里配置你注入的任意脚本!
}
该字段表示:想要在 web 中直接访问插件中的资源的话必须显示声明,否则会报错。
当一切都搞定后,此时,当我们点击 a.html
中的按钮时,控制台将会打印 inject.js;这就表明我们已经成功解决指定页面的 DOM 无法调用扩展的脚本。
更多信息,参见:inject-scripts
injectJS 不能访问 contentScripts?
虽然在 contentScripts 缺陷的解决方法 一节中,我们解决了指定页面的 DOM 访问扩展的脚本以及 contentScripts 访问页面的其他脚本问题,
但是该解决方法存在一个问题,即:injectJS 无法访问 contentScripts,这是因为二者是不同的,injectJS 就相当于页面上的脚本,而 contentScripts 仍然是运行在网页的上下文中;
contentScripts 访问不了页面的其他脚本文件,也自然无法访问 injectJS,反过来也是一样的:injectJS 无法通过普通方式访问 contentScripts。
那么该怎么解决呢?
参见:消息通信之 injectJS 和 contentScripts
详见:injecteJS 和 contentScripts 通信方法
note
- contentScripts 将先于指定页面加载
这意味着你需要首先在 contentScripts 中判断当前要注入 contentScripts 的 HTML 是否加载完毕,你才能获取到 DOM 元素(如:
document.head
等)
Reference
Doc
API