1. 项目概述Jetro 是什么以及它为何值得关注如果你是一名长期与浏览器打交道的开发者或者是一个对提升网页浏览效率有极致追求的“效率控”那么你很可能已经对浏览器扩展Extension生态了如指掌。从广告拦截到密码管理从页面美化到自动化脚本扩展极大地丰富了浏览器的能力边界。今天要聊的这个项目——Jetro就是这样一个旨在重塑浏览器扩展开发与使用体验的开源项目。它不是一个具体的功能扩展而是一个浏览器扩展的运行时框架。简单来说Jetro 试图为开发者提供一个更现代、更高效、更模块化的方式来构建浏览器扩展同时也为最终用户带来更稳定、更安全的扩展使用体验。为什么在 Chrome Web Store 和 Firefox Add-ons 已经如此成熟的今天我们还需要关注 Jetro 这样的项目核心痛点在于传统扩展开发模式的“历史包袱”。传统的扩展开发尤其是基于 Manifest V2 的扩展虽然灵活但存在诸多问题内容脚本Content Script与后台脚本Background Script的通信机制复杂且容易出错扩展的权限管理相对粗放不同浏览器平台Chrome、Firefox、Edge的 API 存在差异增加了跨平台开发的成本扩展的更新和热重载体验对开发者不友好。Jetro 的出现正是为了解决这些问题。它通过引入现代前端工程化的思想将扩展的各个部分如弹出页、选项页、内容脚本、后台服务进行更清晰的解耦和更高效的通信管理让开发者能像开发一个现代 Web 应用一样去开发浏览器扩展。对于用户而言基于 Jetro 框架开发的扩展理论上能获得更好的性能和安全性。因为框架本身对资源加载、API 调用和权限隔离做了更精细的控制。对于开发者Jetro 意味着更快的开发速度、更少的跨浏览器兼容性代码以及更易于维护的项目结构。这个项目标题 “JetroExtension/Jetro” 本身就指向了其 GitHub 仓库暗示了它是一个处于活跃开发状态的开源工具值得技术社区特别是前端和浏览器生态的开发者投入精力去研究和实践。2. 核心架构与设计理念拆解2.1 模块化与微前端思想的应用Jetro 最核心的设计理念是将现代 Web 开发中的模块化和微前端架构思想引入到浏览器扩展开发中。在传统扩展中一个扩展的各个部分Popup、Options、Background、Content Scripts虽然物理上是分离的但在逻辑和资源管理上常常是紧耦合的。开发者需要手动管理这些部分之间的依赖、通信和状态同步代码组织容易变得混乱。Jetro 的做法是将扩展的每个 UI 部分如弹出窗口、选项页面以及后台服务都视为一个独立的“微应用”。每个微应用可以拥有自己的技术栈理论上但通常推荐统一、自己的构建流程和自己的生命周期。Jetro 框架则充当了一个“运行时容器”和“总线”的角色。它负责应用的加载与隔离按需加载各个微应用并确保它们运行在合适的上下文中例如内容脚本在页面上下文中后台服务在扩展的后台上下文中。应用间通信提供一套标准、高效、类型安全的通信机制让 Popup、Options、Background、Content Script 之间可以像调用本地函数一样进行交互而无需再直接操作chrome.runtime.sendMessage或postMessage这些底层 API。共享依赖与状态管理允许在容器层面注入共享的依赖库如状态管理工具 Redux/Vuex、工具函数库等和共享状态避免在每个微应用中重复打包和初始化。这种架构带来的直接好处是开发体验的极大提升。团队可以按照功能模块划分成小团队并行开发不同的“微应用”每个部分可以独立开发、测试、部署在扩展更新的大框架下技术栈的升级或替换可以局部进行风险可控。2.2 基于消息的通信机制优化通信是浏览器扩展开发的难点和痛点。Jetro 对此进行了深度抽象和优化。它很可能实现了一套基于Promise和TypeScript的 RPC远程过程调用风格通信层。传统方式的问题// 发送方 chrome.runtime.sendMessage({type: fetchData, url: ...}, (response) { if (chrome.runtime.lastError) { // 处理错误 } console.log(response.data); }); // 接收方 chrome.runtime.onMessage.addListener((request, sender, sendResponse) { if (request.type fetchData) { // 处理逻辑 sendResponse({data: result}); } });这种方式存在回调地狱、错误处理繁琐、消息类型难以维护、缺乏类型提示等问题。Jetro 的优化思路 Jetro 可能会定义一个通信协议和服务接口。开发者首先用 TypeScript 定义好各个服务Service的接口。// 定义在共享类型文件中 interface DataService { fetchData(url: string): PromiseDataResult; updateSettings(settings: UserSettings): Promisevoid; }然后在服务提供方如 Background Script实现这个接口并在 Jetro 容器中注册。在服务调用方如 Popup 页面开发者可以直接“注入”或“获取”这个服务代理像调用本地函数一样使用。// 在 Popup 组件中 const dataService container.getServiceDataService(dataService); const result await dataService.fetchData(https://api.example.com/data); // 类型安全自动补全框架底层会自动处理消息的序列化、发送、接收、反序列化和错误传递。对于调用者来说这完全是一个透明的本地调用过程。这极大地简化了代码提高了开发效率并借助 TypeScript 保证了类型安全减少了运行时错误。2.3 构建与开发工具链集成一个优秀的框架离不开配套的工具链。Jetro 必然与现代化的前端构建工具深度集成例如Vite或Webpack。它可能会提供一个官方的 CLI命令行工具或预设的构建配置。开发服务器与热重载HMR这是传统扩展开发最痛苦的环节之一。修改代码后需要手动点击扩展的“重新加载”按钮刷新测试页面才能看到效果。Jetro 的目标是实现真正的热重载。当你修改 Popup 的 Vue/React 组件时开发服务器能立即将变更推送到浏览器无需手动刷新扩展或页面。这对于提升开发效率是革命性的。多目标构建一个命令同时为 Popup、Options、多个 Content Scripts 和 Background Script 进行构建和打包并输出符合浏览器扩展目录结构的文件。环境变量与配置管理方便地区分开发、测试、生产环境注入不同的 API 端点、功能开关等。资源处理自动处理图片、字体、CSS 等资源优化打包体积。这些工具链的集成使得从零开始一个扩展项目变得非常简单开发者可以更专注于业务逻辑而不是繁琐的工程配置。3. 从零开始基于 Jetro 创建一个浏览器扩展3.1 环境准备与项目初始化假设我们已经决定采用 Jetro 来开发一个新的效率工具类扩展。首先我们需要一个现代的 Node.js 环境建议 LTS 版本。第一步使用脚手架创建项目如果 Jetro 提供了官方的脚手架工具初始化将非常简单。# 假设 Jetro CLI 工具名为 create-jetro-app npx create-jetro-app my-awesome-extension cd my-awesome-extension npm install这个命令会创建一个标准的项目结构类似于下面这样my-awesome-extension/ ├── packages/ # 采用 monorepo 结构每个微应用一个包 │ ├── popup/ # 弹出窗口应用 │ ├── options/ # 选项页面应用 │ ├── background/ # 后台服务应用 │ └── content/ # 内容脚本应用可能按功能分多个 ├── shared/ # 共享的代码、类型定义、工具函数 ├── scripts/ # 构建、部署脚本 ├── manifest.json # 扩展的主清单文件可能由构建生成或合并 └── package.json第二步理解核心配置文件manifest.json这是扩展的“身份证”。Jetro 可能会在构建时根据各子包的配置自动生成或合并最终的 manifest。你需要关注permissions权限、content_scripts内容脚本匹配规则、background后台脚本声明等关键字段。Jetro 框架可能会要求一些固定的权限如storage、activeTab等以支持其运行时。各子包内的package.json和构建配置如vite.config.ts用于定义每个微应用的具体技术栈和构建行为。3.2 定义服务与实现业务逻辑我们的示例扩展功能是在任意网页上高亮显示所有图片并可以一键下载页面中的所有图片。1. 定义共享类型与服务接口在shared/types中// shared/types/services.ts export interface ImageInfo { src: string; alt: string; width: number; height: number; } export interface IImageService { // 获取当前页面的所有图片信息 scanPageImages(): PromiseImageInfo[]; // 下载指定图片 downloadImage(url: string, filename?: string): Promisevoid; // 批量下载图片 downloadAllImages(urls: string[]): Promisevoid; } // shared/types/events.ts export enum AppEvents { IMAGES_SCANNED images:scanned, DOWNLOAD_PROGRESS download:progress, }2. 实现后台服务在packages/background中后台服务是实现核心逻辑和安全操作的地方比如网络请求、大量数据处理。// packages/background/src/services/ImageService.ts import { IImageService, ImageInfo } from shared/types; export class ImageService implements IImageService { async scanPageImages(): PromiseImageInfo[] { // 这个函数本身不会在这里执行。 // 它会被注册到 Jetro 的通信层当 Content Script 或 Popup 调用时Jetro 会将请求路由到 Content Script 去执行实际的 DOM 操作。 // 这里更多是定义接口实际实现可能通过消息转发给 Content Script。 // 这是一个重要的设计涉及页面 DOM 的操作应交给 Content Script。 throw new Error(This method should be called from content script context.); } async downloadImage(url: string, filename?: string): Promisevoid { // 后台脚本可以使用 fetch 和 chrome.downloads API const response await fetch(url); const blob await response.blob(); const objectUrl URL.createObjectURL(blob); const suggestedName filename || url.split(/).pop() || download; chrome.downloads.download({ url: objectUrl, filename: images/${suggestedName}, saveAs: false, }, (downloadId) { URL.revokeObjectURL(objectUrl); if (chrome.runtime.lastError) { throw new Error(chrome.runtime.lastError.message); } }); } async downloadAllImages(urls: string[]): Promisevoid { for (const url of urls) { await this.downloadImage(url).catch(err { console.error(Failed to download ${url}:, err); // 可以选择继续下载其他图片 }); } } } // packages/background/src/main.ts import { Container } from jetro-core; import { ImageService } from ./services/ImageService; const container Container.getInstance(); container.registerService(imageService, new ImageService()); // 初始化其他后台逻辑...3. 实现内容脚本在packages/content中内容脚本是唯一能直接访问和操作页面 DOM 的部分。我们需要在这里实现scanPageImages的具体逻辑。// packages/content/src/main.ts import { exposeService } from jetro-content-script; import { IImageService, ImageInfo } from shared/types; class ContentImageService implements IImageService { async scanPageImages(): PromiseImageInfo[] { const images Array.from(document.querySelectorAll(img)); return images.map(img ({ src: img.src, alt: img.alt, width: img.naturalWidth, height: img.naturalHeight, })).filter(imgInfo imgInfo.src); // 过滤掉没有 src 的图片 } // 内容脚本不能直接触发下载所以这些方法需要转发给后台 async downloadImage(url: string): Promisevoid { // 通过 Jetro 调用后台的 ImageService return window.__JETRO_CALL__(imageService.downloadImage, url); } async downloadAllImages(urls: string[]): Promisevoid { return window.__JETRO_CALL__(imageService.downloadAllImages, urls); } } // 将服务暴露给 Jetro 框架使其能被 Popup 或 Background 调用 exposeService(imageService, new ContentImageService());3.3 构建 Popup 用户界面Popup 是用户点击扩展图标后弹出的界面。我们使用一个现代前端框架如 Vue 3来开发。!-- packages/popup/src/App.vue -- template div classpopup-container h3页面图片探测器/h3 button clickscanImages :disabledscanning扫描页面图片/button div v-ifimages.length 0 p找到 {{ images.length }} 张图片/p ul classimage-list li v-for(img, index) in images :keyindex img :srcimg.src :altimg.alt classthumbnail / span{{ img.alt || 无描述 }} ({{ img.width }}x{{ img.height }})/span button clickdownloadImage(img.src)下载/button /li /ul button clickdownloadAll下载全部/button /div p v-else点击扫描按钮开始。/p /div /template script setup langts import { ref, onMounted } from vue; import { useService } from jetro-vue; // 假设 Jetro 提供了 Vue 集成钩子 import type { IImageService, ImageInfo } from shared/types; const imageService useServiceIImageService(imageService); const images refImageInfo[]([]); const scanning ref(false); const scanImages async () { scanning.value true; try { images.value await imageService.scanPageImages(); } catch (error) { console.error(扫描失败:, error); alert(扫描失败请确保在普通网页页面使用。); } finally { scanning.value false; } }; const downloadImage async (url: string) { try { await imageService.downloadImage(url); } catch (error) { console.error(下载失败:, error); } }; const downloadAll async () { const urls images.value.map(img img.src); if (urls.length 0) { try { await imageService.downloadAllImages(urls); } catch (error) { console.error(批量下载失败:, error); } } }; // 组件挂载时可以尝试自动扫描当前页 onMounted(() { // 可选自动扫描 // scanImages(); }); /script style scoped .popup-container { width: 400px; padding: 16px; } .image-list { max-height: 300px; overflow-y: auto; } .thumbnail { max-width: 50px; max-height: 50px; margin-right: 8px; vertical-align: middle; } /style3.4 构建、调试与打包开发模式 在项目根目录运行npm run dev这个命令会启动 Jetro 的开发服务器它可能做以下几件事同时构建 Popup、Options、Background、Content Scripts 等所有目标。启动一个开发服务器提供热重载功能。自动将构建好的扩展加载到浏览器可能需要你预先打开浏览器的“开发者模式”并加载未打包的扩展目录。打开一个调试页面方便查看日志。此时你修改任何源代码对应的部分都会热更新无需手动刷新扩展。生产构建npm run build这个命令会进行代码压缩、Tree Shaking 等优化并在dist或build目录下生成一个完整的、可以提交到 Chrome Web Store 或 Firefox Add-ons 的扩展文件夹。调试技巧Popup/Option 页面右键点击扩展图标 - “检查弹出内容”即可打开 DevTools。Background Script在 Chrome 中打开chrome://extensions/找到你的扩展点击“背景页”或“Service Worker”链接。Content Script在它注入的普通网页上打开 DevTools在 Sources 面板中通常能找到以chrome-extension://[your-extension-id]开头的文件或者直接在 Console 中查看来自扩展的日志注意选择正确的上下文环境。4. 深入原理Jetro 运行时机制剖析4.1 依赖注入与服务容器Jetro 的核心是一个轻量级的依赖注入DI容器。这个容器贯穿于扩展的各个部分Background, Popup, Content Script。它的作用是管理应用中所有“服务”实例的生命周期和依赖关系。工作流程注册在应用启动时例如 Background 的 main.ts开发者将服务类的实例注册到容器中并赋予一个唯一的标识符如imageService。解析当其他部分如 Popup 组件需要某个服务时它向容器请求这个标识符对应的服务。提供容器返回服务实例。如果这个服务依赖于其他服务容器会自动递归地解析并注入这些依赖。优势解耦服务使用者不需要知道服务是如何创建的只需要知道接口。可测试性在单元测试中可以轻松地用 Mock 对象替换掉真实的依赖。单一实例容器通常可以管理服务的生命周期确保某些服务如全局状态管理是单例的。在跨上下文如 Popup 调用 Background 服务的场景下Jetro 的容器会与通信层协作。当 Popup 请求一个服务时本地容器发现该服务不在当前上下文中它会通过通信层将请求代理到拥有该服务的上下文如 Background并将结果返回。这个过程对开发者是透明的。4.2 跨上下文通信的底层实现这是 Jetro 框架中最精妙的部分。它需要桥接多个完全隔离的 JavaScript 执行环境扩展进程运行 Background Script (Service Worker) 和 Popup/Option 页面。页面进程运行 Content Scripts它们与普通网页的 JS 环境隔离但能访问 DOM。可能的其他进程如 DevTools 页面等。Jetro 需要在这三者之间建立一条可靠、高效、支持双向通信的通道。其底层很可能依然基于浏览器原生的chrome.runtime.sendMessage、chrome.tabs.sendMessage和window.postMessage但进行了高层抽象。实现猜想代理Proxy与存根Stub当在 Popup 中调用imageService.downloadImage(url)时imageService实际上是一个本地代理对象。这个代理对象的方法被调用时它并不执行逻辑而是将方法名和参数序列化通过chrome.runtime.sendMessage发送给 Background。消息路由Background 中有一个消息路由器它监听chrome.runtime.onMessage事件。收到消息后根据消息中的“服务名”和“方法名”从本地的服务容器中找到真正的服务实例调用对应的方法。结果返回服务实例执行完成后将返回值或 Promise序列化再通过chrome.runtime.sendResponse或类似的回调机制发回给 Popup 端的代理对象。Promise 转换代理对象收到响应后解析数据并 resolve 或 reject 最初调用时返回的 Promise。对于 Content Script 与 Background 的通信流程类似但可能使用chrome.tabs.sendMessage和chrome.runtime.onMessage。Jetro 框架会隐藏这些细节为开发者提供统一的调用接口。4.3 资源加载与模块联邦现代前端应用常使用动态导入Dynamic Import来优化首屏加载。在扩展中由于 Popup 通常是点击后才瞬间加载对加载速度要求极高。Jetro 可以利用Webpack 5 的 Module Federation或Vite 的构建优化来实现扩展内资源的按需加载和共享。例如Vue 或 React 的运行时代码、公共的工具库可以被构建为一个或多个“共享包”Shared Bundle。当 Popup 加载时它优先加载这些共享包。而 Popup 业务特有的代码则被拆分成更小的块Chunk动态加载。这样能最大化利用缓存减少每次 Popup 打开时的网络请求和解析时间。Jetro 的构建配置需要精心设计以确保最终打包出来的扩展文件结构清晰没有冗余代码同时满足浏览器扩展对文件路径和 CSP内容安全策略的特殊要求。5. 实战避坑与高级技巧5.1 权限管理与安全边界使用 Jetro 并不能绕过浏览器的安全沙箱。你必须清晰理解扩展的权限模型。清单文件manifest.json这是权限声明的地方。Jetro 项目可能需要一些基础权限来运行其框架如storage用于状态持久化、activeTab用于与当前标签页交互。你的业务功能所需的权限如downloads、all_urls用于内容脚本注入需要额外声明。Content Script 隔离内容脚本运行在一个“隔离世界”Isolated World中它与页面原有的 JavaScript 环境完全隔离无法直接访问页面全局变量如window.jQuery反之亦然。Jetro 的通信机制必须处理好这个隔离。常见坑点试图在 Content Script 中直接调用页面脚本定义的函数会导致错误。需要通过window.postMessage与页面脚本进行安全通信但这超出了 Jetro 默认通信层的范围需要自行实现。CSP内容安全策略扩展有自己的 CSP。Jetro 生成的脚本和样式必须符合该 CSP。如果你的扩展需要加载外部资源如图片、字体、或特定的脚本必须在manifest.json的content_security_policy字段中明确允许。重要提示不要轻易使用unsafe-eval或过宽的script-src指令这会极大降低扩展的安全性。5.2 状态管理与数据持久化扩展的不同部分经常需要共享状态比如用户的设置、当前高亮的文本等。共享状态可以使用 Jetro 容器提供的状态管理机制如果它内置了或者集成一个轻量级的状态库如Zustand或PiniaVue。关键是要确保状态变更能同步到所有需要的上下文中。Jetro 的通信层可以用于广播状态变更事件。数据持久化浏览器提供了chrome.storageAPI推荐使用chrome.storage.sync或chrome.storage.local。你可以在 Background 中创建一个StorageService统一管理所有持久化操作并通过 Jetro 暴露给其他部分使用。注意chrome.storageAPI 是异步的返回 Promise。状态同步的挑战当 Popup 关闭后其 JavaScript 环境会被销毁。重新打开时需要从持久化存储或 Background 中重新获取状态。设计时要考虑状态的初始化流程。5.3 性能优化与调试懒加载 Content Scripts不要在manifest.json中通过content_scripts的matches字段将脚本注入到所有页面。这会严重拖慢浏览器速度。应该使用chrome.scripting.executeScriptAPI 在用户需要时例如点击扩展按钮后动态注入脚本。Jetro 框架应该提供便捷的方式来管理和执行这种动态注入。后台 Service Worker 的休眠与唤醒Manifest V3 强制使用 Service Worker 作为后台脚本它会在不活动时休眠。你的代码必须能处理被唤醒后的状态恢复。避免在 Service Worker 中保存大量的内存状态重要数据应存入chrome.storage。Jetro 框架本身的生命周期管理需要与此兼容。高效的通信虽然 Jetro 的 RPC 调用很便捷但频繁发送大量数据的消息仍然会有性能开销。对于需要传输大量数据如图片列表的场景考虑使用chrome.storage作为中转或者使用sendMessage的transfer参数如果支持来传递ArrayBuffer等可转移对象。使用 Chrome DevTools 的扩展调试工具除了常规的 Console、Sources 面板特别关注Application面板下的Service Workers和Storage部分以及Network面板中过滤chrome-extension://的请求。5.4 扩展的打包与发布环境变量使用.env文件管理开发和生产环境的不同配置如 API 端点、日志级别、是否启用调试工具等。构建工具如 Vite可以很好地支持这一点。版本管理与更新在manifest.json中正确设置version。每次发布新版本时递增。考虑用户无缝升级的需求如果数据结构有变更需要在 Background 的安装/更新事件监听器chrome.runtime.onInstalled中编写数据迁移逻辑。多浏览器支持虽然 Jetro 旨在简化跨浏览器开发但 Chrome、Firefox、Edge 的 API 和 manifest 规范仍有细微差别。你可能需要条件编译或不同的构建配置。可以使用webextension-polyfill库来抹平大部分 API 差异。代码签名与商店提交准备好各种尺寸的图标、详细的描述和截图。仔细阅读 Chrome Web Store 或 Firefox Add-ons 的开发者政策确保扩展符合规范特别是隐私政策的要求。6. 常见问题与排查实录在实际开发基于 Jetro 或类似框架的扩展时你一定会遇到一些典型问题。以下是我在多个项目中总结出来的“避坑指南”。问题 1调用服务方法时报错 “Service not found” 或 “Method not defined”。排查思路检查服务注册确认服务是否在正确的上下文中被注册。例如一个需要在 Content Script 中调用的、操作 DOM 的服务方法其实现应该注册在 Content Script 的容器里而不是 Background。检查服务名调用时使用的服务标识符必须与注册时完全一致大小写敏感。检查上下文Popup 脚本无法直接调用只在另一个 Popup 脚本中注册的服务。确保服务注册在全局可访问的上下文中通常是 Background或者通过事件机制进行通信。检查 Jetro 初始化确保调用服务前Jetro 的容器和通信层已经初始化完成。在 Vue/React 组件中通常可以在onMounted生命周期钩子中进行调用。问题 2Content Script 注入失败或者注入后无法与 Background 通信。排查思路检查 manifest.json确认content_scripts的matches模式是否正确匹配目标网页的 URL。或者如果使用动态注入检查chrome.scripting.executeScript的target和func/files参数。检查权限动态注入需要scripting权限并在manifest.json中声明host_permissions或具体的匹配模式。检查 CSP某些网站有严格的 CSP会阻止扩展的内容脚本执行。这种情况下你的扩展可能在该网站无法正常工作。可以在 Content Script 开头加console.log来验证是否成功注入。检查通信目标Content Script 向 Background 发送消息使用的是chrome.runtime.sendMessage。确保没有错误地使用了chrome.tabs.sendMessage这是 Background 向特定标签页的 Content Script 发送消息用的。问题 3Popup 页面打开缓慢或样式丢失。排查思路检查资源加载打开 Popup 的 DevTools查看 Network 面板是否有资源JS、CSS、图片加载失败或缓慢。扩展的资源都是从本地加载的应该极快。如果慢可能是构建产物过大。优化构建产物使用构建分析工具如rollup-plugin-visualizer查看打包后的模块体积。确保对第三方库进行了有效的 Tree Shaking。考虑对大的依赖进行动态导入。检查 CSS 作用域如果使用 Vue/React 的 scoped CSS确保选择器正确。有时 CSS 文件可能因为路径问题未被加载。问题 4扩展更新后用户数据或状态丢失。排查思路数据存储位置确认关键用户数据是存储在chrome.storage中而不是内存变量里。Service Worker 休眠或扩展更新都会导致内存状态丢失。监听更新事件在 Background 的 Service Worker 中监听chrome.runtime.onInstalled事件在details.reason update时执行必要的数据迁移或初始化逻辑。版本兼容在chrome.storage中存储一个数据结构的版本号。更新时对比版本号如果旧版本数据结构不兼容则运行迁移脚本将其转换为新格式。问题 5在 Firefox 或 Edge 上运行不正常。排查思路使用 polyfill确保引入了webextension-polyfill库并使用browser对象代替chrome对象进行 API 调用或者让 polyfill 自动处理。检查 Manifest V3 支持Firefox 对 Manifest V3 的支持是逐步推进的且与 Chrome 存在差异。如果使用 MV3需关注 Firefox 的兼容性状态并准备 MV2 的备选构建。检查特定 API某些 API如chrome.scripting、chrome.declarativeNetRequest在不同浏览器中的支持度和行为可能有差异。查阅 MDN 或相应浏览器的扩展开发文档。开发浏览器扩展是一个细致活尤其是涉及多个执行环境和安全限制时。Jetro 这类框架通过提供高层次的抽象极大地降低了开发复杂度但底层浏览器的运行机制和限制依然存在。理解这些原理结合框架提供的便利才能高效地构建出稳定、强大的浏览器扩展。