chrome插件(扩展)开发指南(一):概览

2,133 阅读4分钟

前言

chrome插件大家应该都不陌生了,或多或少都会装几个。那么作为前端开发者,当然要考虑用插件来做一些提升开发效率的工具了。

插件能做什么?

  • 抓取埋点请求,格式化埋点数据。
    使用gif图上报埋点是很常见的场景,这种数据一般是一坨放在url参数后面,测试和产品在验收时都很麻烦(直接看数据后台可能会受到其他数据的影响),使用插件拦截的http请求后,可以将所有埋点的请求记录下来,格式化后呈现。

  • 请求mock
    拦截http请求,修改入参和出参,比charles、Fiddler等第三方工具方便太多了。

  • 在线UI图的图片素材直接上传CDN
    现在很多公司都在用蓝湖、慕客这种在线UI稿,图片素材需要先下载到本地,再上传到CDN,使用插件可以做到点击页面的图片直接上传到自己的CDN,复制好CDN链接。

  • 自动登录,切换账号
    开发过程中经常要切换环境、切换账号,使用插件记录不同的环境和不同类型的账号,快捷切换。

  • ...

以上都是我在开发过程中真正使用了的插件,提升了开发效率,对测试和产品也有很大帮助。

这个系列文章最终会实现一个自动登录/切换环境的插件,并可简单识别验证码。

本章节代码放在这里

插件效果如下:

2022-09-13 11.29.48.gif

文章目标

  • 插件的开发流程
  • 基础功能介绍
  • 搭建vue开发环境
  • 开发一个切换环境与账户的插件

参考资料

谷歌官方文档

【干货】Chrome插件(扩展)开发全攻略,这个教程写的很不错,只是时间有点久了,文章采用的v2版本,目前chrome已经停止了接收v2版本的插件(本地开发可以使用)。v3版本的修改也不是特别多,所以这篇文章也还是可以继续参考的。

实现一个最基本的插件

采用官方文档的示例,实现一个改变当前页面背景色的插件,以此来对插件的文件结构和开发流程有一个简单的认识。

新建manifest文件

新建插件目录chrome-ext-change-bg, 目录下新建manifest.json文件,来对项目进行声明及配置,完整的配置项可以看这里

{
  "name": "change bg", // 插件名
  "description": "change current page background color", // 描述
  "version": "1.0", // 插件版本
  "manifest_version": 3,  // manifest版本,固定写3即可
}

在chrome中加载插件

没错,建好manifest文件, 插件就已经创建好了(当然了,没有任何功能),在chrome中把我们的插件加载进去:

  1. 地址栏输入chrome://extensions打开扩展程序设置(也可以在设置中进入)。
  2. 打开右上角开发者模式,点击加载已解压的扩展程序,选择插件文件夹即可。

image.png

此时没有指定icon图标,会以插件首字母作为默认的图标。

点击图中按钮,将我们的插件固定在任务栏。

image.png

注册background script

要让插件执行某些功能,就需要一个运行环境background script就是这个运行环境之一(其他环境我们后面再说)。

manifest.json添加background配置:

{
  // ...
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js" // 声明一个service_worker,代码内容为当前目录下的`background.js`
  }
}

现在chrome知道了你的插件包含一个service worker,在你重新加载插件时,chrome会运行声明的文件(这里即background.js)。这个worker的生命周期贯穿整个chrome的生命周期,从插件加载(打开chrome)开始运行,到关闭chrome后停止。所以如果有什么需要在插件开始运行时就执行的操作,如插件事件的监听,可以放到这里。

创建background script的执行文件

我们在插件加载完毕(installed)后,使用 storage API存储一个默认的颜色值。

在插件目录下新建background.js,写入如下代码:

// background.js

let color = '#3aa757';

chrome.runtime.onInstalled.addListener(() => {
  chrome.storage.sync.set({ color });
  console.log('Default background color set to %cgreen', `color: ${color}`);
});

插件API chrome.*

chrome 插件 API 在插件的运行环境中,chrome提供了一些API,这些API都挂载在全局变量chrome上,使用这些API,可以获取当打开的所有页面(tabs),在插件中存储一些持久化数据(如上面的storage)...
大部分API的功能在使用时,都要在manifest中进行权限声明。

添加storage权限声明

要使用storage,需要在manifest中声明权限。插件的权限要求比较严格,http请求、加载外部资源等,都需要声明。

{
  "permissions": ["storage"]
}

检查background script

回到插件的设置页,点击Reload图标重新加载插件(没有热更新,每次修改都需要reload)

image.png

点击service worker查看background script的打印, "Default background color set to green"

image.png

用户界面

用户界面有好几种类型,每个界面的形式都是一个html页面。这里我们先使用popup。就是左键点击chrome右上角的插件图标,弹出的界面。

image.png

vue调试工具的popup

在根目录创建popup.html。放置一个按钮来修改背景色,与普通的html页面一样,可以引入css文件。

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="button.css">
  </head>
  <body>
    <button id="changeColor"></button>
  </body>
</html>

创建button.css

button {
  height: 30px;
  width: 30px;
  outline: none;
  margin: 10px;
  border: none;
  border-radius: 2px;
}

button.current {
  box-shadow: 0 0 0 2px white,
              0 0 0 4px black;
}

与background script类似,这个文件也必须在manifest里声明。添加action配置来声明popup.html

{
  "name": "change bg",
  "description": "change current page background color",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["storage"],
  "action": {
    "default_popup": "popup.html"
  }
}

插件在工具栏的图标也是在action里设置的,图片文件可以在这里下载,解压到插件根目录。

{
 // ...
 "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "/images/get_started16.png",
      "32": "/images/get_started32.png",
      "48": "/images/get_started48.png",
      "128": "/images/get_started128.png"
    }
  }
}

在chrome插件管理页面和警告、其他用户界面的favicon,也会显示图标,这些图标的配置在manifest配置项的根节点下的icons.

{
  "name": "change bg",
  "description": "change current page background color",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["storage"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "/images/get_started16.png",
      "32": "/images/get_started32.png",
      "48": "/images/get_started48.png",
      "128": "/images/get_started128.png"
    }
  },
  "icons": {
    "16": "/images/get_started16.png",
    "32": "/images/get_started32.png",
    "48": "/images/get_started48.png",
    "128": "/images/get_started128.png"
  }
}

刷新插件,可以看到chrome工具栏的插件图标已经变化了

image.png

创建popup.js,给popup界面的按钮加上之前在content script 里设置的颜色

// Initialize button with user's preferred color
let changeColor = document.getElementById("changeColor");

chrome.storage.sync.get("color", ({ color }) => {
  changeColor.style.backgroundColor = color;
});

popup.html中插入

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="button.css">
  </head>
  <body>
    <button id="changeColor"></button>
    <script src="popup.js"></script>
  </body>
</html>

重新加载插件,可以看到按钮变成了绿色

image.png

content script

目前为止我们使用了两种代码的运行环境:

  • background script
  • popup

这两个环境都是独立于用户浏览的实际页面的(将这些页面称为web page),我们想修改web page的颜色,需要让代码在web page里执行,这种代码就叫做content script

content script是运行在web page中的代码,可以操作页面DOM,修改它们,或者读取页面信息传递给background script等其他环境(使用messages通信)。

与普通的js不同,在content script中还可以使用一部分chrome插件的API,如获取插件的storage中内容,使用messages与插件其他环境通信...但是相比于其他环境,做了很大的限制。

在content script中支持的API:

content script的执行方式

content script的执行方式有两种:

  1. 动态插入
    background scriptpopup等环境中,通过chrome提供的API: chrome.scripting.executeScript,动态插入到web page中执行,支持插入代码块和代码文件。
chrome.scripting.executeScript({
    target: { tabId: tab.id }, // 要插入的web page的id
    func: setPageBackgroundColor, // 插入代码块
    files: ['content-script.js'] // 插入文件
});

需要注意的是,如果是插入代码块(func),chrome会将函数体复制后执行,而不是直接执行原函数,所以在函数体内不能使用外部变量,否则会报变量未声明错误。

  1. 声明式
    在manifest中声明需要插入的content script文件,可配置需要插入的页面url规则,以及执行时机(如页面加载完毕后执行)。
{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

改变页面背景色

现在我们的插件有了图标和popup交互界面,并且把popup中按钮的颜色设置为存在插件的storage中的颜色值。接下来在popup.js后添加逻辑,点击popup的绿色按钮,让当前页面的背景色改变。而让当前页面(web page)背景色改变,就需要使用content script了。

因为我们要在popup中点击按钮时才改变页面背景色(即触发content script执行),所以采用方式1来动态插入content script。

popup.js文件末尾添加如下代码:

// 点击按钮时,将 setPageBackgroundColor 函数插入到当前页面执行
changeColor.addEventListener("click", async () => {
  // chrome.* 是chrome给插件提供的api,后面会再详细说明
  // 获取当前页面
  let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

  // 使用chrome.scripting.executeScript, 在web page中执行代码块(content script)
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: setPageBackgroundColor,
  });
});

// 这个函数的「函数体」将作为当前页面content script被执行
function setPageBackgroundColor() {
  // content script可以
  chrome.storage.sync.get("color", ({ color }) => {
    document.body.style.backgroundColor = color;
  });
}

这段代码给button添加了点击事件,点击后将改变背景色的代码块(setPageBackgroundColor)插入到当前正在浏览的web page中,改变其背景色。

对于上面使用到的API,要更新权限声明,修改manifest.json

{
  "name": "change bg",
  ...
  "permissions": ["storage", "activeTab", "scripting"],
  ...
}

保存后重新加载插件,点击工具栏的插件图标,点击绿色按钮,当前页面的背景色就会被改为绿色了。

2022-09-23 15.03.13.gif

插件配置页options

现在我们的插件已经可以修改页面的背景色了,但是只能修改为默认的绿色。现在来提供一个选项页面,让用户可以选择更多的背景色。

options页面是个交互界面,与popup不同的是,popup可以理解为用户的操作界面,而options则是一个用来让用户配置插件选项的页面

在根目录新建options.html

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="button.css">
  </head>
  <body>
    <div id="buttonDiv">
    </div>
    <div>
      <p>Choose a different background color!</p>
    </div>
    <script src="options.js"></script>
  </body>
</html>

在manifest中注册

{
  "name": "change bg",
  ...
  "options_page": "options.html"
}

创建options.js

let page = document.getElementById("buttonDiv");
let selectedClassName = "current";
const presetButtonColors = ["#3aa757", "#e8453c", "#f9bb2d", "#4688f1"];

// 点击不同的颜色块,将颜色值存入storage
function handleButtonClick(event) {
  // Remove styling from the previously selected color
  let current = event.target.parentElement.querySelector(
    `.${selectedClassName}`
  );
  if (current && current !== event.target) {
    current.classList.remove(selectedClassName);
  }

  // Mark the button as selected
  let color = event.target.dataset.color;
  event.target.classList.add(selectedClassName);
  chrome.storage.sync.set({ color });
}

// 将几种预设的颜色按钮渲染到options页面中,添加点击事件
function constructOptions(buttonColors) {
  chrome.storage.sync.get("color", (data) => {
    let currentColor = data.color;
    // For each color we were provided…
    for (let buttonColor of buttonColors) {
      // …create a button with that color…
      let button = document.createElement("button");
      button.dataset.color = buttonColor;
      button.style.backgroundColor = buttonColor;

      // …mark the currently selected color…
      if (buttonColor === currentColor) {
        button.classList.add(selectedClassName);
      }

      // …and register a listener for when that button is clicked
      button.addEventListener("click", handleButtonClick);
      page.appendChild(button);
    }
  });
}

// 初始化颜色按钮
constructOptions(presetButtonColors);

重新加载组件,右键点击工具栏的插件图标,进入选项页面。

2022-09-23 15.24.14.gif