Skip to content

Commit e504ee7

Browse files
Merge pull request #28 from programmer-zhang/dev-2.11
Dev 2.11
2 parents cc97e78 + 21e15d2 commit e504ee7

File tree

8 files changed

+276
-1
lines changed

8 files changed

+276
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
183183
> [用 electron 实现一个桌面客户端]
184184
185-
> [谷歌浏览器插件制作指南]
185+
> [谷歌浏览器扩展程序 V3 制作指南](https://github.com/programmer-zhang/front-end/tree/master/profiles/chrome-extension.md)
186186
187187
> [前端使用 node 实现 读写 Excel文件](https://github.com/programmer-zhang/front-end/tree/master/profiles/node-xlsx.md)
188188

images/chromeExtension/catalog.png

15.4 KB
Loading
59.4 KB
Loading
79.5 KB
Loading
92.2 KB
Loading
110 KB
Loading
216 KB
Loading

profiles/chrome-extension.md

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# 谷歌浏览器扩展程序 V3 制作指南
2+
3+
> 出于对隐私保护、安全性、性能的加强,谷歌随着 `Chrome 88` 的升级,将推行 `Manifest V3` 扩展程序规范。
4+
5+
> 本文也将基于 `Manifest V3` 规范进行扩展程序制作。
6+
7+
## 写在前面
8+
* 本次大版本提升从 `Manifest V2` 升级到 `Manifest V3`,带来了很多新特性和功能变化。以下是按照我认为的重要性变化点:
9+
* 使用 `service-worker.js` 替代 `background.js`
10+
* 不再允许远程托管代码,所有 `JavaScript` 必须包含在包中
11+
* `Promise` 支持,但仍然支持回调函数
12+
13+
## 目录结构
14+
* 以下为 `Chrome` 官方文档给出的参考目录结构
15+
* 可以通过多种方式构建项目目录,唯一的前提条件是必须将 `manifest.json` 文件放在根目录中。
16+
17+
```
18+
|-README.md
19+
|-manifest.json // 配置文件,必不可少
20+
|-popup.html // 入口文件
21+
|-popup.css // 样式文件
22+
|-popup.js // 用户界面用到的JS文件,运行时加载
23+
|-service-worker.js // 后台运行JS文件,也就是V2版本的 background.js 文件
24+
|-logo.png // 扩展程序LOGO文件
25+
```
26+
27+
## Manifest 配置
28+
* `manifest.json` 文件的配置详情如下:
29+
30+
```
31+
{
32+
"name": "rep-tool",
33+
"version": "0.0.1",
34+
"manifest_version": 3,
35+
// 简单描述
36+
"description": "a chrome browser extension tool",
37+
// 插件图标
38+
"icons": {
39+
"16": "./logo.jpeg",
40+
"48": "./logo.jpeg",
41+
"128": "./logo.jpeg"
42+
},
43+
// 选择默认语言
44+
"default_locale": "en",
45+
46+
// 浏览器图标部分
47+
"action": {
48+
"default_title": "小助手",
49+
"default_icon": "./logo.jpeg",
50+
"default_popup": "./popup.html"
51+
},
52+
53+
// 后台常驻文件
54+
"background": {
55+
"service_worker": "service-worker.js"
56+
},
57+
58+
// 引入一个脚本
59+
"content_scripts": [
60+
{
61+
"matches": ["<all_urls>"],
62+
"js": ["qrcode.min.js", "jquery.min.js"]
63+
}
64+
],
65+
// 跨域请求
66+
"host_permissions": [
67+
"*://api.xxxxxx.com/*"
68+
],
69+
// 插件中用到的静态资源需要在这里配置
70+
"web_accessible_resources": [
71+
{
72+
"matches": ["<all_urls>"],
73+
"resources": ["qrcode.min.js", "jquery.min.js"]
74+
}
75+
],
76+
// 应用权限配置: cookie 权限、tabs 权限、系统通知权限等
77+
"permissions": [
78+
"contextMenus",
79+
"tabs",
80+
"storage",
81+
"cookies",
82+
"notifications",
83+
"alarms"
84+
]
85+
}
86+
```
87+
88+
### 权限类型
89+
90+
> 在 V3 版本当中可以声明以下类别的权限
91+
92+
* `permissions`: 包含写明的权限列表中的项
93+
* `optional_permissions`: 用户运行时授予的权限
94+
* `content_scripts.matches`: 包含一个或多个匹配模式,可允许内容脚本注入一个或多个主机中
95+
* `host_permissions`: 包含一个或多个匹配模式,可提供对一个或多个主机的访问权限,多用于 `fetch()` 跨域请求
96+
* `optional_host_permissions`: 由用户在运行时授予
97+
98+
## 主体页面
99+
* 编写 `popup.html`
100+
101+
```
102+
<!DOCTYPE html>
103+
<html lang="en">
104+
<head>
105+
<meta charset="UTF-8">
106+
<title>暴力小助手</title>
107+
</head>
108+
<body>
109+
<div class="container">
110+
<div class="tab-content">
111+
<ul id="tab">
112+
<li class="tab-item active" data-area="develop">页面二维码</li>
113+
<li class="tab-item" data-area="ipip">IP解析</li>
114+
</ul>
115+
</div>
116+
</div>
117+
</body>
118+
</html>
119+
```
120+
121+
## 样式调整
122+
* 使用样式调整页面主体
123+
124+
```
125+
<head>
126+
<meta charset="UTF-8">
127+
<title>暴力小助手</title>
128+
<link href="./popup.css" rel="stylesheet"> // 引入样式文件
129+
</head>
130+
```
131+
132+
## 逻辑处理
133+
* 加入 `JavaScript`
134+
135+
```
136+
<!DOCTYPE html>
137+
<html lang="en">
138+
<head>
139+
<meta charset="UTF-8">
140+
<title>暴力小助手</title>
141+
<link href="./popup.css" rel="stylesheet"> // 引入样式文件
142+
</head>
143+
<body>
144+
<div class="container">
145+
...
146+
</div>
147+
</body>
148+
<script src="popup.js"></script> // 引入样式文件
149+
// 当然也可以引入其他JS文件
150+
<script src="jquery.min.js"></script>
151+
<script src="qrcode.min.js"></script>
152+
</html>
153+
154+
```
155+
156+
## 处理请求
157+
* 使用 `fetch``service-worker` 中发送请求
158+
159+
```
160+
fetch(request.url + request.body).then(response => {
161+
return response.json();
162+
}).then(jsonData => {
163+
console.log('service-worker.js-fetch', jsonData);
164+
sendResponse({success: true, jsonData});
165+
}).catch(err => {
166+
console.error('service-worker.js-fetch', err);
167+
sendResponse({success: false, error: 'Failed to send the request'});
168+
});
169+
```
170+
171+
## 运行插件
172+
* 管理扩展程序
173+
174+
![](../images/chromeExtension/extensions-page.png)
175+
176+
* 打开开发者模式
177+
178+
![](../images/chromeExtension/open-decode.png)
179+
180+
* 添加已解压的扩展程序
181+
182+
![](../images/chromeExtension/export-extension-doc.png)
183+
184+
* 导入文件夹
185+
186+
> 导入的是代码文件夹,不是压缩包
187+
188+
![](../images/chromeExtension/open-sources.png)
189+
190+
* 添加成功
191+
192+
![](../images/chromeExtension/export-success.png)
193+
194+
## 错误排查
195+
### 错误信息:`Unchecked runtime.lastError: The message port closed before a response was received.`
196+
> 造成此错误的原因归根到底是 `onMessage` 监听器消息端口自动关闭
197+
198+
* **可能错误原因一:** `chrome.runtime.onMessage` 监听器在异步操作完成之前关闭了消息端口,导致无法发送响应。
199+
200+
* **解决方案一:** 保持消息端口打开,使用 `return true` 提前返回响应 或 二次监听 的方法来保持消息端口开放。
201+
202+
```
203+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
204+
if (request.action === 'sendRequest') {
205+
sendChromeRequest(request, sendResponse);
206+
}
207+
return true; // 防止消息端口关闭
208+
});
209+
210+
async function sendChromeRequest(request, sendResponse) {
211+
fetch(request.url + request.body).then(response => {
212+
return response.json();
213+
}).then(jsonData => {
214+
sendResponse({ success: true, jsonData });
215+
}).catch(err => {
216+
sendResponse({ success: false, error: 'Failed to send the request' });
217+
})
218+
}
219+
```
220+
221+
* 早期已经有国外的开发者发现此问题并提出了解决方案,传送门 [https://github.com/mozilla/webextension-polyfill/issues/130](https://github.com/mozilla/webextension-polyfill/issues/130)
222+
223+
* **解决方案二:** 双监听器策略。
224+
225+
```
226+
// 第一个监听器,返回 true 以保持消息端口开启
227+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
228+
return true;
229+
});
230+
231+
// 第二个监听器,处理实际的接口响应和其他异步操作
232+
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
233+
if (request.action === 'sendRequest') {
234+
sendChromeRequest(request, sendResponse);
235+
}
236+
});
237+
```
238+
239+
* 上述解决方案参考[这里](https://gitcode.csdn.net/65e95d911a836825ed790a29.html)
240+
241+
* **可能错误原因二:** `chrome.runtime.sendMessage` 未正确使用回调函数 `sendResponse`
242+
243+
* **解决方案:**增加 `sendResponse` 回调
244+
245+
```
246+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
247+
sendResponse({ success: true }); // 必须处理 sendResponse 回调函数
248+
});
249+
```
250+
251+
* **可能错误原因三:** 跨域权限
252+
253+
* **解决方案:** `host_permissions` 权限配置
254+
255+
*`manifest.json` 文件中对需要跨域请求的域名进行配置
256+
257+
```
258+
"host_permissions": [
259+
"*://api.xxxxxx.com/*"
260+
],
261+
```
262+
263+
### 使用 `fetch` 发送数据后接收不到 `response`
264+
265+
* **原因一:** `response` 数据需要`JSON` 格式化数据后 `return`
266+
267+
```
268+
fetch(request.url + request.body).then(response => {
269+
return response.json(); // 必不可少
270+
}).then(json => {
271+
sendResponse({ success: true, json }); // 真实处理数据
272+
}).catch(err => {
273+
sendResponse({ success: false, error: 'Failed to send the request' });
274+
})
275+
```

0 commit comments

Comments
 (0)