插件是一个压缩文件,文件名字是以.crx 结尾的文件,压缩包内部由js html css image等资源组成。

浏览器插件(Chrome Extension)

简介

主要使用web前端技术开发,可以用来增强浏览器功能,例如网络代理、请求拦截、广告过滤、修改页面、截屏、页面的打开与关闭、通知等等。

浏览器插件可以安装到以webkit内核的国产浏览器, 例如360安全浏览器、微软edge浏览器等等。

tip: 如果本地离线安装,需要将修改后缀.crx为.zip自动解压,在开启浏览器插件开发者模式-> 加载已解压的扩展程序->选择对应的插件目录确认即可导入

查看浏览器插件的属性结构

例如浏览器代理插件: SwitchyOmega

插件案例

解压后的文件属性结构如下

 1.
 2├── AUTHORS
 3├── COPYING
 4├── _locales  // 国际化
 5│   ├── ...
 6│   └── zh_CN
 7├── _metadata
 8│   └── verified_contents.json
 9├── background.html
10├── css
11│   ├── options.css
12│   └── popup.css
13├── img
14│   └── icons
15├── js // 项目的js
16│   ├── background.js  // 后台运行的js, v3新增
17│   ├── options.js
18│   ├── .....
19│   └── popup.js
20├── lib  // 类库
21│   ├── angular
22│   ├── bootstrap
23│   ├── jquery
24│   ├── .....
25│   └── script.js
26├── manifest.json  // 必须文件
27├── options.html  // 点击插件图片右击页面
28├── popup  // 点击插件弹出的页面
29│   ├── css
30│   ├── index.html
31│   ├── js
32│   └── proxy_not_controllable.html
33└── popup.html

插件都可以干什么

  • 书签控制: bookmark的读取, 删除、分组等
  • 下载控制: 典型的m3u8下载
  • 窗口控制: 控制窗口大小
  • 标签控制: 打开、关闭、高亮、选中等
  • 网络请求控制: 对执行url进行处理, content_script.js 脚本处理
  • 自定义原生菜单: 在浏览器 右击 展示功能
  • 完善的通信机制:

开发工具

  1. 文本编辑器+vim

  2. vscode + ESLint (推荐, 不推荐webstrome), 符合规范,不容易出问题

  3. 脚手架

使用脚手架插件快速创建chrome-extension-cli浏览器的扩展程序

在浏览器-扩展插件-中开启 开发者模式, 然后加载创建的项目

debug

  1. 脚本记录
 1# 忽略vscode编辑器和vscode插件eslint的安装
 2# 安装 nodejs
 3brew install nodejs
 4# 安装脚手架
 5npm install -g chrome-extension-cli
 6
 7# 在当前目录创建名字为 simple-plugin.js ,开发语言使用JavaScript的插件
 8# 注意后面的 --language= 值是小写 ,支持ts(typescript)、js(javascript)
 9chrome-extension-cli ./simple-plugin.js --language=javascript
10
11输入内容如下:
12Creating a new Chrome extension in ..../simple-plugin.js
13
14Installing packages. This might take a couple of minutes.
15Installing webpack, webpack-cli and few more...
16Installing packages. This might take a couple of minutes.
17Installing webpack, webpack-cli and few more...
18
19npm notice created a lockfile as package-lock.json. You should commit this file.
20+ copy-webpack-plugin@10.2.4
21+ file-loader@6.2.0
22+ mini-css-extract-plugin@2.7.2
23+ css-loader@6.7.3
24+ prettier@2.8.4
25+ webpack-merge@5.8.0
26+ webpack-cli@4.10.0
27+ webpack@5.75.0
28added 181 packages from 195 contributors and audited 181 packages in 61.132s
29
3029 packages are looking for funding
31  run `npm fund` for details
32
33found 0 vulnerabilities
34
35Generated a README file.
36
37Success! Created simple-plugin.js at /Users/liubaixun/Downloads/temp2/simple-plugin.js
38Inside that directory, you can run below commands:
39
40  npm run watch
41    Listens for files changes and rebuilds automatically.
42
43  npm run build
44    Bundles the app into static files for Chrome store.
45
46  npm run format
47    Formats all the files.
48
49We suggest that you begin by typing:
50
51  1. cd simple-plugin.js
52  2. Run npm run watch
53  3. Open chrome://extensions
54  4. Check the Developer mode checkbox
55  5. Click on the Load unpacked extension button
56  6. Select the folder simple-plugin.js/build
  1. 查看创建后的文件属性结构

执行命令: tree -I "node_modules" -I 代表忽略某个目录, 可以增加-L 展示树形的深度

 1.
 2├── README.md // 项目的说明
 3├── build // 编译之后代码,浏览器需要加载此目录中的代码。
 4├── config
 5│   ├── paths.js
 6│   ├── webpack.common.js
 7│   └── webpack.config.js // 如果新增js css需要再里面的entry增加
 8├── package-lock.json //
 9├── package.json // nodejs 包管理
10├── public
11│   ├── icons // 默认插件的图标,替换对应的即可
12│   │   ├── icon_128.png
13│   │   ├── icon_16.png
14│   │   ├── icon_32.png
15│   │   └── icon_48.png
16│   ├── manifest.json // 插件核心文件,配置js、注入、权限等等
17│   └── popup.html // 弹出页面
18└── src // 代码的主要书写目录
19    ├── background.js // 后端运行
20    ├── contentScript.js // 内容处理js, 注入到执行页面中执行注入的代码
21    ├── popup.css // 弹出页面的css样式
22    └── popup.js // 弹出页面的js控制,
23
245 directories, 16 files

开发项目

  1. 进入上步骤创建的项目: simple-plugin.js

开发开发模式,会自动检测目录中的文件的改动会自动编译到./build目录下。

  1. 自动编译

npm run watch

  1. 进入vscode编写自己的代码即可

  2. 打开 chrome://extensions/, 验证插件, 如果修改了js,可以重载代码

核心文件描述

  1. manifest.json说明
 1{
 2  //chrome插件的版本
 3  "manifest_version": 3,
 4  //插件名称
 5  "name": "ChromeName",
 6  //插件版本号
 7  "version": "1.0.0",
 8  //插件描述
 9  "description": "__MSG_Plugin_Desc__",
10  //默认语言(如果当前浏览器设置的语言不存在多语言配置文件,则默认中文),Chrome插件的多语言只能根据当前浏览器设置的语言来设定,无法通过代码更改语言
11  "default_locale": "zh_CN",
12   //内容安全政策,V2的value是字符串,V3是对象
13  "content_security_policy": {
14    //原文:此政策涵盖您的扩展程序中的页面,包括 html 文件和服务人员;具体不是很明白,但是参数值得是self,即当前自己
15    "extension_pages": "script-src 'self'; object-src 'self'",
16    //原文:此政策涵盖您的扩展程序使用的任何[沙盒扩展程序页面];具体不是很明白,但是参数值得是self,即当前自己
17    "sandbox": "sandbox allow-scripts; script-src 'self'; object-src 'self'"
18  },
19   //key,发布插件后会给一个key,把那个key的值放这里
20  "key": "xxx",
21  //icon,浏览器扩展程序管理里面的图标、浏览器右侧插件图标点开的下拉菜单展示的已开启插件的图标、以及插件详情页的标签卡的那个小图标
22  "icons": {
23    "16": "static/img/logo-16.png",
24    "19": "static/img/logo-19.png",
25    "38": "static/img/logo-38.png",
26    "48": "static/img/logo-48.png",
27    "128": "static/img/logo-128.png"
28  },
29  //背景页,后台脚本引入,v2是scripts:[xxx,xxx],可以引入多个js文件,v3是service_worker:'xxx',只能引入一个js,v3版最大的改动应该就是这里了,扩展程序管理界面的插件的那个“背景页”也将变成“Service Worker”,改动之后background.js将和浏览器完全分离,即无法调用window和ducoment对象
30  //可以看介绍:
31  //1、//developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#background-service-workers;
32  //2、//developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/
33  "background": {
34    "service_worker": "background.js"
35  },
36  //注入脚本,值是个数组对象,可以有多个对象
37  "content_scripts": [
38    //每个对象代表一个注入的配置
39    {
40      //需要在指定页面注入的js脚本文件
41      "js": [
42        "xxx.js",
43        "xxx.js",
44      ],
45       //需要注入js脚本文件的指定页面
46      "matches": [
47        "https://*.csdn.net/*",
48        "https://*.xxx.com/*"
49      ],
50       //不允许注入js脚本文件的指定页面
51       "exclude_matches": ["https://*.xxx.com/*"],
52       //什么时候注入的js脚本,document_start=页面加载开始时,document_end=页面加载结束时
53      "run_at": "document_end"
54    }
55  ],
56  //API权限,需要使用某些API时需要设置该API权限才行
57  "permissions": [
58      "contextMenus", //自定义创建右键菜单API
59      "tabs", //tab选项卡API
60      "storage", //缓存API
61      "webRequest", //监听浏览器请求API
62      ...
63  ],
64  //主机权限,在背景页backgroud.js里面或者popup页面走请求时,请求域名的白名单权限,如果没添加的则请求会失败
65  "host_permissions": [
66    "https://*.csdn.net/*",
67    "https://*.xxx.com/*"
68  ],
69  //动作API,原文:在 Manifest V2 中,有两种不同的 API 来实现操作: `"browser_action"` 和 `"page_action"` .
70  //这些 API 在引入时扮演了不同的角色,但随着时间的推移它们变得多余,因此在 Manifest V3 中,我们将它们统一为单个 `"action"` API;
71  //配置上action:{},可以是空对象,但是action这个配置得有,不然的话扩展程序管理界面的“Service Worker”将显示无效,
72  //且无法点开“Service Worker”的开发者工具控制台以及点击插件图标时触发的这个方法会报错chrome.action.onClicked.addListener,
73  "action": {
74  },
75  //通过网络访问的资源,v2是提供一个数组,v3得提供数组对象,每个对象可以映射到一组资源到一组 URL 或扩展 ID
76  "web_accessible_resources": [{
77    //允许访问的资源路径,数组传多个参数
78    "resources": ["*/img/xxx.png", "*/img/xxx2.png"],
79
80    //允许访问资源的页面
81    "matches": [
82      "https://*.csdn.net/*",
83      "https://*.xxx.com/*"
84    ]
85  }]
86}
  1. 可以查看查看官网全部模版 一下是复制官网模版的内容,常用的注明了说明
 1{
 2  // Required
 3  "manifest_version": 3, // 3 或者 2, 目前官方已经 标记废弃2, 推荐使用3
 4  "name": "My Extension", // 插件的名字, 鼠标悬浮在图标上提示的名字
 5  "version": "1.0.1", // 插件的版本号
 6
 7  // Recommended
 8  "action": {
 9     "default_popup": "popup.html" // 点击插件的图标,会弹出这个页面, 建议保留这个名字符合插件的规范
10  },
11  "default_locale": "en",
12  "description": "A plain text description",
13  "icons": { // 图标,全部用一个尺寸 省事
14    "16": "image/logo.png",
15    "48": "image/logo.png",
16    "128": "image/logo.png"
17  },
18
19  // Optional
20  "author": "developer@example.com",
21  "automation": {...},
22  "background": { // 注意 v3改动之后background.js将和浏览器完全分离,即无法调用window和ducoment对象
23    // v2的是 scripts:[xxx,xxx],可以引入多个js文件
24    // v3: v3是service_worker:'xxx',只能引入一个js
25    "service_worker": "background.js" // 后台运行的js
26  },
27  "host_permissions": [
28    "https://developer.chrome.com/*", // 只允许访问 developer.chrome.com 下的 url
29    "<all_urls>" // 允许访问所有url
30  ],
31  // https://developer.chrome.com/docs/extensions/reference/
32  "permissions": [
33    // 常用的 权限如下
34    "storage", // 本地存储
35    "activeTab", // 激活tab权限
36    "scripting", // 脚本 content_scripts节点下 执行 js
37    "cookies", // cookies
38    "background", // 后台运行
39    "notifications", // 通知
40    "webRequest" // 监听浏览器请求API
41    "contextMenus", //自定义创建右键菜单API
42    "tabs", //tab选项卡API
43  ],
44  "optional_host_permissions": ["..."],
45  "optional_permissions": ["..."],
46  "chrome_settings_overrides": {...},
47  "chrome_url_overrides": {...},
48  "commands": {...},
49  "content_scripts": [{...}],
50  "content_security_policy": {
51    //原文:此政策涵盖您的扩展程序中的页面,包括 html 文件和服务人员;具体不是很明白,但是参数值得是self,即当前自己
52    "extension_pages": "script-src 'self'; object-src 'self'",
53    //原文:此政策涵盖您的扩展程序使用的任何[沙盒扩展程序页面];具体不是很明白,但是参数值得是self,即当前自己
54    "sandbox": "sandbox allow-scripts; script-src 'self'; object-src 'self'"
55  },
56  "cross_origin_embedder_policy": {...},
57  "cross_origin_opener_policy": {...},
58  "declarative_net_request": {...},
59  "devtools_page": "devtools.html",
60  "event_rules": [{...}],
61  "export": {...},
62  "externally_connectable": {...},
63  "file_browser_handlers": [...],
64  "file_system_provider_capabilities": {...},
65  "homepage_url": "https://path/to/homepage",
66  "import": [{...}],
67  "incognito": "spanning, split, or not_allowed",
68  "input_components": [{...}],
69  "key": "publicKey",
70  "minimum_chrome_version": "107",
71  "nacl_modules": [...],
72  "oauth2": {...},
73  "omnibox": {...},
74  "options_page": "options.html",
75  "options_ui": {...},
76  "replacement_web_app": "https://example.com",
77  "requirements": {...},
78  "sandbox": {...}, // 沙盒相关
79  "short_name": "Short Name",
80  "storage": {...},
81  "tts_engine": {...},
82  "update_url": "https://path/to/updateInfo.xml",
83  "version_name": "1.0 beta",
84  "web_accessible_resources": [...]
85}

backgroup.js介绍

常驻后台脚本,后台脚本引入

v2是scripts:[xxx,xxx],可以引入多个js文件,

v3是service_worker:‘xxx’,只能引入一个js,v3版最大的改动应该就是这里了,扩展程序管理界面的插件的那个“背景页”也将变成“Service Worker”,改动之后background.js将和浏览器完全分离,即无法调用window和ducoment对象。后面课程将详细进行介绍和演示。

Service Worker 是您的浏览器在后台运行的脚本,与网页分开,为不需要网页或用户交互的功能打开了大门。” 这项技术可以实现类似原生的体验,例如推送通知、丰富的离线支持、后台同步和开放网络上的“添加到主屏幕”。

1{
2  ...
3  "background": {
4      "service_worker": "background.js"
5  },
6  ...
7}

具体的backgroup.js如下

  1'use strict';
  2
  3// With background scripts you can communicate with popup
  4// and contentScript files.
  5// For more information on background script,
  6// See https://developer.chrome.com/extensions/background_pages
  7
  8// 通知代码 脚手架自动生成
  9chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
 10  if (request.type === 'GREETINGS') {
 11    const message = `Hi ${
 12      sender.tab ? 'Con' : 'Pop'
 13    }, my name is Bac. I am from Background. It's great to hear from you.`;
 14
 15    // Log message coming from the `request` parameter
 16    console.log(request.payload.message);
 17    // Send a response message
 18    sendResponse({
 19      message,
 20    });
 21  }
 22
 23});
 24
 25// 一旦扩展安装后显示演示页面'
 26chrome.runtime.onInstalled.addListener((_reason) => {
 27    console.log("install success!");
 28    chrome.tabs.create({
 29      url: 'help.html' // 安装完插件后自动跳转到 help.html页面
 30    });
 31
 32    // contexts: all link image video audio .... 菜单在右击的时候 是否展示 选择器
 33    chrome.contextMenus.create({
 34      id: 'open_popup',
 35      title: "打开Popup",
 36      type: 'normal',
 37      contexts: ['page'] // 页面上 右键时 展示改菜单
 38    });
 39
 40    chrome.contextMenus.create({
 41      id: 'close_popup',
 42      title: "关闭Popup",
 43      type: 'normal',
 44      contexts: ['link'], // 在网页中的链接上 右键时 展示改菜单
 45    });
 46
 47    chrome.contextMenus.create({
 48      id: 'open_url',
 49      title: "打开官网",
 50      type: 'normal',
 51      contexts: ['image'], // 在网页中的图片上 右键时 展示改菜单
 52    });
 53});
 54
 55// Open a new search tab when the user clicks a context menu
 56chrome.contextMenus.onClicked.addListener((item, tab) => {
 57    const id = item.menuItemId
 58    if (id === 'close_popup') {
 59        chrome.action.disable();
 60    } else if (id === 'open_popup') {
 61        chrome.action.enable();
 62    } else if (id === 'open_url') {
 63        chrome.tabs.create({ url: "https://baidu.com", index: tab.index + 1 });
 64    }
 65});
 66
 67// tab 的创建时间
 68chrome.tabs.onCreated.addListener(async (data) => {
 69  console.log('tab onCreated', data);
 70});
 71
 72chrome.tabs.onActivated.addListener(async (data) => {
 73  console.log('tab onActivated', data);
 74});
 75
 76chrome.tabs.onAttached.addListener(async (tabId, selectInfo) => {
 77  console.log('tab onAttached', tabId, selectInfo);
 78});
 79
 80chrome.tabs.onCreated.addListener(async (data) => {
 81  console.log('tab onCreated', data);
 82});
 83
 84chrome.tabs.onDetached.addListener(async (data) => {
 85  console.log('tab onDetached', data);
 86});
 87
 88chrome.tabs.onHighlighted.addListener(async (data) => {
 89  console.log('tab onHighlighted', data);
 90});
 91
 92chrome.tabs.onMoved.addListener(async (data) => {
 93  console.log('tab onMoved', data);
 94});
 95
 96chrome.tabs.onRemoved.addListener(async (data) => {
 97  console.log('tab onRemoved', data);
 98});
 99
100chrome.tabs.onReplaced.addListener(async (data) => {
101  console.log('tab onReplaced', data);
102});
103
104// 标签组的事件
105chrome.tabGroups.onCreated.addListener(async (data) => {
106  console.log('tabGroup onCreated', data);
107});
108
109chrome.tabGroups.onMoved.addListener(async (data) => {
110  console.log('tabGroup onMoved', data);
111});
112
113chrome.tabGroups.onRemoved.addListener(async (data) => {
114  console.log('tabGroup onRemoved', data);
115});
116
117chrome.tabGroups.onUpdated.addListener(async (data) => {
118  console.log('tabGroup onUpdated', data);
119});

content_scripts 脚本

  1. 在 manifest.json中配置,主要作用是向页面注入自定义脚本 ,值是个数组对象,可以有多个对象;

内容脚本是在网页上下文中运行的文件。通过使用标准文档对象模型(DOM),可以够读取浏览器访问的网页的详细信息,对其进行更改,并将信息传递给其父扩展。

  • js:字符串数组;可选的。要注入匹配页面的 JavaScript 文件列表。这些是按照它们在这个数组中出现的顺序注入的。
  • css:字符串数组;可选的。要注入匹配页面的 CSS 文件列表。在为页面构建或显示任何 DOM 之前,它们按照它们在此数组中出现的顺序被注入。
  • matches:字符串数组 必需的。指定此内容脚本将被注入到哪些页面。有关这些字符串的语法的更多详细信息
  • match_about_blank:布尔值 可选的。脚本是否应该注入about:blank父框架或开启框架与matches. 默认为假
  • exclude_matches: 字符串数组 可选的。排除此内容脚本将被注入的页面。有关这些字符串的语法的更多详细信息
  • include_globs: 字符串数组 可选的。之后应用matches以仅包含那些也匹配此 glob 的 URL。
  • exclude_globs: 字符串数组 可选的。之后应用matches以排除与此 glob 匹配的 URL。
  1. 使用方式

manifest.json

 1{
 2....
 3 "content_scripts": [
 4    {
 5      "matches": ["<all_urls>"],
 6      "run_at": "document_idle",
 7      "css": ["yxcss.css"],
 8      "js": ["jquery.js", "yxjs.js"]
 9    },
10    {
11      // 只有匹配了 才会注入 js里面的 脚本代码
12      // 匹配所有url 可以执行url,格式是正则格式的,
13      // 例如: "https://*.clibing.com/*" 代表匹配clibing.com下的所有域名
14      "matches": ["<all_urls>"],
15      // "css": [""]  可以注入css
16      // 什么时候注入的js脚本,
17      // document_start=页面加载开始时,
18      // document_end=页面加载结束时,
19      // document_idle 默认
20      // 这个很重要,比如如果引入的JS中使用到了onload就要选择document_start,否则会错过事件
21      // 但是如果没有类似的事件的话最好保持默认值document_idle,这样不对原页面的加载速度产生影响
22      "run_at": "document_idle",
23      "js": ["contentScript.js"]
24    }
25  ]
26...
27}

请求外网

目前使用内置函数 fetch,具体使用如下

在popup弹出页面,点击按钮,拉取指定url的内容,注意请求的url需要再manifest.json中授权

以下代码来自popup.js

 1document.getElementById('fetchUrl').addEventListener('click', () => {
 2    fetch("http://api.xxx.com/sss/param?key=value&key1=value1",
 3      {
 4        method: 'GET',
 5        headers: {
 6          'Accpet': 'application/json, text/javascript, */*; q=0.01',
 7          'Content-Type': 'application/json;charset=UTF-8'
 8        }
 9      })
10      .then(response => response.json())
11      .then(data => {
12        let value = data.lines;
13        console.log(JSON.stringify(value));
14      });
15  });

参考