当前位置: 首页 > news >正文

【Chrome】开发一个Chrome扩展以及常见问题的解决方案

前言

本文介绍开发chrome扩展很重要的几种操作,如:操作网页dom、发送请求、渲染弹层、不同沙盒环境的通信方式、扩展与网页的通信方式、遇到iframe时的操作等。最终会提供一个简单的案例,其中涵盖了上述操作。

还有一些本人相关文章,有兴趣也可以看一下,如:判断是否安装了某个Chrome插件、background.js中log打印未出现在控制台、Edge扩展程序上架流程、Chrome扩展程序上架流程。

本文都是在该页面下测试扩展

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script>localStorage.setItem("tag", "123456") // 标识是否需要执行插件</script>
</head><body><input id="test-chrome-extensions" type="text">
</body></html>
  • 需要一个标识用于确认是否需要在该页面下运行扩展,本文以本地存储举例,也可能是下面几种方式,需要根据实际场景判断使用。
    • 根据域名判断,可以通过扩展的 manifest.json 中 content_scripts 的 matches 字段来定义允许执行的页面(下文会提及)。
    • 根据url路径参数
    • 根据页面中的特殊标签
    • 根据本地存储
  • 需要一个input输入框用于测试扩展对网页dom元素的直接操作

一、目录结构

开发一个Chrome扩展,项目目录大致如下,下文统一以标准命名介绍。

在这里插入图片描述

  • background.js:后台脚本
  • content.js:内容脚本
  • manifest.json:插件配置文件(清单列表)
  • popup.html 和 popup.js:插件弹窗页面及其脚本

二、配置清单(manifest.json)

  • 该文件必须位于项目的根目录下。
  • 必需的键只有 “manifest_version”、“name” 和 “version”。
  • 开发过程中支持注释 (//),但在将代码上传到 Chrome 应用商店之前,必须先移除这些注释。

基本配置如下

{"manifest_version": 3,"name": "My Chrome Extension","version": "1.0","permissions": ["storage", "activeTab", "scripting"],"background": {"service_worker": "background.js"},"action": {"default_popup": "popup.html"},"content_scripts": [{"matches": ["<all_urls>"],"js": ["content.js"]}]
}

Chrome官方文档中明确说明,v2已经弃用了。

在这里插入图片描述

在这里插入图片描述

三、沙盒环境

Chrome 插件的每个模块(background.js、popup.js、content.js)都运行在自己的沙盒环境中,意味着它们的 JavaScript 变量和函数是相互隔离的,避免了相互干扰。唯一的通信方式是通过 Chrome 提供的 API(如 chrome.runtime.sendMessagechrome.storage)。

  • background.js:运行在插件的后台,它运行在独立的沙盒环境中,无法与网页内容直接交互
  • popup.html:弹窗页面,用户点击浏览器工具栏的插件图标时弹出。
  • popup.js:只能在弹窗页面存在时运行,弹窗页对应的脚本。
  • content.js:内容脚本,运行在网页的环境中可以直接访问网页的 DOM

浏览器运行环境:与普通的网页脚本不同,Chrome 插件模块无法直接访问网页的全局变量和 DOM,必须通过 content.js 作为桥梁进行交互。同时,插件可以使用 chrome 提供的扩展 API,而网页中的 JavaScript 代码则没有这些权限。

四、通信方式

1. chrome.runtime API

由于处于不同沙盒环境,只能通过chrome.runtime APIchrome.storage通信

chrome.runtime.sendMessage发送消息,接收两个参数

  • message: 要发送的消息内容,可以是一个对象。
  • callback (可选): 当消息成功发送或遇到错误时调用的回调函数。
chrome.runtime.sendMessage({ type: "test", message: "Hello World!" }, (response) => {console.log("回应:", response);
});

chrome.runtime.onMessage.addListener监听消息,参数为回调函数,其参数:

  • message:chrome.runtime.sendMessage发送的数据(第一个参数)。
  • sender:发送消息的上下文,包含有关消息来源的信息,如 tab、url、扩展id。
  • sendResponse:返回消息(chrome.runtime.sendMessage中callback可以接收)
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {console.log("接收到的消息:", message);sendResponse({ type: "responseData", data: "返回数据" })
});

2. chrome.storage

不同于 localStoragechrome.storage是异步的,所有存储操作都会接收一个回调函数,以处理操作完成后的逻辑。

chrome.storage 主要有两个存储区域:

  • chrome.storage.local:在本地存储数据,不同步到其他设备,存储容量最大为 5MB。
  • chrome.storage.sync:同步数据到 Chrome 登录账户的所有设备,存储容量为 100KB,适合存储轻量配置或设置。

基本用法

存储数据

// 存储数据
chrome.storage.local.set({ key: "value" }, function() {console.log('数据已存储');
});// 或者使用多个键值对
chrome.storage.local.set({ key1: "value1", key2: "value2" }, function() {console.log('多组数据已存储');
});

获取数据

// 获取存储的数据
chrome.storage.local.get(['key'], function(result) {console.log('获取到的值:', result.key);
});// 获取多个键值对
chrome.storage.local.get(['key1', 'key2'], function(result) {console.log('key1 的值:', result.key1);console.log('key2 的值:', result.key2);
});

删除数据

// 删除指定键的数据
chrome.storage.local.remove('key', function() {console.log('数据已删除');
});// 删除多个键的数据
chrome.storage.local.remove(['key1', 'key2'], function() {console.log('多个数据已删除');
});

清除所有数据

// 清除存储中的所有数据
chrome.storage.local.clear(function() {console.log('所有数据已清除');
});

测试

进入扩展程序管理页,或者直接访问chrome://extensions/

在这里插入图片描述

确保开启开发者模式

在这里插入图片描述

直接将文件夹拖入即可

注意:background.js不同于content.js,他并不与页面共享相同的 JavaScript 环境,所以需要单独打开一个控制台。

点击检查视图,会弹出background.js对应的控制台

在这里插入图片描述

v2版本的扩展有所不同,名为background.html,新项目不建议使用v2。

在这里插入图片描述

五、案例演示

案例中,content使用chrome.runtime API通信,popup使用chrome.storage获取数据,实际场景中可以使用任意通信方式,没有限制,流程如下:

  1. content 判断该页面是否需要扩展运行
  2. 如果需要则通知 background 发送请求
  3. background 将返回数据通过 chrome.runtime API 交予 content,然后content将该值赋给页面中input元素
  4. background 将返回数据保存到 chrome.storage 供 popup 渲染

在这里插入图片描述

content.js

console.log("content.js - 开始运行...")const tag = localStorage.getItem("tag")
console.log("标识:", tag)if (tag) {console.log("该页面需要使用插件,准备接收消息...")// 通知 background.js 可以继续执行接口调用chrome.runtime.sendMessage({ type: "pageCheck", valid: true, tag },(response) => {console.log("收到消息:", response)if (response.type === "responseData") {const inputElement = document.getElementById("test-chrome-extensions")if (inputElement) {inputElement.value = response.data.name}}})
} else {console.log("该页面不需要使用插件,不执行扩展逻辑。")// 通知 background.js 不用调用接口chrome.runtime.sendMessage({ type: "pageCheck", valid: false, tag })
}

background.js

console.log("background.js - 开始运行...")// 模拟请求并返回数据
function fetchData(tag) {console.log("标识为", tag) // 可能需要该标识作为请求参数const responseData = { name: "田本初", age: 23 }return new Promise((resolve) => {setTimeout(() => {resolve(responseData)}, 1000) // 模拟接口异步返回})
}// 监听 content.js 发来的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {if (message.type === "pageCheck") {if (message.valid) {console.log("页面需要接口调用...")fetchData(message.tag).then((response) => {chrome.storage.local.set({ data: response }) // 存储数据sendResponse({ type: "responseData", data: response }) // 发送消息console.log("接口调用成功,向content发送消息,并将data存储到storage供popup使用",response)})// 返回 true 表示异步使用 sendResponse,否则content.js 不会等待异步返回结果return true} else {console.log("页面不需要接口调用...")return}}
})

popup.html

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>Popup</title><script src="popup.js"></script><style>.container {width: 600px;height: 600px;background-color: #efefef;}</style>
</head><body><div class="container"><div id="show_name"></div><div id="show_age"></div></div>
</body></html>

popup.js

console.log("popup.js - 开始运行...")// 获取数据
chrome.storage.local.get(["data"], (result) => {if (chrome.runtime.lastError) {console.error("获取数据时发生错误:", chrome.runtime.lastError)return}console.log("获取到的值:", result)// 处理数据const data = result.data// 更新 popup.html 中的内容const nameDiv = document.getElementById("show_name")const ageDiv = document.getElementById("show_age")if (nameDiv && ageDiv && data) {nameDiv.textContent = `姓名:${data.name}`ageDiv.textContent = `年龄:${data.age}`}
})

六、分析manifest.json

上文提过一次 manifest.json,通过案例后详细分析一下清单:

{"manifest_version": 3,"name": "My Chrome Extension","version": "1.0","permissions": ["storage", "activeTab", "scripting"],"background": {"service_worker": "background.js"},"action": {"default_popup": "popup.html"},"content_scripts": [{"matches": ["<all_urls>"],"js": ["content.js"]}]
}
  • manifest_version:Manifest 文件的版本,官方已经不再支持发布v2版,目前都是3。
  • name:插件名
  • version:版本号,后续更新时必须修改在这里插入图片描述
  • permissions:权限,本文只用到了storage
    • activeTab:允许扩展在用户点击扩展图标时访问当前活动标签页的内容
    • scripting:允许扩展使用 chrome.scripting API 注入脚本到网页中
  • background:在后台运行,负责处理扩展的主要逻辑和事件
  • action:用户点击扩展图标时,会显示指定的弹出页面
  • content_scripts:定义要注入的内容脚本及其匹配规则
    • matches:定义哪些网页 URL 匹配规则,以决定哪些页面将注入指定的内容脚本。

      "http://\*/\*":匹配所有 HTTP 协议的网页。
      "https://\*/\*":匹配所有 HTTPS 协议的网页。
      "*://example.com/*":匹配 example.com 域下的所有网页。
      "*://*.example.com/*":匹配 example.com 域下的所有子域网页。
      

修改matches为"matches": ["https://www.baidu.com/"], 可以发现只有在该域名下才执行了 content.js

在这里插入图片描述

配置扩展图标,其中action下的default_icon对应工具栏中的图标,icons对应管理扩展页面中的图标。

"action": {"default_popup": "popup.html","default_icon": {"16": "icons/icon16.png","48": "icons/icon48.png","128": "icons/icon128.png"}
},
"icons": {"16": "icons/icon16.png","48": "icons/icon48.png","128": "icons/icon128.png"
},

在这里插入图片描述

在这里插入图片描述

更多详细使用请参考:官方文档


http://www.mrgr.cn/news/27870.html

相关文章:

  • Django 的 ModelViewSet 中的 get_queryset 方法自定义查询集
  • The Planets: Earth -- 练习
  • CC3学习记录
  • shell编程之编程基础
  • iOS swift开发--- 加载PDF文件并显示内容
  • 自顶向下逐步求精解决LeetCode第3307题找出第K个字符II题
  • Linux df命令详解使用
  • 【自动驾驶】控制算法(八)横向控制Ⅳ | 调试与优化
  • 【目标检测数据集】锯子数据集1107张VOC+YOLO格式
  • C语言 | Leetcode C语言接雨水II
  • 自由流转--实例(二)
  • 高级java每日一道面试题-2024年9月12日-安全篇[加密篇]-有哪些加密算法, 加密算法都有哪些分类?
  • Kubernetes Pod的3种重启策略
  • java中init()函数(JAVA基础)
  • NISP 一级 | 5.3 电子邮件安全
  • 【人工智能】AI创业的前沿思考 | 从垂直领域到通用智能模型AGI的崛起
  • uniapp js修改数组某个下标以外的所有值
  • 2020-11-04 求最小与均值输入0结束
  • 代码随想录算法训练营第四十四天| LeetCode322. 零钱兑换、LeetCode279.完全平方数、LeetCode139.单词拆分
  • python画图|同时输出二维和三维图
  • C++——哈希unordered_set/unordered_map的封装
  • 火语言RPA流程组件介绍--下拉框选择
  • 你可能遗漏的一些C#/.NET/.NET Core知识点
  • 高效网络爬虫设计:多线程抓取网页内容
  • AI学习指南深度学习篇-RMSprop算法流程
  • [产品管理-21]:NPDP新产品开发 - 19 - 产品设计与开发工具 - 详细设计与规格定义