Chrome扩展程序 简述

547 阅读8分钟

一、简介

1. 是什么?

顾名思义,扩展程序就是可以扩展浏览器功能的应用程序。

它基于Web技术(例如HTML,JavaScript和CSS),可以实现浏览器原本无法实现的功能。

2. 能做什么

包括但不限于以下列举:

  • 自定义工具栏
  • 桌面通知
  • 标签页控制
  • 网页控制
  • 书签控制
  • 网络请求控制
  • 权限控制
  • 各类事件监听
  • 配置快捷键
  • 右键菜单控制
  • 消息传递

3. 举例

很多很多,我的浏览器上已安装的,将近二十了。列一下前端很常用的:

image1.png

image 2.png

image3.png

image4.png

4. 文件介绍

基础目录:

MyExtension ├─ manifest.json // 必须 ├─ imgs // 不必须 │ ├─icon1.png │ ├─icon2.png │ └─icon3.png ├─ popup.html // 不必须 ├─ popup.js // 不必须 ├─ 其他文件 // 不必须

哈,也就是说,只有一个是必须的文件,就可以写一个扩展程序,简单吧。

这个必须的文件就是manifest.json文件。

二、开始开发

1. Hello World

一个简单的扩展程序,只需声明一个manifest.json文件,这个文件需要包含一些必填字段:

image5.png

manifest_version:代表了manifest文件的版本,目前只可以填写2和3,注意1很少会支持了。浏览器会根据这个值去指定该版本拥有的功能。

name:插件的名称。

version:插件版本。

将manifest.json文件放到一个文件夹内。你的第一个程序就已经写好了。

2. Hello World的安装

打开浏览器的扩展工具管理页(在浏览器地址栏输入chrome://extensions/),启动开发者模式,点击 加载已解压的扩展程序,选择扩展工具所在目录即可安装。或者直接将文件夹拖动到管理页亦可。然后打开程序开关:

image.png

2c4yeo6187b5.png

好样的,你开发了一个谷歌扩展程序并可以开始使用了!

如果想打包的话,上图中有个按钮<打包扩展程序>,点它上传你的文件包(有个输入框是选填输入私钥文件的,第一次不用填,他主要是更新时用)。以前打包好的文件可以直接拖到扩展程序里运行,但是现在我拖进去,可以安装但是无法打开程序,因为现在要求只有上传的谷歌商店的才可以打开。如何上传因为需要绑定银行卡花钱注册,我这里就不讲了。

3. 做一个有功能的插件

为了快速了解扩展程序,上文开发的Hello World,是一个没有任何功能的废插件,那么如何做一个真正意义上的插件呢?

第一步:了解manifest.json文件的配置。

总体大约50多个配置,还是比较的多。不过对大部分开发者来说,很多配置是不常用的。想都了解的话去官网看吧,我这里先简单介绍一些很常用的。

{
  // 必须的字段
  "manifest_version": 2, // 用整数表示manifest文件自身格式的版本号
  "name": "My Extension",
  "version": "1.0",// 版本字符串

  // 建议提供的字段
  "default_locale": "en", // 国际化的支持
  "description": "A plain text description",
  "icons": {
    "16": "icon16.png", // 扩展程序页面上的网站图标
    "32": "icon32.png",  // Windows计算机通常需要此大小。提供此选项将防止48x48选项缩小造成的尺寸失真。
    "48": "icon48.png",  // 显示在扩展程序管理页面上
    "128": "icon128.png" // 在安装时和Chrome Webstore中显示
  },

  // 多选一,或者都不提供
  "browser_action": {...}, 
  "page_action": {...},
  "app": {...},
 
  // 根据需要提供
  "background": { // 会一直常驻的后台JS或后台页面
    // 推荐
    "persistent": false,
  },
  "content_scripts": [{...}], // 需要直接注入页面的JS文件
  "omnibox": { // 地址栏的快捷搜索
    "keyword": "aString"
  }, // 多功能地址栏
  "permissions": ["tabs"], // 扩展内将使用的一组权限
  "commands": {...},// 键盘快捷键
  "web_accessible_resources": ["js/inject.js"],// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
  "minimum_chrome_version": "versionString",// 扩展需要的chrome的最小支持版本
  "offline_enabled": true, // 指定扩展是否支持脱机运行
  "chrome_url_overrides":// 覆盖浏览器默认页面
   {
	// 覆盖浏览器默认的新标签页 掘金插件有使用
	"newtab": "newtab.html"
   },
  // 有点不常用的
  "devtools_page": "devtools.html"// devtools页面入口,注意只能指向一个HTML文件,不能是JS文件
  "homepage_url": "http://path/to/homepage",// 这个扩展的主页
  "options_ui":
	{
		"page": "options.html",
		// 添加一些默认的样式,推荐使用
		"chrome_style": true
	},
  ...
}

应用以上的配置,就已经能开发出很多特定功能的程序了。

其中,有几个需要我重点介绍下,因为他们有比较特殊的能力,而且很常用关键:background、content_scripts、inject-scripts、browser_action、permissions

background

background可以配置一个页面或多个js,当浏览器打开就开始运行,直到浏览器关闭(插件启动时)。

所以一般将浏览器一启动就运行、一直运行或需要监听浏览器事件的代码放到这里。

{
  ...
  // 一下两个配置只能二选一,可在管理扩展程序页对应的程序上看到<背景页>的文本链接
  "background": {
    // 指定一个网页,可通过相对路径引入js
    "page": "./html/background.html"
    // 指定若干个js文件
    "scripts": ["./js/background.js"]
  }
}

background拥有的权限比较高,几乎可以调用所有的Chrome扩展API(除了devtools),同时拥有直接跨域的能力。所以使用频率比较高。

通常会在js里对浏览器事件进行监听:

截屏2022-04-12 12.40.26.png

通过chrome-extension://<ID>/xx.html可以直接打开后台页

29x8dx96djwh.png

content_scripts

content_scripts配置能够向页面中注入脚本(包括js和css),其中js不会与页面脚本发生冲突(与网页的js在不同的环境作用域);也就是说它和原始页面共享DOM,但是不共享JS(例如某个JS变量)。

content_scripts配置的每一项之间js共享作用域,每项内的js可以注入多个并且共享作用域。

举例:当打开任意一个网页,运行other.js,当打开百度搜索首页,将搜索按钮的颜色改为红色,并增加点击事件:

29xyf39ee1a9.png

29xy9ew7ydj5.png

上例命中2个规则,依次会注入content.js、content2.js、other.js。

content-scripts能够访问的Chrome API的权限较少,只能访问以下四个API:

  • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
  • chrome.i18n
  • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
  • chrome.storage

如果还需要其他API的话,可以通过background.js以通信的方式传递过来。

inject-scripts

content-scripts通常只能操作DOM,但与原始页面的js无法相互访问。inject-scripts正好解决了这一点。

inject-scripts就是通过content-scripts往原始页面中插入script标签,动态引入js资源实现的。

举例:

百度页面登录后全局有bdUser变量,直接通过contentJs无法访问到,但是inject-scripts可以直接拿到。

截屏2022-04-12 12.51.25.png

截屏2022-04-12 12.58.41.png

截屏2022-04-12 12.57.34.png

需要注意的是,在网页中注入js资源必须配置web_accessible_resources,值为需要引入的资源路径数据。

browser_action

browser_action可以配置一个弹窗页面。它的配置包括一个图标、title、和popup页面的相对路径。图标会显示到浏览器右上角,当点击时会跳出弹窗,离开网页也会立即关闭。

主要特点是,完全自定义的页面,自适应大小,可快速方便的与用户交互,声明周期较短,权限较大,并可通过chrome.extension.getBackgroundPage()直接获取background的全局变量。

举例: 点击选项改变百度主题背景色。

29ynzm9fthc1.png

permissions

这个就比较简单了,当你发现某个API没有权限使用时,看看这里有没有配置吧。常用的权限包括:

  • 访问网页
  • 本地存储
  • web请求
  • 标签
  • 通知
  • 右键
  • 等等
{
  ...
  "permissions": [
    "contextMenus", // 右键菜单
    "tabs", // 标签
    "notifications", // 通知
    "webRequest", // web请求
    "webRequestBlocking",
    "storage", // 插件本地存储
    ...// 等等
  ]
}

background、content、inject和popup之间的关系图:

2a228x2pleyp.png

第二步:了解Chrome API。

Chrome 为扩展程序提供了许多特殊用途的API。有很多,但是我们不必所有的记住。初期我们只需要对大部分API有个了解,然后在开发时去查具体怎么用也是可以的。当然,喜欢深入研究和开发较为复杂的程序的伙伴例外。

对本文中涉及到的API我都会做一定的介绍,但API的讲解不是本文的重点,而且有很多API我也没有了解和使用过,也不敢在这里做总结了,还是自行查阅吧。

第三步:了解如何通讯。

主要指background、content_scripts、inject-scripts、browser_action(popup)之间的通信。

popup和backgrobaund之间的通信

popup可以直接调用background中的JS全局变量和方法,也可以直接访问background的DOM:

var bg = chrome.extension.getBackgroundPage(); 
bg.params(); // 访问bg的变量,注意不能是局部变量
bg.fn(); // 访问bg的方法 
console.log(bg.document.body.innerHTML); // 访问bg的DOM

background通常不会访问popup的数据,如果popup已经打开,也可以使用chrome.extension.getViews访问到弹窗:

var views = chrome.extension.getViews({type:'popup'});
if(views.length > 0) { console.log(views[0].location.href); }
popup或者backgrobaund向content之间的通信

popup或backgrobaund向content发送消息:

2c4z0vq2lszm.png

message可以是任意类型变量,无需JSON解析。需要指定将请求发送至哪个选项卡。

content向backgrobaund或popup发送消息:

29yxgbsj0v0h.png

injectJs和content之间的通信

有3种方法:

  1. 通过window.postMessage
  2. 通过自定义DOM事件来实现
  3. localStorage

第一种是以前前端使用iframe标签时的信息传递一样:

window.postMessage({"test": '你好!'}, '*');

接收:

window.addEventListener("message", function(e) { console.log(e.data); }, false);

第二种有些麻烦,需要自定义一个事件并绑定到隐藏的div里,通过隐藏的div传递:

inject-script中:

var customEvent = document.createEvent('Event');
customEvent.initEvent('myCustomEvent', true, true);
function fireCustomEvent(data) {
	hiddenDiv = document.getElementById('myCustomEventDiv');
	hiddenDiv.innerText = data
	hiddenDiv.dispatchEvent(customEvent);
}
fireCustomEvent('你好,我是普通JS!');

content-script.js中:

var hiddenDiv = document.getElementById('myCustomEventDiv');
if(!hiddenDiv) {
	hiddenDiv = document.createElement('div');
	hiddenDiv.style.display = 'none';
	document.body.appendChild(hiddenDiv);
}
hiddenDiv.addEventListener('myCustomEvent', function() {
	var eventData = document.getElementById('myCustomEventDiv').innerText;
	console.log('收到自定义事件消息:' + eventData);
});

这种方法很不友好。

第三种本地存储,需要注意异步问题,可能这边取的时候,那边还没存上。

上边讲到的sendMessage通信方式被称为短链接,其实chrome还提供了另一种通信方式,即长连接(chrome.tabs.connectchrome.runtime.connect),和WebSocket比较像,不具体介绍了,可直接查看API使用。

通讯方法表(横向看):

injectcontentpopupbackground
injectwindow.postMessage
customEvent
contentwindow.postMessage
customEvent
chrome.runtime.sendMessagechrome.runtime.sendMessage
popupchrome.tabs.sendMessagechrome.extension.getBackgroundPage
backgroundchrome.tabs.sendMessagechrome.extension.getViews
当你走到这一步,你已经有能力开发个不错的插件了,动手试一试吧。