前言
相关文章 谷歌官方文档 (需翻墙)
Chrome 插件开发全攻略 (强烈推荐看这一篇!)
你只需要看完上面那篇文章和掌握一些前端开发基础,就足以自行编写一个 Chrome 插件。本文也是基于上面文章加上自己之前写的插件所记。
什么是 Chrome 插件
如果你用过 Chrome 浏览器的话,也许会用到过一些插件,其中比较知名的就是油猴插件,通过这些插件能够帮你例如自动完成一些功能,屏蔽广告,相当于一个浏览器内置的脚本。应该来说这是 Chrome 扩展开发,不过说 Chrome 插件更顺口,后文也会说成 Chrome 插件。
安装 Chrome 插件
首先打开 Chrome,如下图即可进入插件的管理页面

这时候记得把右上角的开发者模式给勾上,如果不勾上的话你无法直接将文件夹拖入 Chrome 进行安装,就只能安装.crx格式的文件。Chrome 要求插件必须从它的 Chrome 应用商店(需要翻墙)安装,其它任何网站下载的都无法直接安装,所以可以把crx文件解压,然后通过开发者模式直接加载。
然后将写好的 Chrome 插件文件夹拖入到刚刚打开的插件管理页面即可。
Chrome 插件知识
manifest.json
是manifest.json切记不要英文单词打错字,一定要有这个文件,且需要放在根目录上,否则就会出现未能成功加载扩展程序的错误。
background.html 和 background.js
可以理解为后台,同时这个页面会一直常驻在浏览器中,而主要 background 权限非常高,几乎可以调用所有的 Chrome 扩展 API(除了 devtools),基本很多操作都是放在 background 执行,返回给 content,而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS。这对我们后面要在 content 中发送跨域请求至关重要!
我习惯的做法是通过”page”:"background.html"来导入background.js或其他 js 代码,如下
// manifest.json
 "background": {
    "page": "background.html",
  },
<!-- background.html -->
<!doctype html>
<html>
  <head>
    <title>背景页</title>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  </head>
  <body>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/background.js"></script>
  </body>
</html>
如果是 scripts 方式导入 js 文件则需要反复修改manifest.json文件。
关于乱码
有时候你在编写代码中出现了中文可能会出现了如下的乱码,

我遇到的原因是就是我原先的background.html代码写成如下的情况
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/background.js"></script>
没错,就只写了这两个行,就出现乱码(将 UTF-8 的编码变为了 windows1252),而只需要把 background.html 代码修改成正常的 HTML 结构,也就是上上面的那个代码即可解决该乱码情况。
content.js
我们主要的向页面注入脚本就依靠这个文件,相当于给页面添加了一个 js 文件,但是content和原始页面共享 DOM,但是不共享 JS,如要访问页面 JS(例如某个 JS 变量),只能通过injected js来实现(后文会提到)。并且content不能访问绝大部分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 来帮你调用。
inject.js
上文也说到了content是无法访问页面中的 JS,可以操作 DOM,但是 DOM 却不能调用它,也就是无法在 DOM 中通过绑定事件的方式调用content中的代码(包括直接写onclick和addEventListener2 种方式都不行),但是,在页面上添加一个按钮并调用插件的扩展 API是一个很常见的需求,那该怎么办呢?这时候就需要注入 inject.js 这个文件
document.addEventListener('DOMContentLoaded', function () {
  injectCustomJs()
})
// 向页面注入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)
}
还没有完,因为注入有权限,所以需要在 manifest.json 声明一下这个文件。也就是下面的这行代码
{
	// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
	"web_accessible_resources": ["js/inject.js"],
}
这样你就能调用
关于消息通信
Chrome 插件主要就 4 个部分组成,injected,content,popup,background,但这 4 个部分所对应的权限 ,应用都有可能各自不一,这时候就需要通过消息通信,将对应的数据发送到对应的文件,主要也就如下四种通信方式:
popup 和 background
popup 可以直接调用 background 中的 JS 方法,也可以直接访问 background 的 DOM:
// background.js
function test() {
  alert('我是background!')
}
// popup.js
var bg = chrome.extension.getBackgroundPage()
bg.test() // 访问bg的函数
alert(bg.document.body.innerHTML) // 访问bg的DOM
background访问popup如下(前提是popup已经打开):
var views = chrome.extension.getViews({ type: 'popup' })
if (views.length > 0) {
  console.log(views[0].location.href)
}
popup 或 bg 与 content
popup 或 bg 向 content 发送请求
//background.js或popup.js:
function sendMessageToContentScript(message, callback) {
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
      if (callback) callback(response)
    })
  })
}
sendMessageToContentScript({ cmd: 'test', value: '你好,我是popup!' }, function (response) {
  console.log('来自content的回复:' + response)
})
content.js通过监听事件接收:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
  if (request.cmd == 'test') alert(request.value)
  sendResponse('我收到了你的消息!')
})
content 向 popup 或 bg
// content.js
chrome.runtime.sendMessage(
  { greeting: '你好,我是content呀,我主动发消息给后台!' },
  function (response) {
    console.log('收到来自后台的回复:' + response)
  },
)
//background.js 或 popup.js:
// 监听来自content的消息
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  console.log('收到来自content的消息:')
  console.log(request, sender, sendResponse)
  sendResponse('我是后台,我已收到你的消息:' + JSON.stringify(request))
})
注意:
- content_scripts 向
popup主动发消息的前提是 popup 必须打开!否 则需要利用 background 作中转; - 如果 background 和 popup 同时监听,那么它们都可以同时收到消息,但是只有一个可以 sendResponse,一个先发送了,那么另外一个再发送就无效;
 
injected 和 content
主要就是injected向content发送,injected无需监听。
content和页面内的脚本(injected自然也属于页面内的脚本)之间唯一共享的东西就是页面的 DOM 元素,有 2 种方法可以实现二者通讯,:
- 可以通过
window.postMessage和window.addEventListener来实现二者消息通讯;(推荐) - 通过自定义 DOM 事件来实现(我就懒得写了,没怎么用到);
 
injected中:
window.postMessage({ test: '你好!' }, '*')
content script中:
window.addEventListener(
  'message',
  function (e) {
    console.log(e.data)
  },
  false,
)
injected 与 popup
injected无法直接和popup通信,必须借助content作为中间人。不过一般这种都少,直接和 bg 通信即可。
我的模板
关于 Chrome 的主要内容也就这些,实际开发如果有个模板就能大大方便开发,在原文章中该作者已经分享了有对应的源代码,这里放上我自写的 Chrome 模板编写过程。

当然,这里需要提几点地方: