其实这个小工具是我很早以前开发的,最近因为有需求,又翻出来修了修,觉得蛮有用的,分享给大家。Automate GPT 是一个浏览器扩展,可以方便我们更好的使用 ChatGPT。
OpenAI 最近几次发布的新功能还是很强力的,比如近期大热的各种风格化绘图,什么吉卜力啊、卡通3D啊、玩具啊,等等。相信大家都在各种平台上看过,我也试做了一些:




其实这个小工具是我很早以前开发的,最近因为有需求,又翻出来修了修,觉得蛮有用的,分享给大家。Automate GPT 是一个浏览器扩展,可以方便我们更好的使用 ChatGPT。
OpenAI 最近几次发布的新功能还是很强力的,比如近期大热的各种风格化绘图,什么吉卜力啊、卡通3D啊、玩具啊,等等。相信大家都在各种平台上看过,我也试做了一些:




本文接续前一篇 【教程】浏览器扩展中实现一键登录 Google(1),重点介绍代码相关的部分。
为防止有些同学不了解 SSO,这里简单介绍一下 SSO 的基本原理:
使用 SSO 的好处是:
chrome.identity API 整合登录功能Chrome Extension API 提供两个登录方式:
getAuthToken() 获得基于 Google 账号的 auth token,可以用来访问特定的 Google 服务,比如 Google Drive,在第三方浏览器比如 Edge 里不可用。launchWebAuthFlow() 使用上述 SSO 流程,在任何支持 SSO 的网站里完成登录。因为我并不打算和 Chrome 深度绑定,所以自然,我们要用第二种。
由于我们要在浏览器扩展中 SSO,所以无法接受登录之后的重定向,所以上述步骤的(3)会变成在扩展里拿到 access_token,然后去 Supabase 换取用户身份信息,并存储在本地。
首先,我们要修改 manifest.json,将需要的 oauth2 信息添加进去。因为我用 CRXJS Vite Plugin,所以这里直接用 TS 来写就好:
{
permissions: [
'identity',
],
oauth2: {
'client_id': 'XXXX-XXXXXX.apps.googleusercontent.com',
// 我们只需要 openid 和 email 就够用了
'scopes': [
'openid',
'email',
],
},
}
接下来,完成登录函数,代码写出来大约是这样:
async function doLogin(): Promise<void> {
// 获取 manifest.json 内容,方便重用代码
const manifest = chrome.runtime.getManifest();
let url = new URL('<https://accounts.google.com/o/oauth2/auth>');
if (!manifest?.oauth2?.scopes) return;
url.searchParams.set('client_id', manifest.oauth2.client_id);
url.searchParams.set('response_type', 'id_token');
url.searchParams.set('access_type', 'offline');
url.searchParams.set('redirect_uri', `https://${chrome.runtime.id}.chromiumapp.org`);
url.searchParams.set('scope', manifest.oauth2.scopes.join(' '));
let redirectedTo;
try {
// 开启 SSO 弹窗,获取重定向地址
redirectedTo = await chrome.identity.launchWebAuthFlow({
url: url.href,
interactive: true,
});
} catch (e) {
return;
}
url = new URL(redirectedTo);
const params = new URLSearchParams(url.hash.replace(/^#/, ''));
// 用 token 去 supabase 进行验证
const { data, error } = await supabase.auth.signInWithIdToken({
provider: 'google',
token: params.get('id_token') as string,
});
if (error) {
return;
}
userStore.setUser(data.user);
}
登录一定需要后端验证,但是在 Supabase SDK 的帮助下,我们可以完全跳过这部分开发。Supabase SDK 甚至贴心的提供了 signInWithIdToken() ,所以我强烈推荐各位独立开发者使用 Supabase 作为用户体系的基础。
这里有几个坑必须小心。
getAuthToken()getRedirectURL() ,它会在末尾多带一个 / ,导致登录失败。我也不知道为啥会有这么蠢的问题,但是调试几个小时之后,我不得不认为的确是这个问题……总有一些用户没有 Google 账号,所以一般来说我们会给他们提供常规的邮箱+密码登录的方式作为兜底。
因为使用 Supabase,所以这里的功能实现也比较简单。Supabase 默认开启 Email provider,直接在代码增加一个登录函数即可。因为我们暂时不验证用户邮箱,所以在一个函数里帮用户完成注册/登录。表单我就不贴了,两个文本框一个按钮,很简单。
async function doLoginViaForm(): Promise<void> {
try {
const user = await userStore.register(email.value, password.value);
userStore.setUser(user);
} catch (e) {
const msg = (e as Error).message || Object.toString.call(e);
// if the user is already registered, we will try login
if (msg === 'User already registered') {
try {
const user = await userStore.login(email.value, password.value);
userStore.setUser(user);
} catch (e) {
message.value = (e as Error).message || Object.toString.call(e);
}
} else {
message.value = msg;
}
}
}
至此,在浏览器扩展里集成 Google SSO,实现一键登录的功能就完成了。这个过程本身不复杂,但是有几个坑,还有不少易混淆的地方,我也花了不少时间在上面。希望我的这篇分享对大家有帮助。如果大家对浏览器扩展开发、Serverless 数据库使用、Vue 开发有什么问题,欢迎留言评论讨论。
感谢几位赞助商支持我创作技术分享,也请大家多多使用我的分享链接注册使用他们的服务。在顶部导航的“各种代理”里就能找到链接。谢谢大家。

本文分享我最近开发 AutomateGPT 扩展时集成 Google SSO 的经验。除了 Google 外,我还用到 Supabase 提供的用户管理与登录功能。
本来想一篇博客搞定,没想到写着写着就超长了,那就拆成两篇吧,哈哈。
开发浏览器扩展的时候,我们有时候需要建立用户体系,以便更好的服务用户。此时我们有多个选择:
很明显,第二种更好,因为我们不需要建立用户注册、校验等一系列功能,只要用第三方提供的用户身份标记就好。这样一来可以减少我们的开发成本,二来可以利用现有的互联网基建,对用户来说也更省事。
作为面向 ChatGPT 用户的辅助应用,AutomateGPT 选择用户体系时,我很自然的选择了 Google。
Google 是个让人又爱又恨的公司,一方面他们提供大量免费的互联网基建,给我们开发带来巨大帮助;另一方面,由于摊子铺的太大,各种年久失修或者考虑不周,导致我们经常要自己踩坑自己摸索才能最终完成想要的作品。
这次也是。Google 提供的浏览器扩展开发文档包含一些错误和遗漏;Google Cloud 又是完全的黑盒,导致我折腾了将近两天才把这项功能做好。
行吧,闲言少叙,下面开始正文。
因为 Google SSO 要跟 Extension ID 绑定在一起,所以我们要先去 Chrome Web Store 里占个坑,保证以后扩展无论安装在哪里,都是统一的 ID。
我先假定大家都有一些扩展开发经验,可以自行创建浏览器扩展。如果你需要从零学起,我刚好做过一期相关视频,可以方便你快速上手:
肉山小教程-浏览器扩展开发-快速入门_哔哩哔哩_bilibili
创建完毕,在本地调试没问题之后,就打包,上传 Chrome Web Store。此时我们不需要添加太多功能,只要能加载,能看到效果就行,并不是最终发布,所以不用太担心。
假定各位读者已经拥有 Google 账号,那么就请进入开发者信息中心(Developer Dashboard)。
接着点击“上传新内容“,上传刚才准备好的压缩包,稍等片刻,CWS 会帮我们创建一个应用,此时就能看到应用 ID 了,大约如图所示:

具体的 ID 不重要,点击图中红框的 View public key,将 key 复制下来,粘贴到插件的 manifest.json 里,记得要把换行删掉:
{
"key": "刚才复制的 key,不能有换行符"
}
但是 manifest.json 加了 key 之后,为生产环境构建发版时可能会出问题(因为本地没有私钥,无法信任公钥)。此时,如果你用了我以前推荐的 CRXJS Vite Plugin,就很简单,判断一下当前环境即可:
export default defineManifest(async function (env) {
return {
...process.env.NODE_ENV === 'development' && {
key: '刚才复制的 key,不能有换行符',
},
};
});
所以,推荐使用 Vite + CRXJS Vite Plugin 构建浏览器扩展开发环境。如果你还不会使用这个插件,建议看我另一个视频:
2024 浏览器扩展开发必备:CRXJS Vite Plugin_哔哩哔哩_bilibili
为了验证效果,可以在浏览器里删除之前导入的测试扩展,然后重新加载改好 key 的版本。如果之后看到的 ID 与你在 CWS 开发者中心看到的一致,那就说明操作成功。
接下来,我们需要在 Google Cloud 里完成配置。如果你已经有项目,那么沿用之前的项目也没问题。如果没有的话,那就新建一个。目前 Google Cloud 还是免费的,新用户更是有 $300 的试用额度,可以放心体验。
同样,我假定各位读者已经拥有 Google 账号,那么打开 Google Cloud Console,并按照向导指引创建即可。
创建完成,点击左上角的菜单按钮,从里面选择“API和服务”,然后,选择“OAuth 同意屏幕”,跟随引导创建 OAuth 同意屏幕。注意,因为新应用默认采用白名单,为了之后普通用户能够正常注册登录,最好把应用设置为“已发布”。

然后,进入“凭据”页面,添加登录凭证。点上面的“创建凭据“,然后选”OAuth 客户端 ID”。注意,虽然 Google 给我们准备了”Chrome 扩展程序“这个选项,但是不能选,要选择”Web 应用“。

然后在”已获授权的重定向 URI”里填入 https://{刚才获得的 Extension ID}.chromiumapp.org,创建凭证。然后你会得到一个形如 xxxx-ooooo.apps.googleusercontent.com 的客户端 ID。
用户登录免不了用到一些通用功能,比如用户管理、用户信息存储等等。开发这些功能免不了需要很多工作量,我觉得自己做并不合算。所以我建议大家选用一些第三方服务。
我目前比较喜欢使用 Supabase,大概有几点优势:
简而言之,下限有保证,上限有空间,非常推荐。使用 Supabase 集成 Google SSO 非常简单。另外,因为 Supabase 的界面比较友好,不像 Google Cloud 那么反人类,我就不截图了,相信大家很容易找到。
本文主要介绍了开发环境的配置。下篇博客会讲解如何写代码,并解释整个流程的原理,方便大家适配其它 SSO provider。
如果大家对浏览器扩展开发有什么问题和想法,欢迎留言提问、讨论。
AutomateGPT 是我们最近开发的 ChatGPT 增强扩展,方便大家更好的使用 ChatGPT,充分利用包月的价值。它能够帮你重复执行多个 prompt;也可以分解大文件,拆成块依次处理(开发中)。可以用在大文本翻译、批量生成内容、网站分析等领域。欢迎大家使用,反馈问题和意见,谢谢。

早在开坑 使用 CRXJS Vite 插件开发 ChatGPT SidePanel 插件(一) 系列时我就想做这个视频了。时隔数日,这个插件修复了不少问题,热重载更好用了,也适配了 Vite5,更加值得推荐。
CRXJS Vite plugin 可以大大改进我们开发浏览器扩展时的体验。本视频就简单介绍一下这个插件,带大家初步体会它的能力。另外浏览器扩展近期还开放了 Side Panel 的能力,非常适合做与页面相关的功能。本视频里也一并推荐。
希望大家喜欢,请留下您的意见想法与一键三连,我们过两天再见。
视频大纲:
有任何问题、意见、建议,欢迎留言弹幕私信与我交流。目前我正在筹划新的系列教程,关于方向的选择,想听听大家的意见,请大家移步 B 站帮我投票选择:https://t.bilibili.com/877511288038621239,谢谢。
无论选择哪个方向,我都会遵循 #build-in-public 的方式,欢迎关注我的博客。

OpenAI DevDay 上发布了一大堆新特性新功能,提升上下文容量、降低 token 价格,再次震撼整个行业。相信大家已经通过各种渠道了解到这次更新的细节,所以我就不再赘述。这里简单分享三个观点:
Chrome 浏览器从 v114 之后,开始支持 SidePanel,从此我们可以把扩展放在浏览器侧边栏里,提供新的可能性。

之前我们使用扩展时,有三种方案,它们都有一些影响使用的问题:
这些问题都可以被 SidePanel 很好地解决。于是,我们可以利用 SidePanel API 开发一个浏览器扩展,它可以大幅加强某个网站的功能、提升在这个网站里执行自动化操作的能力。我们不用担心它会被以外关闭,导致自动化失效;也不用担心它会和目标网页产生冲突。
假如,我们针对 ChatGPT 网站开发一个扩展,加强它的功能,把 ChatGPT Plus 的功能和额度用好用满,应该可以实现一些相当不错的功能。
产生上述想法之后,我就一直想找机会试试。不过开发浏览器扩展还有个痛点:扩展拥有加强版 API,在普通页面里无法使用;但是如果使用开发者模式加载扩展,又会丧失 HMR,开发不便。
经过调研,发现 CRXJS Vite 插件 可以解决这个问题。它可以给插件开发环境添加 自动更新的功能,我们就不需要每次更改代码之后再手动刷新,也可以确保我们的开发环境支持全套 chrome.* API,与实际运行环境一致,大大提升我们的开发效率。
使用该插件的方式非常简单。首先,创建一个 vite 项目。对我来说,效率最高的框架还是 Vue3。本着每次尝试的新技术不要超过 1/4 的比例,那就 vue-ts 吧:
pnpm create vite my-vue-app --template vue-ts
接下来,安装并配置 crxjs vite 插件:
pnpm i @crxjs/vite-plugin@beta -D
然后配置 vite.config.ts:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.config';
export default defineConfig({
plugins: [
vue(),
crx({ manifest }),
],
// 注意,这段配置很关键,请保证开发端口与 hmr 端口一致。不知道为何,插件生成的扩展里缺少 5173 默认值。
server: {
strictPort: true,
port: 5173,
hmr: {
clientPort: 5173
},
},
})
我的 manifest.json 也是使用 TypeScript 生成的,所以上面我 import 本地的 manifest.config.ts 文件。
export default defineManifest(async function (env) {
return {
"manifest_version": 3,
"name": "my ChatGPT tools",
permissions: [
'activeTab', // 要往目标页注入脚本
'scripting', // 同上
'sidePanel', // 启用 sidePanel
'tabs', // 为了与 content script 通信
],
content_scripts: [
{
matches: ['https://chat.openai.com/*'],
// crxjs 会帮我们把目标文件编译后注入目标页面
js: ['./content/src/index.ts'],
},
],
// 针对 ChatGPT 而做
host_permissions: [
'https://chat.openai.com/*',
],
// 启动 sidePanel 时,加载当前项目的页面
side_panel: {
default_path: 'index.html',
},
// 这里主要为了点击图标能打开或关闭 sidePanel,background script 同样交给 crxjs 处理
background: {
'service_worker': 'src/sw.ts',
'type': 'module'
},
};
});
配置完成之后,照常启动项目 pnpm run dev。
然后在浏览器的扩展管理器里启动开发者模式,加载已解压的扩展目录即可。
启动开发环境之后,CRXJS 会帮我们生成一个开发版的浏览器扩展,里面除了必备文件之外,还有各大组件所需的加载器,帮我们分别加载 service worker、content script,和页面内 js。它还会建立一个 WebSocket 连接到 vite 开发服务器,当侦听到目标文件出现变化时,就通过各种方式重新加载。比如,页面文件可以直接 HMR,service worker 可能要刷新组件,而 content script 甚至要刷新目标页。
于是,便实现了浏览器扩展在开发环境下的 HMR。
首先,前面代码里有写,需要注意配置 HMR 端口。不知道为何,CRXJS 不使用默认的 5173 端口。
其次,content script 需要在目标页面执行,所以 content script 修改后,常常需要刷新目标页。但是不知道什么原因,有时候 CRXJS 自动刷新目标页之后,content script 并没有更新,我猜测与这几步操作的执行顺序有关。我建议用开发者工具打开 content script 确认一眼。
比如我的 content script 是 content/src/index.ts,那么就确认 content/src/index.ts.js 即可。
以及,由于 HMR 可能会更新运行环境,如果此时恰逢我们在使用 chrome.tabs.sendMessage() 传递消息,可能导致 SidePanel 页和目标页连接断开,消息传送失败。解决方案嘛,就是多重启。修改完消息两端的代码之后,连目标页带侧边栏一起重启一次,即可。
目前我的扩展还在开发中,将来做好了可能会上线 CWS,暂时就先不公开仓库了。
新技术总能带来新的可能性,希望大家都能抓住这一波机会,无论是 OpenAI、LLM 还是浏览器 SidePanel 扩展,做出有价值的产品。
有任何问题、建议、想法,欢迎留言讨论,共同进步。

这几天回顾 Chrome Extension MV3,发现官方发布了一篇文章:Known issues when migrating to Manifest V3,准备结合我的经验,摘译过来分享给大家。建议大家还是要看一下原文,而且这篇文章也在不断更新,如果有新内容,也麻烦提醒我更新。目前版本是:2022-12-09,Chrome 108。
这篇文章列举了已知的、影响开发者将插件迁移到 MV3 的重大问题。答题分为两部分:
这篇文章是 Chromium issue 追踪器的子集,全部问题大家可以自行通过标签查找:平台 > 扩展。
新功能完成或者 bug 解决后,会随着稳定版发布。之后,对应的问题就会从这个页面里移除,所以,如果大家想知道问题变更的历史记录,可以去看: What’s New in Chrome Extensions。
webRequest.onAuthRequired events普通用户安装的 MV3 扩展无法拦截事件。在 Chrome 108 中已修复。
MV3 强制把 background page/script 改成 service worker,带来一个问题:以前的 background page 可以渲染页面,也就包含了完整的 DOM API,如今没有了,那么很多依赖 DOM 的功能也都做不到了。比如剪贴板、音乐播放、解析 HTML 等。所以新版本 Chrome 109 里增加了新的 Offscreen Documents API,方便用户使用。
目前这个 API 的文档很简单,我用 Edge,目前还停留在 108 版本,也没能试用,回头体验了再分享。
目前扩展 Service worker 执行一段时间之后就会被终止掉,据我测试大约是 28 秒。未来这个时间长度会动态调整,以便 service worker 的功能能够稳定完成。预计 2023 年一季度开始测试此功能。
目前扩展不允许执行外部脚本,只能执行发布时已经打包进去的那些脚本。导致 GA、FB 这些平台 SDK 很难用,Showman 也一样。下一步,MV3 会提供一个管理工具,让用户授权一些特定代码和样式。
预计也是 2023 年一季度开始测试。
目前 Storage 的存储空间是 1MB,开发团队准备适当扩容,具体的数字还没想好。我对这个变化没啥期待,Storage 本来也就存点配置之类的信息,感觉 1M 能存很多了。
webRequest 时间后不启动之前的版本中,MV3 扩展只会在安装后的很短时间里,能够接收到 Web Request API 事件,后面就接收不到了。如今,这个 bug 修复了,发布于 Chrome 107。
(容我吐个槽,刚开始推广 MV3 的时候,这种 bug 不胜枚举。)
现在的沙箱页面 CSP 无法被用户自定义配置覆盖,会固定使用初始值。大约会在 2023 年一季度开始测试修复。
看看这些设计导致的兼容性问题,和开发引入的质量 bug,当初他们有什么信心在 2023 年 1 月完成彻底迁移的?
我还是那个观点:
如果你在开发插件的时候遇到问题,欢迎与我交流。

前几天朋友提醒,Google 没有强推 Chrome Extension MV3。如果我没记错的话,按照原本的计划,今年 1 月 1 日起,Chrome Web Store 就要下架所有 MV2 扩展,只允许企业内部进行组织部署;7 月 1 日后就连用户本地也不允许安装和运行 MV2 扩展了。所有扩展必须升级到 MV3。
不过看 Manifest V2 support timeline 的最新内容,这些安排都延期和搁置了,至少到 2024 年 1 月之前,大家还能放心继续使用 MV2。至于之后能不能用,还在考虑之中。
我找到了官方的延迟通知,在这里:暂停清单 V2 逐步淘汰更改 (google.com)。按照官方的说法,除了一些开发负担,还有很多之前没想到的问题,比如缺少 DOM,导致很多合理的功能在 MV3 完全无法实现。这点我之前的 Chrome Extension Manifest V3 升级笔记 一文也提到过。
朋友推测,这里的原因在于:
相对来说,Google 推 GA4 就很硬气,猜测是因为 GA 没有什么特别大的竞争对手,从速度、效果、Adsense 等方面来看,GA 有巨大优势。而且升级 GA 很容易,换个 JS,改下初始化就行了。
升级 MV3 很困难,各种不兼容的 API 变化,大量程序几乎要完全重写。而且,看过 Chrome Extension Manifest V3 升级笔记 的同学应该还记得,Google 最开始推 MV3 的时候,API 和文档烂的一塌糊涂,简直不能用。
Chrome Web Store 团队会进一步聆听社区,讨论升级方案,并且在 2023 年 3 月拿出新方案(他们没有放弃!),所以大家也不要掉以轻心。
总之,我的建议是:
如果你有把浏览器扩展从 MV2 升级 MV3 的问题、经验,欢迎跟我讨论分享。

升级浏览器扩展到 MV3 时,因为 MV3 不再允许使用 background 页面,必须使用 service worker 类型的 background script。这样一来,就没有 window 对象,会遇到很多问题。
今天分享下如何在 service worker 类型的 background script 里完成扫描二维码并传出结果。比如我帮朋友升级 二维码生成器 (Quick QR) 这个扩展,它有个功能是通过右键菜单扫描页面中的二维码,并返回扫描结果。这个过程只能在 background script 里完成,于是升级时就给我带来了不小的麻烦。
首先,右键菜单的响应事件只能得到图片 URL。其次,扫描功能需要用到 jsqr,它接受 imageData 作为参数,可以返回扫描结果。所以我们就需要把 URL 转换成 imageData,过程如下:
// service worker 里可以使用 `fetch()` 请求服务器
const response = await fetch(src);
const blob = await response.blob();
// 使用 `createImageBitmap` 可以将支持的图片格式转换成位图
const bitmap = await createImageBitmap(blob);
const {width, height} = bitmap;
// 在 service worker 里,需要使用 OffscreenCanvas
const canvas = new OffscreenCanvas(width, height);
// 注意,这里只能是 `2d`,`bitmaprenderer` 无法使用 `getImageData` 方法
const context = canvas.getContext('2d');
context.drawImage(bitmap, 0, 0);
const imageData = context.getImageData(0, 0, width, height);
// 接下来识别就好
const data = jsQR(imageData.data, width, height);
const [tab] = await getActiveTab();
// 因为没有 `window`,`alert()`、`prompt()` 都不能用,只能通过在目标页面执行脚本的方式输出结果。注意,`tab.executeScript` 也没有了,必须用下面这个新 API,并且要提前申请权限
await chrome.scripting.executeScript({
args: [i18n(SCAN_RESULT), data.data],
target: {
tabId: tab.id,
},
func: (message, result) => {
result = prompt(message, result);
if (result) {
navigator.clipboard.writeText(result)
.catch(() => void 0);
}
},
});
大概过程如上面代码所示,需要注意的东西我都放在注释里了。
关于 MV3 的其它升级经验,我会更新在 Chrome Extension Manifest V3 升级笔记,欢迎常来看看。
(更多…)
我们知道,浏览器扩展(Chrome extension)可以通过 content script 在目标网页里执行代码。它和目标页面的 JS 处于不同的沙箱,但是共用一套 DOM。所以,content script 无法访问目标页面的 JS 变量,但是可以修改目标页面的 DOM、侦听 DOM 上的事件、或者触发 DOM 事件。
在这种情况下,要操作 CodeMirror 的时候会遇到一些问题。为了性能考虑,CodeMirror 不会把所有代码都渲染出来,而是会按需渲染。比如代码总共 1k 行,但是当前窗口里只能看到 40 行,那么 CodeMirror 就会渲染当前的 40 行,和前后大概各 20 行(方便快速滚屏),总计约 80 行。
换言之,如果无法接触到目标页面的 JS 环境,单纯从 DOM 里无法获取到全部代码,也很难修改 CM 里的代码。于是我们就需要一些特别的技巧——这些技巧在操作其它类似的类库时也会用到,所以写篇博客分享下。
<script>此时,我们可以通过创建 <script> 的方式向目标网页注入一段 JS。这个 JS 的执行环境也是目标网页,所以可以访问到目标网页 环境下的 JS 变量。
CodeMirror 会把实例添加到容器节点上,所以我们可以先找到容器节点,即 div.CodeMirror;然后访问它的 CodeMirror 属性找到 CM 实例;最后进行操作。
为了方便 content script 和 CodeMirror 交互,我们必须搭建一个桥梁,也就是下面这段代码:
const cms = document.getElementsByClassName('CodeMirror')
for (const dom of cms) {
// 这个 textarea 被 CodeMirror 视图所取代,是隐藏的。我们利用它传递代码
const textarea = dom.previousElementSibling;
const cm = dom.CodeMirror;
// 给 CodeMirror 添加清空功能,可以通过在 dom 上广播事件触发
dom.addEventListener('clear', () => {
cm.setValue('');
});
// `changes` 是 CodeMirror 的事件,在代码改变时触发,这里我们通过侦听它,并且将代码同步到 `textarea` 的方式,方便 content script 访问
cm.on('changes', cm => {
if (texture.value === cm.getValue()) {
return;
}
textarea.value = cm.getValue();
});
// 为初始化
textarea.value = cm.getValue();
}
因为我执行 content script 的时机总在页面完成初始化之后,此时所有 CodeMirror 都已就位。如果你要在别的时间执行,可以做一些调整。

关于 MV3 的知识,可以看这篇前文:
近期尝试把一个浏览器扩展升级到 MV3,然后发现了很多问题,写篇博客记录一下。
chrome.i18n.getMessage 或者 chrome.i18n.getUILanguagechrome.contextMenus.create 无法向 browser_action 添加菜单项(MV3 不再支持)chrome.storage.local.get(keys) Promise 结果固定返回 undefined,不可用,需用回 callbackchrome.permissions.request 未 Promise 化,无返回值chrome.permissions.containschrome.contextMenus.removechrome.contextMenus.create 时,必须传入 id,且不能使用 onclick 函数。只能侦听 chrome.contextMenus.onClicked.addListener,然后做进一步判断。(有些可能是暂时的)
MV3 不支持使用外部 JS,所以 Google Analytics 和 Facebook 分享按钮之类的 SDK 都无法使用。也即是说,现在扩展不能使用 GA,也不能嵌入 Facebook Like,也不能使用其它需要引用外部 SDK 才能实现的功能。
Service worker 没有 window 对象,也就没有那些 window 对象上才有的方法,比如 matchMedia()。所以我们就无法在 service worker 里检查系统的 dark mode,实现动态修改 icon 的功能。
因为 createImageBitmap 无法解析 SVG blob,所以原则上,所有 SVG 的操作均无法执行。
无法使用 alert() 和 prompt(),也无法手动弹出 popup。所以如果我们想从 service worker 里向外传出内容,只有三个选择:
chrome.scripting.executeScript() 在目标页执行脚本(1)(3)需要不同的需要权限,(2)的体验不好。怎么用就自己选择吧。