创建谷歌插件

8/10/2021 工具

# 入门谷歌插件

一个谷歌扩展其实就是一个配置(入口)文件 manifest.json 和一系列 html、css、js、图片文件的集合

  • manifest.json 说明
{
  // 插件的名称
  "name": "测试插件",
  // 插件的描述
  "description": "april的谷歌插件",
  "version": 0.1,
  "manifest_version": 2,
  // 图标位置
  "icons": {
    "19": "icons/icon19.png",
    "38": "icons/icon38.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  // 设置扩展的图标放在 Chrome 工具栏中
  "browser_action": {
    "default_title": "april",
    // 点击扩展图标所显示的页面位置
    "default_popup": "popup/popup.html"
  },
  // 可以使拓展常驻后台,比较常用的是指定子属性scripts,表示在拓展启动时自动创建一个包含所有指定脚本的页面。
  "background": {
    "scripts": [
      "background.js"
    ]
  },
  // 定义拓展需要向chorme申请的权限
  "permissions": [
    // 可以跨域请求的接口
    "https://www.baidu.com/",
    // 存储
    "storage",
    // 浏览器选项卡
    "tabs",
    // 当前活动选项卡
    "activeTab",
    // 浏览器通知
    "notifications",
    // 右键菜单
    "contextMenus",
  ],
  // 自定义页面替换chrome相应的页面,新标签页(newTab)、书签页面(bookmarks)、历史记录(history)
  "chrome_url_overrides": {
        "newtab": "tab.html"
   },
   // 需要直接注入页面的JS
  "content_scripts": [
    {
      // 匹配的URL,<all_urls>表示匹配所有
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "content-script.js"
      ],
      // 通过插件向页面中注入脚本。content-scripts 和原始页面共享 DOM,但是不共享JS。代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
      "run_at": "document_start"
    }
  ]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  • 调试
├── images/
├── popup.html
├── manifest.json
├── content-script.js

1
2
3
4
5

打开拓展页面(chrome://extensions/),打开开发者模式,点击如图 加载正在开发的扩展程序,选择你的文件夹,就可以看到了。每次更改配置文件之后,都需要点击刷新按钮重新加载。

如图所示

# 遇到的主要问题

  • 消息通信 popup 向 content 互相通信

popup.js

// 向content-script发送信息
function sendMessageToContentScript(message, callback) {
    getCurrentTabId((tab) => {
        // 在参数中添加当前页面的URL
        chrome.tabs.sendMessage(tab.id, { ...message, url: tab.url }, function(response) {
            if (callback) callback(response)
        })
    })
},
1
2
3
4
5
6
7
8
9
// 获取当前选项卡ID
function getCurrentTabId(callback) {
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
        if (callback) callback(tabs.length ? tabs[0] : null)
    })
}
1
2
3
4
5
6
// 测试通讯事件
document.getElementById('getSume').onclick = function() {
    sendMessageToContentScript({ cmd: 'update_font_size', size: 42 }, function(response) {
        // response就是content-script发过来的消息
        alert(response)
    })
}
1
2
3
4
5
6
7

content-script.js

// 接收来自后台的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    console.log(
        '收到来自 ' + (sender.tab ? 'content-script(' + sender.tab.url + ')' : 'popup或者background') + ' 的消息:',
        request
    )
    sendResponse('我收到你的消息了:' + JSON.stringify(request))
})
1
2
3
4
5
6
7
8
  • 带有异步的 chrome.runtime.onMessage 响应

错误的代码,传到 popup.js 中一直获取的是 undefined

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
    let key = await getKey()
    sendResponse(key)
})
1
2
3
4

解决:

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) => {
    (async () => {
        let key = await getKey()
        sendResponse(key)
    })()
    return true
})
1
2
3
4
5
6
7
  • content-script.js 向 popup 传递 Blob 类型数据 问题:在传参为 Blob 类型,在 content-script.js 打印出来没问题,但传给后端就变成了[object object]。如图所示:

    打印的blob

    传递参数

    解决: 谷歌扩展只能传输和 JSON 兼容的数据,可通过 JSON.parse(JSON.stringify(form_data))判断兼容与否。 谷歌文档 Blob 和 JSON 不兼容。要上传文件,可通过发送方将 Blob 对象转换为 dataURL,接收方解析回 Blob 对象,再生成 FormData 对象上传文件

    发送方:

    let file = null
    let reader = new FileReader()
    // 将blob转换为dataURL
    reader.readAsDataURL(data)
    reader.onload = function(e) {
        file = e.target.result
        sendResponse({
            file,
        })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    接收方:

    this.sendMessageToContentScript(item, async function(response) {
        // dataURL转换为Blob
        const dataURLtoBlob = (dataUrl) => {
            let arr = dataUrl.split(','),
                mime = arr[0].match(/:(.*?);/)[1],
                bstr = atob(arr[1]),
                n = bstr.length,
                u8arr = new Uint8Array(n)
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n)
            }
            return new Blob([u8arr], {
                type: mime,
            })
        }
        const { file } = response
    
        let formData = new FormData()
        formData.append('file', dataURLtoBlob(file))
        let { data } = await axios.post('XXX', formData, {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
            },
        })
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

--- 接到需求的时候,觉得自己搞不定,了解一番之后也没有想象的那么难~勇敢牛牛怎么会害怕困难呢~完

勇敢牛牛

# 参考文章:

上次更新: 8/13/2021, 5:12:51 PM