你是否曾经对某个网页的功能感到不满?比如 Google 搜索页面上那个显眼的 AI Mode 按钮:
通过浏览器扩展,你可以让它彻底消失:
什么是浏览器扩展?一般而言,浏览器扩展是一种修改用户浏览网页的方式,它赋予了网站使用者而不是开发者更多的权限。让有能力的用户可以自定义它们的网页浏览体验,现代浏览器提供了极其丰富的扩展 API,甚至能实现一些看起来需要 app 才能做到的事情,但它的基本出发点是让用户可以控制正在浏览的网页。
一些例子
隐藏 Google AI 相关功能
根据规则自定义网页重定向
解除网页上的复制粘贴限制
在浏览器后台运行定时任务
基本结构1 2 3 4 5 6 manifest.json # 扩展入口文件 icon/*.png # 扩展图标 content-scripts/content.js # 注入到网页的脚本,可选 background.js # 后台脚本,可选 options.html # 配置页面,可选 popup.html # 点击扩展 icon 的弹出窗口,可选
创建 Manifest现在,我们来创建一个基本的扩展,不使用任何包管理器、TypeScript、或者 Web 框架,只使用基本的 JavaScript 完成。在下一章节中,将会使用现代开发工具链(WXT)重写它,这里只是让我们对扩展的实际结构有个了解。作为例子,我们将会创建一个扩展来隐藏 google.com 中那个 AI Mode 按钮。
首先,创建一个 manifest.json,里面是一些基本信息,manifest_version 是一个固定值,代表使用扩展 API 的第三个主要版本。
1 2 3 4 5 6 { "manifest_version" : 3 , "name" : "Hide AI Mode on Google Search" , "version" : "0.0.1" , "description" : "Hide the AI Mode button on Google Search pages." }
实现 Content Script然后我们需要创建一个 content-scripts/content.js 脚本(这个路径不是固定的,只是一般做法),里面实现在网页加载时自动隐藏 AI Mode 按钮。 基本思路也很简单
找到 google.com 中的 AI Mode 按钮的 CSS selector
在 google.com 加载时注入一个 js 脚本,自动隐藏它
分析获得 CSS selector打开 google.com,然后打开 DevTools > Elements 可以看到 AI Mode 是一个 button 元素,但它看起来并没有可以作为稳定 CSS selector 的东西。
对于现代网站而言,class 通常被构建工具压缩的连亲妈都不认识了,所以需要一些跳脱的方法,例如 button 中包含的 SVG 似乎很适合作为一个选择器(网站上的 icon 改动频率并不高)。这样,借助 :has 子选择器和属性选择器,我们就可以组合出一个稳定的 selector 了。
1 button:has([d="M15.65 11.58c.18-.5.27-1.03.31-1.58h-2c-.1 1.03-.51 1.93-1.27 2.69-.88.87-1.94 1.31-3.19 1.31C7.03 14 5 12.07 5 9.5 5 7.03 6.93 5 9.5 5c.46 0 .89.08 1.3.2l1.56-1.56C11.5 3.22 10.55 3 9.5 3 5.85 3 3 5.85 3 9.5S5.85 16 9.5 16c.56 0 2.26-.06 3.8-1.3l6.3 6.3 1.4-1.4-6.3-6.3c.4-.5.72-1.08.95-1.72z" ])
:has 指的是如果元素包含指定 selector 的子元素,就匹配它。对于这个 selector 而言,这意味着只有包含特定 AI Mode svg 图标的 button 按钮才会被匹配到。
实现 content script接下来创建 content.js 脚本,由于我们只想隐藏这个按钮,最简单的方法就是注入一个 css 样式,例如
1 2 3 4 const style = document .createElement ('style' ) style.textContent = 'button:has([d="M15.65 11.58c.18-.5.27-1.03.31-1.58h-2c-.1 1.03-.51 1.93-1.27 2.69-.88.87-1.94 1.31-3.19 1.31C7.03 14 5 12.07 5 9.5 5 7.03 6.93 5 9.5 5c.46 0 .89.08 1.3.2l1.56-1.56C11.5 3.22 10.55 3 9.5 3 5.85 3 3 5.85 3 9.5S5.85 16 9.5 16c.56 0 2.26-.06 3.8-1.3l6.3 6.3 1.4-1.4-6.3-6.3c.4-.5.72-1.08.95-1.72z"]) { display: none; }' document .head .appendChild (style)
在 Manifest 中声明接下来,我们还需要在 manifest.json 中声明这个内容脚本,告诉浏览器要在哪些网站注入,这里我们只需要在 google.com 注入,所以需要修改为
1 2 3 4 5 6 7 8 9 { // before config... "content_scripts" : [ { "matches" : [ "https://www.google.com/" ] , // 只匹配首页,因为 AI Mode 按钮仅在首页出现 "js" : [ "content-scripts/content.js" ] } ] }
调试扩展接下来,我们需要在 Chrome 中加载这个扩展。
首先,打开 chrome://extensions/,并启用 Developer Mode
然后,使用 Load unpacked 按钮选择扩展目录,就能加载扩展到浏览器了。
访问 google.com,可以看到 AI Mode 按钮确实不见了,但会闪一下出现然后才消失,这意味着注入的脚本时机不够早,在注入的脚本执行之前,AI Mode 按钮就已经显示了,然后脚本注入才隐藏了按钮,这是一个基本的时序问题。幸运的是,可以简单调整 manifest 配置解决这个问题。
1 2 3 4 5 6 7 8 9 { "content_scripts" : [ { "matches" : [ "https://www.google.com/" ] , "js" : [ "content-scripts/content.js" ] , "run_at" : "document_start" // 在网页刚开始加载时就注入脚本,参考 https://developer.chrome.com/docs/extensions/reference/manifest/content-scripts#world-timings } ] }
然后在 chrome://extensions/ 重新加载扩展让修改生效。
接着就看到了一个错误 Uncaught TypeError: Cannot read properties of null (reading 'appendChild')。
这是因为注入脚本的时机非常早,甚至连 document.head 标签都还没有渲染,可以修改为在 document.documentElement 注入 style 标签。
1 2 3 4 const style = document .createElement ('style' ) style.textContent = 'button:has([d="M15.65 11.58c.18-.5.27-1.03.31-1.58h-2c-.1 1.03-.51 1.93-1.27 2.69-.88.87-1.94 1.31-3.19 1.31C7.03 14 5 12.07 5 9.5 5 7.03 6.93 5 9.5 5c.46 0 .89.08 1.3.2l1.56-1.56C11.5 3.22 10.55 3 9.5 3 5.85 3 3 5.85 3 9.5S5.85 16 9.5 16c.56 0 2.26-.06 3.8-1.3l6.3 6.3 1.4-1.4-6.3-6.3c.4-.5.72-1.08.95-1.72z"]) { display: none; }' document .documentElement .appendChild (style) // document.documentElement 代表页面的根元素,即 `<html></html>` 标签。
再次刷新扩展然后访问 google.com 可以看到 AI Mode 按钮不见了,而且也不再出现闪烁的情况。
添加图标最后,这个扩展还缺少图标,我们将一张 128x128 的 png 图像放在 icon/128.png。 然后修改 manifest 添加 icons 设置即可。
1 2 3 4 5 { "icons" : { "128" : "icon/128.png" } }
再次刷新扩展,就可以看到扩展图标已经被正确加载了。
总结这就是第一个基本扩展。通过这个例子,我们了解了扩展开发的几个核心概念:
Manifest 文件定义了扩展的基本信息和需要哪些权限
Content Script 可以注入到网页中,直接操作 DOM
脚本注入时机会影响实际效果,需要根据场景选择 document_start、document_end 或 document_idle
扩展的调试流程和普通网页开发类似,都可以在 DevTools 中查看错误
你可能注意到,直接编写原生 JavaScript 并手动管理文件有些繁琐。在下一章中,我们将使用 WXT 这个现代开发工具重构这个扩展,体验 TypeScript、热重载等特性。
如果有任何问题,欢迎加入 Discord 群组讨论。https://discord.gg/VxbAqE7gj2
完整代码:https://github.com/rxliuli/browser-extension-dev-examples/tree/main/packages/01-basic
参考
Google Chrome 扩展开发文档 https://developer.chrome.com/docs/extensions