Fork me on GitHub

Chrome 插件实践指南

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

什么是 Chrome 插件

众所周知,在 Chrome 的多进程架构中,包含这样几种进程:

  • 浏览器主进程

    整个浏览器的主要进程,其他几个进程都是这个进程的子进程,由它来管理和调配;同时你所看到的浏览器的整个窗口,包含地址输入栏,书签栏这些东西也都是它来展示的;

  • 渲染进程

    一般来说一个 Tab 标签页面就是一个渲染进程;每个渲染进程中会运行 Blink 布局引擎,V8 JavaScript 执行引擎等,单独服务于一个 Tab 标签页;运行在沙盒中无法访问系统资源。

  • 插件进程

    一个插件单独存在于一个进程当中,同时为了安全性,运行在沙盒中限制其权限。

  • 网络进程

    发起网络请求访问。

  • GPU 进程

    处理 GPU 渲染方面的任务。

Chrome 插件就是运行在 Chrome 浏览器中的拓展程序,所属浏览器插件进程。类似 Electron 是运用 Web 技术加 Electron 提供给我们的 API 开发,Chrome 插件其实就是运用 Web 技术和 Chrome 的 API 开发的能增强 Chrome 功能的 web 软件而已。Chrome Extension API 中已经列出了我们所有能用到的 API,Chrome 插件可以做到但不限于以下功能:

  • 网络请求控制
  • 自定义右键
  • 网页中插入 CSS 、JavaScript 文件
  • 管理书签
  • 管理 cookie
  • omnibox 管理

基本概念

基本组成

开发一个 Chrome 插件最常用最基本组成包括:

  • manifest.json
  • background script
  • content script
  • popup

manifest.json

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
28
29
30
31
32
33
34
35
36
37
38
{
"name": "扩展名称",
"version": "1.0.0",
"manifest_version": 2,
"description": "扩展描述",
"icons" : { // 扩展的icon
"16" : "icon.png",
"48" : "icon.png",
"128" : "icon.png"
},
"browser_action": { // 程序图标会出现在地址栏右侧,若要出现在地址栏则写成 page_action
"default_title": "日报工具",
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": { // background 常驻 Chrome 后台的页面
"scripts": ["background.js"]
},
"content_scripts": [ // content_scripts 是在 Web 页面内运行的 javascript 脚本
{
"matches": [
"http://www.google.com/*"
],
"css": [
"custom.css"
],
"js": [
"custom.js"
],
"all_frames": true,
"run_at": "document_idle"
}
],
"permissions": [ // 一些权限的配置
"cookies", // cookie权限
"notifications" // 系统通知权限
]
}

background script

background 可以理解为是一个常驻 Chrome 后台的页面,只要浏览器打开它就存在,Chrome 关闭它才关闭,一般把需要一直运行的全局的代码都放在 background 里。

content script

在 Chrome 插件中使用 Content Scripts 有以下两种方式:

  • 在 manifest.json 文件中声明 Content Scripts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "version": "0.0.1",
    "name": "welearnmore-content_scripts",
    "manifest_version": 2,
    "description": "welearnmore",
    "content_scripts": [
    {
    "matches": ["https://icepy.me/*"],
    "js": ["content_scripts.js"]
    }
    ],
    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';"
    }
  • 动态注入 Content Scripts

    利用 chrome.tabs.executeScript 向页面动态注入一段 Content Scripts 。

    1
    2
    3
    4
    5
    chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
    chrome.tabs.create({ url: 'https://icepy.me'}, function(tab){
    chrome.tabs.executeScript(tab.id, {file: 'dynamic_content_scripts.js'});
    });
    })

Popup 就是一个普通的HTML文件,它可以包含任何内容,比如:HTMLCSSJavaScript,与普通网页唯一的区别是,它显示在浏览器的右上角。

如果要开启 Popup 需在 manifest.json 文件中 配置入口文件

1
2
3
4
5
6
7
8
9
{
"name": "我的popup扩展程序",
"version": "2.0",
"browser_action": {
"default_title": "popup action page",
"default_icon": "img/logo.png",
"default_popup": "popup.html"
}
}

通信机制

content script => background

1
2
3
4
5
6
7
8
9
10
11
12
// 在 content-script 端发送消息
chrome.runtime.sendMessege(
message,
function(response) {…}
)

// 在 background 端监听消息
chrome.runtime.onMessege.addListener(
function(request, sender, sendResponse) {
sendResponse('已收到消息:'+JSON.stringify(request))
}
)

background => content script

一个插件里只有一个 background 环境,而 content-script 可以有多个(一个页面一个)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取当前选项卡id
* @param callback - 获取到id后要执行的回调函数
*/
function getCurrentTabId(callback) {
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
if (callback) {
callback(tabs.length ? tabs[0].id: null);
}
});
}
// 在 background 端发送消息
chrome.tabs.sendMessage(tabId, message, function(response) {...});

// 在 content-script 端监听消息
chrome.runtime.onMessege.addListener(function(request, sender, sendResponse) {

})

popup 与 background 通信,可以使用chrome.runtime.sendMessagechrome.runtime.onMessage

1
2
3
4
5
6
// background.js 中,利用消息通信的机制,去打开一个tab,由 Popup 页面中的某个按钮来触发
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
chrome.tabs.create({ url: 'https://icepy.me'}, function(tab){
chrome.tabs.executeScript(tab.id, {file: 'dynamic_content_scripts.js'});
});
})

在 popup 中也可以直接调用 background 里的方法:

1
2
var bg = chrome.extension.getBackgroundPage();
bg.dosomething(); // dosomething 是 background 中一个 method

DNS 缓存清除插件开发

目录结构

1
2
3
4
5
6
./
├─ manifest.json //扩展的配置项
├─ Custom.js //自定义js脚本
├─ Custom.css //自定义css样式
├─ icon.png //扩展程序的icon
└─ popup.html //扩展的展示弹窗

代码实现

manifest.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "dns-clear",
"version": "0.0.1",
"description": "clear chrome dns cache",
"manifest_version": 2,
"browser_action": {
"default_title": "dns-clear",
"default_icon": "img/logo.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"]
},
"permissions": ["nativeMessaging", "tabs", "background"]
}
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
const CLEAR_DNS = 'clear dns';

var bmmode = localStorage.getItem('bmmode');

// option
var checkbox = document.getElementById('clear-checkbox');
checkbox.checked = bmmode === 'true' ? 'checked' : '';
checkbox.addEventListener('click', function() {
bmmode = this.checked;
localStorage.setItem('bmmode', bmmode);
});

// 清除 dns
var btn = document.getElementById('clear-btn');
btn.addEventListener('click', function() {
chrome.runtime.sendMessage(
{
action: CLEAR_DNS,
bmmode: bmmode
},
response => {
// 处理
console.log('response', response);
}
);
});

background

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
28
29
30
31
32
33
34
const CLEAR_DNS = 'clear dns';
const SOCKETS_URL = 'chrome://net-internals/#sockets';

var tabs = chrome.tabs;

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
var action = request.action;
var bmmode = request.bmmode;

switch (action) {
case CLEAR_DNS:
clearDns(bmmode);
break;
}
});

function clearDns(bmmode) {
var bm = chrome.benchmarking;
if (bm && bmmode === 'true') {
bm.clearHostResolverCache();
bm.clearCache();
bm.closeConnections();
} else {
tabs.query({ url: SOCKETS_URL }, function(tabArr) {
if (tabArr.length > 0) {
tabs.update(tabArr[0].id, { active: true }, function() {
tabs.reload();
});
} else {
tabs.create({ url: SOCKETS_URL, active: true }, function() {
tabs.reload();
});
}
});

安装

调试

加载完成后,可以看到 背景页,点击打开,就看到我们熟悉的调控台了。

参考文档