六款浏览器扩展,零台服务器:一套本地优先工具套件的工程实践与代价

本地优先浏览器扩展套件的工程实践与架构解析

文档版本 1.0
适用范围 Omni Suite 全系列六款浏览器扩展
技术基线 Manifest V3 · TypeScript 5 · Next.js 16 · React 19


摘要

Omni Suite 是一套由六款独立浏览器扩展构成的本地优先工具集合,覆盖图片采集、简历匹配、多平台分发、端侧人工智能、小游戏与视频转封装六个领域。本白皮书从工程视角剖析其共性技术架构,重点阐述以下命题:在浏览器扩展这一受限执行环境中,如何在不引入服务器基础设施的前提下,同时达成数据不出域、能力不掉线、失败可审计三重目标。

白皮书将首先界定问题空间,分析传统云端方案的固有代价;随后阐述本地优先架构的技术实现路径,包括六款扩展各自的领域算法与降级策略;再者说明官网作为产品文档载体的前端工程实现;最后讨论质量保障体系的构建方法与遗留挑战。

全文所述技术细节均以实际实现为准,不含前瞻性规划。所有性能数据来源于实测,不使用行业通用估算值。


第一章 问题定义

1.1 浏览器扩展的处境

浏览器扩展是一类特殊的软件形态。它既不像桌面应用那样拥有完整的文件系统与进程控制权,也不像网页应用那样可以任意调用后端接口。它被夹在浏览器的安全模型中间:内容脚本运行在页面上下文,扩展页面运行在隔离的扩展上下文,service worker 则是一个随时可能被终止的临时线程。

Manifest V3 的引入进一步收紧了边界。曾经的持久化 background page 被替换为 service worker,这个变化带来了两个深远影响。第一,扩展无法假设自己拥有常驻内存,任何状态必须持久化到本地存储。第二,扩展失去了在页面加载前拦截网络请求的能力,相应的能力被移交给了声明式网络规则。

这些约束塑造了 Omni Suite 的技术选型空间:在这样的环境里,哪些事情是可以做的,哪些事情必须用非常规手段完成,以及哪些事情是根本无法回避的。

1.2 云端方案的固有代价

主流扩展产品普遍采用云端后端架构:本地扩展负责采集与交互,服务器负责存储、计算与分发。这条路径在工程上是最省事的,但它引入了三类结构性成本。

运营成本是显性的。 一旦产品进入真实用户规模,服务器、带宽、存储与计算的开销与用户量线性相关。即便是轻量级的图片特征提取服务,若日活达到万级,年成本也会进入五位数区间。更麻烦的是成本曲线不会因为用户规模而变缓——它只会更陡。

隐私成本是隐性的但不可逆的。 数据一旦离开用户设备,就脱离了用户的控制范围。即便运营方承诺不滥用,用户也无法验证这一承诺。这类风险不在于技术本身,而在于信任的建立需要时间和证据,而破坏只需要一次事故。对于处理简历、浏览历史这类高度敏感数据的工具,信任是产品价值的核心组成部分,一旦崩塌便无法重建。

可用性成本是最容易被低估的。 云端服务存在不可用时段、区域性故障、流量高峰期的响应延迟,以及超出预算后的降级甚至关停。作者项目被关停意味着所有用户的功能同时失效——这是一种把产品价值绑定在个人带宽上的结构性风险。

1.3 本地优先的重新定义

Omni Suite 将设计目标确立为本地优先而非本地可选。这个措辞差异是刻意的:本地可选意味着云端为主、本地为辅,断网即降级;本地优先意味着核心能力完全在本地闭环,云端至多承担分发与遥测这类非关键职责。

这个选择带来的直接后果是:产品必须在浏览器的能力边界内解决问题,而不能把困难的部分推给服务器。具体来说,图像特征提取必须在浏览器内完成深度学习推理,简历关键词匹配必须在前端实现分词与边界分析,视频转封装必须在页面内完成解密与封装。这三个约束在后续章节会分别展开。


第二章 架构总览

2.1 分层结构

Omni Suite 的技术架构分为四层,自下而上依次为基础设施层、引擎层、数据层与应用层。

基础设施层由浏览器原生能力构成,包括 Shadow DOM 隔离、Offscreen Document、WebAssembly、Web Crypto、WebRTC DataChannel、IndexedDB 等。这一层的特点是零依赖成本,全部由浏览器提供,但也受制于浏览器的实现现状。

引擎层是六款扩展各自的核心能力实现,包括图像嗅探与特征抽取引擎、关键词边界分析引擎、多平台适配层、本地推理运行时、游戏沙箱管理器、音视频转封装器。这一层是产品差异化的集中体现。

数据层负责本地持久化,采用 chrome.storage.local 与 IndexedDB 双轨制。选择双轨而非单一存储的理由是:前者适合存储配置项与小体积结构化数据,API 简单且同步读取可用;后者适合存储大体积二进制数据与向量,支持事务与索引查询。ResumeRadar 的简历文本与 OmniSense 的语义记忆向量分别落在这两条轨道上。

应用层是用户直接接触的界面,包括浏览器侧边栏、网页内注入的悬浮控件、以及独立弹出窗口。这一层的共同约束是必须与宿主页面样式隔离,且要适配不同的显示形态。

2.2 权限最小化原则

扩展申请的每一项权限都会在安装时呈现给用户,也会在应用商店的审核中被审视。六款扩展的权限申请遵循同一原则:只申请实现功能所必需的,不申请任何"将来可能用到"的权限。

具体而言,OmniPic 需要在所有网站上读取图片资源,这是其核心功能所必需的;ResumeRadar 需要读取当前页面文本,用于提取职位描述;OmniPost 复用浏览器已有的登录态,因此不需要申请任何凭据存储权限——这恰恰是它最重要的安全设计(详见第四章第三节)。

这种克制带来了双重收益。用户侧的信任成本降低,因为看到的权限列表与理解的功能完全对应。审核侧的通过率提高,因为扩展没有可疑的广泛权限申请。

2.3 六款扩展的能力矩阵

Omni Suite 各扩展在能力维度上并非均匀分布。下表按六个维度对六款产品做了客观标注,用以说明每一款产品的定位差异。

维度 OmniPic ResumeRadar OmniPost OmniSense OmniGame OmniVideo
端侧本地运行 完整 完整 完整 完整 完整 完整
零数据上云 完整 完整 完整 完整 完整 完整
商店渠道上架 完整 完整 部分 无 无 无
侧边栏工作台 完整 完整 完整 完整 完整 部分
内置 AI 能力 完整 完整 部分 完整 无 无
可自建本地服务 无 无 无 无 无 完整

从矩阵可以读出两个结论。其一,六款产品在"端侧本地运行"与"零数据上云"两个维度上完全一致,这是套件的共同底线。其二,其余四个维度呈现明显的差异化分布,说明每款产品的重心不同:OmniPic 与 ResumeRadar 已完成商店上架,OmniVideo 独有自建服务能力,OmniSense 与 OmniPic 侧重内置 AI 能力。

这个分布不是功能缺失的结果,而是产品演进的自然轨迹:优先把可独立成型的能力做深,再考虑分发渠道。


第三章 基础设施层的浏览器能力

3.1 Shadow DOM 样式隔离

网页内注入的扩展控件面临一个固有难题:宿主页面的样式规则会作用于扩展的 DOM 节点,而扩展的样式同样可能泄漏到宿主页面。传统做法是给所有元素加上冗长的类名前缀,但这既不彻底也不可维护——宿主页面可能使用元素选择器或属性选择器,扩展无法穷举防御。

Omni Series 采取的方案是 Shadow DOM 隔离。ResumeRadar 的悬浮雷达控件被封装在一个附着模式的 Shadow Root 内,其中拥有独立的样式作用域。宿主页面的样式不会穿透进来,扩展的样式也不会泄漏出去。

// ResumeRadar 中悬浮控件的宿主节点创建
const host = document.createElement('div');
host.id = 'resume-radar-host';
host.style.cssText = 'position:fixed;z-index:2147483647;top:0;left:0;';

// 关键:attachShadow 开启样式隔离
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = templateHtml;

// shadow.appendChild 而非 host.appendChild
document.body.appendChild(host);

Shadow DOM 的隔离是双向的,但需要注意的是,它并非完全的物理隔离。宿主页面依然可以通过 JavaScript 访问 Shadow Root(当 mode 为 open 时),因此隔离针对的是样式而非脚本访问权限。这一区别在设计扩展时必须清楚。

3.2 Offscreen Document 与执行环境逃离

Manifest V3 的 service worker 是短生命周期执行环境,它没有 DOM,无法操作 canvas,也无法触发下载。这些限制对于需要图像处理与文件生成的功能构成了实质性障碍。

OmniPic 与 OmniVideo 的解决方案是 Offscreen Document:通过 chrome.offscreen API 在一个隐藏的文档环境中获得完整的 DOM 与 Canvas 能力。扩展在需要时创建该环境,处理完毕后销毁。

这一架构的关键在于执行环境的分离。service worker 负责调度与状态管理,offscreen document 负责实际的图像计算与文件生成,二者通过消息传递协作。特征抽取在 Web Worker 中进行以避免阻塞,文件生成在 offscreen document 中完成以获得完整 API。

3.3 WebAssembly 与端侧推理

端侧人工智能的实现基础是 WebAssembly。OmniPic 的特征抽取模型经过权重剪枝与网络拓扑重构后,体积压缩至约十六兆字节;OmniSense 采用 WebLLM 与 ONNX Runtime 两条推理路径,前者面向生成式任务,后者面向向量检索任务。

WebAssembly 在浏览器中的价值在于它提供了接近原生的执行性能,同时保持完全本地的运行特性。ONNX Runtime Web 的一个额外优势是支持 WebGPU 后端——在支持的设备上,矩阵运算可以下沉到 GPU,推理速度相比纯 CPU 路径有数量级提升。

需要如实说明的是性能边界。生成式模型对设备有明确要求:官方建议八吉字节以上内存并支持 WebGPU。弱设备上,OmniSense 会主动降级为仅向量检索模式,划词改写与毒舌点评功能提示不可用。这个降级是诚实的边界声明,而非故障。

3.4 Web Crypto 与本地加解密

Web Crypto API 提供了一套原生加密原语,包括 SubtleCrypto 接口下的 AES-GCM 与 AES-CBC 实现,以及基于椭圆曲线的非对称运算。OmniVideo 用它处理 HLS 流中常见的 AES-128 加密切片。

使用原生 API 而非引入第三方加密库有两个考虑。其一是供应链安全:加密实现是最不应该引入外部依赖的地方,因为一个被篡改的加密库可以静默地泄露所有密钥。其二是性能:原生实现通常有底层密码学库加速。

// AES-128-CBC 解密(HLS 标准切片加密方式)
async function decryptSegment(keyBytes, ivBytes, dataBytes) {
  const key = await crypto.subtle.importKey(
    'raw', keyBytes, { name: 'AES-CBC' }, false, ['decrypt']
  );
  return crypto.subtle.decrypt(
    { name: 'AES-CBC', iv: ivBytes }, key, dataBytes
  );
}

需要注意的是,HLS 流的密钥获取过程本身可能涉及网络请求。OmniVideo 的处理方式是:密钥请求在扩展上下文中发起,且仅请求解密所必需的最小范围,不做任何额外的数据获取。

3.5 WebRTC DataChannel 与无服务器联机

OmniGame 的联机功能基于 WebRTC DataChannel,实现了一个完整的无服务器对等连接方案。用户在同一局域网内可以通过自动发现机制找到附近的对战者,或输入六位房间密钥跨网络直连。

WebRTC 的价值在于它提供的是浏览器原生的对等连接能力,不需要任何中转服务器。这一点与产品理念高度一致——如果对战数据必须经过服务器,那么"零外部网络请求"的承诺就不成立。

实现上存在两个需要注意的技术点。其一是信令服务器的必要性:WebRTC 建立连接前需要交换会话描述,理论上这需要一个信令通道;OmniGame 通过局域网内的自动发现广播绕过了这一环节。其二是 DataChannel 的可靠传输与开销:对于游戏这类高频小消息场景,需要权衡可靠传输与延迟,通常选择非可靠传输换取更低延迟。

3.6 IndexedDB 与本地向量存储

ResumeRadar 的简历文本与匹配历史、OmniSense 的语义记忆向量、OmniPic 的图像特征向量,都保存在 IndexedDB 中。

选择 IndexedDB 而非 localStorage 的理由是容量与结构化能力。localStorage 通常限制在五兆字节左右且是同步接口,大数据量下会阻塞主线程;IndexedDB 提供异步接口与事务支持,容量可达数百兆字节,并且支持通过索引进行高效查询。

向量检索的实现需要在本地完成相似度计算。常见的做法是把候选集全部读入内存后暴力计算余弦相似度,这在候选集规模达到数千条时仍可接受(毫秒级完成)。规模更大时则需要引入近似最近邻索引结构,如 HNSW 或 IVF。这类结构能在召回率略有损失的前提下把检索复杂度从线性降到对数级。


第四章 引擎层的领域算法

第四章第一节 图像嗅探与画质还原

OmniPic 的核心能力是整站图片采集,其难点不在于找到图片,而在于找到原始画质的图片。

问题的本质。 现代网页几乎都使用内容分发网络提供图片,运营方会在 URL 中附加裁剪、缩放、压缩参数来节省带宽。例如一个请求路径可能包含宽度三千、但不传就会返回原图;另一个路径可能包含质量参数八十、而不传就是原图。这些参数是 CDN 层面的"降质指令",网页本身未必知道。

解法是多信号融合。 OmniPic 的做法是同时采集多个维度的信号并做交叉验证:

  • 尺寸信号:元素的渲染尺寸与自然尺寸的比例。渲染尺寸远小于自然尺寸,说明这是缩略图。
  • 链接模式:URL 路径中的尺寸参数片段。识别出参数名与模式后,可以尝试移除该参数以获取原图。
  • 源点集合:<picture> 元素的 srcset 属性、srcset 与 src 的差异、图片的 data-src 类属性。
  • 链接文本与 alt:作为关键词分类的依据,同时排除明显的图标与占位图。

为什么要穿透 Shadow DOM。 现代前端框架大量使用 Shadow DOM 封装自定义元素,普通的 querySelectorAll 无法穿透。OmniPic 采用递归遍历的方式进入所有开放的 Shadow Root,这个过程必须小心处理边界(同一节点可能被多个路径引用导致重复采集)与开销控制。

关于懒加载的自动滚屏。 图片站点普遍采用懒加载,不滚动就不会触发加载。OmniPic 的自动滚屏机制是:模拟人类滚动行为,每次滚动后检查是否有新图片被加载,直到连续若干次滚动无新内容出现才停止。这里的关键设计是"停止条件"——无限滚动会触发站点的反爬机制,而过早停止则会漏图。

去重的两个层次。 精确去重依赖文件内容的哈希(MD5 之类),只能识别完全相同的文件。但视觉上相同的图片可能有不同尺寸或格式。OmniPic 为此实现了感知哈希:先把图像缩放到统一的低分辨率灰度尺寸,计算二维离散余弦变换,取低频系数构造指纹。两张视觉内容相同的图片,即便分辨率不同,其 DCT 低频系数也会高度接近。

这种方法的局限也要说明:感知哈希对镜像翻转不敏感(指纹几乎相同),对大幅裁剪则不敏感但可能被误判为不同。阈值设置需要权衡——过低会误删不同图片,过高会漏删重复图片。默认阈值取在经验值附近,并允许用户调整。

第四章第二节 词边界分析与技术别名映射

ResumeRadar 解决的是一个看似简单但极易出错的问题:判断简历中是否出现了某个技术关键词。

朴素匹配的失效。 最直接的实现是子串查找,但它会产生大量误报。搜索 "Go" 会命中 "Google"、"going"、"algorithm";搜索 "C" 会命中几乎任何含该字母的词;搜索 "AI" 会命中 "said"、"chair"、"maintain"。这些误报会让匹配评分失真到毫无参考价值。

词边界是必要的但不够。 加上边界判断可以解决大部分问题,但短关键词仍有隐患。考虑 "K8s" 这个关键词——它是 Kubernetes 的常见简称,但用户可能写成 "k8s"、"K8S" 或 "K-8s",纯边界匹配会漏掉部分写法。

解法是分层匹配 + 别名归一。 ResumeRadar 的做法分三层:

第一层是严格的词边界匹配。对纯字母关键词,要求前后不是字母字符;对含符号的关键词(如 C++、.NET),单独处理符号边界。这一层保证 "Go" 不会命中 "Google","C" 不会命中 "said"。

第二层是别名映射表。把技术栈的各种等价表达归一到同一个规范形式:

规范形式 常见别名
Kubernetes K8s、k8s、K8S
TypeScript TS、ts、Typescript
持续集成 CI、CI/CD、Continuous Integration
JavaScript JS、js、Ecmascript
PostgreSQL PG、pgsql、Postgres

别名表的价值不只是"多认几种写法"。它防止了双向误判:简历写了 "K8s",JD 要求 "Kubernetes",如果不做归一,两者会被判定为不匹配,而实际上完全等价。

第三层是语境加权。同一个词在不同语境下权重不同。例如 "Java" 在职位名称中出现是强信号,在 "JavaScript" 中出现则不应单独计数;"R" 在 "R语言" 中是强信号,在单词中出现的概率则极低。

中文分词的额外挑战。 中文没有空格分隔,需要词典与统计模型共同决定边界。浏览器内置的 Segmenter 接口提供了基于国际统一字符集的分词能力,但它对技术术语的处理取决于其内置词典的覆盖度。ResumeRadar 的做法是把技术术语表作为自定义词典前置输入,使专业词汇不会被切碎。

第四章第三节 多平台适配与凭据零接触

OmniPost 的核心设计原则是:不接触用户凭据。

问题的背景。 自动化发布工具通常要求用户提供平台的账号密码或 API 密钥。这带来两个风险:一是凭据泄露,二是凭据被平台判定为异常来源(同一 IP 大量登录不同账号)。

解法是复用浏览器已有的会话状态。 扩展在用户已登录的标签页上下文中执行发布操作,直接使用该标签页的 Cookie。这个机制的特点是:

  • 扩展不读取、不存储、不传输任何凭据
  • 请求从用户本机浏览器直接发出,来源 IP 是用户自己的
  • 平台看到的登录态与用户手动操作完全一致

实现层面的两个难点。 其一是各平台的内容编辑器结构完全不同,统一的富文本注入方案不可能覆盖所有平台,因此必须为每个平台单独实现适配层,在页面中定位编辑区、注入内容、触发相应的框架事件。这是适配层的核心工作,也是最易随平台改版而失效的部分。

其二是框架的事件系统。主流 SPA 框架(Vue、React 等)不会因为直接修改 DOM 而触发状态更新。正确的做法是同时派发原生输入事件,让框架的响应式系统感知到变化:

function nativeSetValue(element, value) {
  // 绕过框架的受控组件:直接改 DOM 后派发原生事件
  const setter = Object.getOwnPropertyDescriptor(
    window.HTMLTextAreaElement.prototype, 'value'
  ).set;
  setter.call(element, value);
  element.dispatchEvent(new Event('input', { bubbles: true }));
  element.dispatchEvent(new Event('change', { bubbles: true }));
}

关于风控的诚实立场。 自动化发布无法把平台风控风险降为零。OmniPost 的做法是三点:平台间强制不少于三秒的行为限速并加入随机停顿;复用合法登录态而不注入伪造身份;遇到验证码或超时时立即把该标签页切到前台交还人工接管,绝不把失败包装成成功。

最后一点是设计原则而非技术选择。把失败呈现为成功会破坏用户对工具的信任——用户可能因此投递了未发布的内容而不自知。这种错误一次就足以让产品失去全部价值。

第四章第四节 端侧推理与语义记忆

OmniSense 把大语言模型装进浏览器,其价值主张是消除 API 密钥、消除 token 账单、断网可用。

双推理路径的设计。 生成式任务(改写、点评、提炼)走 WebLLM 路径,基于 WebGPU 后端;向量检索任务(语义记忆召回)走 Transformers.js 加 ONNX Runtime 路径,可在纯 CPU 上运行。这个划分不是冗余,而是对硬件异构性的适配——向量检索在弱设备上仍可用,生成式任务则明确提示不可用。

语义记忆的工作方式。 浏览内容时在本地抽取向量并存储;查询时把查询语句同样转为向量,取余弦相似度最高的若干条。关键设计在于 384 维向量的选择——它比 768 维或更高维节省约一半存储空间,而在这个任务上召回损失可以接受。

性能预期的诚实表述。 首次使用需要从本地加载模型权重,通常耗时数秒到数十秒。这不是网络下载,而是磁盘读取与反序列化,但用户的主观感受仍然是"慢"。文档中明确写出这一点,比让用户在等待中困惑要好。

第四章第五节 沙箱化游戏运行时

OmniGame 内置一百六十余款游戏(含十六款自研原创),采用 iframe 沙箱隔离加载。这个选择解决了三个问题:游戏代码之间的相互污染、与宿主页面的样式冲突、以及崩溃时的隔离。

iframe 沙箱的粒度。 每个游戏运行在独立的 iframe 中,通过 postMessage 与宿主通信。沙箱属性的设置需要谨慎:allow-scripts 是必需的,否则游戏无法运行;allow-same-origin 则需要权衡——它会让 iframe 内的脚本能访问父页面的同源存储,取消它则可能破坏某些游戏的内部逻辑。

联机同步的时序问题。 对等连接建立后,两端的状态同步采用"权威端 + 状态广播"模型。联机代码中采用确定性的伪随机数生成器加固定时间步长的推进模型,保证两端在不同时刻推进也能得到相同结果。这是确定性锁步同步的核心:只要初始状态一致、每步输入相同,两端就能保持一致。

这种同步方式的代价需要说明。 锁步同步要求两端都能按时推进。如果某一端网络卡顿或设备性能不足,两端会产生分歧。OmniGame 的处理方式是检测到分歧后强制同步一次完整状态,但这会造成可见的跳变。在低延迟局域网环境下(双方延迟均低于五毫秒),这个问题几乎不会出现。

第四章第六节 内存内音视频转封装

OmniVideo 的技术挑战是把分片的媒体流合并为标准可播放的文件,且全程在浏览器内完成。

分片流的拼接。 HLS 协议将媒体切分为大量小片段,播放时顺序加载。M3U8 清单文件描述了这些片段的地址与时长,其中加密流还包含密钥地址。OmniVideo 的处理流程是:解析清单、多线程并发拉取片段、解密、在内存中按时间戳排序、重新封装为 MP4。

并发与限流的权衡。 并发拉取能显著提升总耗时,但并发过高会触发服务器限流。实现上采用可配置的并发数(默认一到八),并对失败片段做指数退避重试。这个折中的点是经验性的:不同的 CDN 有不同的容忍度,没有普适的最优值。

音视频分离的处理。 部分平台(尤其是国内视频平台)使用分离的音视频轨,即视频和音频是两个独立的流。合并需要理解两者的时间戳对应关系,然后分别封装再复用。OmniVideo 提供两种结果:合并成完整的 MP4,或抽取纯音频轨道。前者便于直接播放,后者便于在音频场景使用。

内存使用的控制。 高分辨率视频片段在内存中累积会占用可观空间。实现上采取分段处理策略:按播放顺序逐批处理并及时释放已封装的中间数据,而不是一次性把所有片段读入内存。这个细节决定了长视频是否会触发浏览器的标签页内存限制。


第五章 官网的前端工程实现

官网作为产品文档与信任载体,其前端实现本身就是六款扩展技术理念的延伸——静态优先、渐进增强、无追踪。

5.1 技术选型与理由

官网采用 Next.js 十六的静态导出模式,配合 React 十九、Tailwind CSS 四、Framer Motion 十二与 Lenis 平滑滚动库。

选择静态优先而非服务端渲染的理由与扩展一致:官网的内容更新频率极低(日均不足一次),服务端渲染带来的收益不足以抵消其运维成本。静态生成后由内容分发网络分发,访问延迟低且无可被攻击的服务端。

构建配置上有一处关键决策:图片优化被显式关闭。

// next.config.ts
const nextConfig: NextConfig = {
  reactStrictMode: true,
  images: {
    unoptimized: true,
  },
};

原因是产品截图必须原样呈现。截图的核心价值在于展示真实界面,任何压缩或裁剪都会削弱其信息量。同时,六款扩展的界面以深色为主,压缩伪影在暗色背景上尤其明显。

5.2 设计令牌体系

视觉系统建立在单一的颜色契约上。核心是三组变量:

基底色。深黑作为页面底色,米白作为文字色,灰色系列承载次级信息。这个组合形成高对比度的暗色界面,投影时对比不因环境光而降低。

强调色。单一的高饱和青色作为唯一的视觉重音,只用于需要用户注意的元素——链接、焦点状态、数据高亮。这个克制是有意的:多色强调会导致注意力分散,而产品界面中有大量数据需要被扫读。

语义令牌与原始令牌解耦。 组件只引用语义名(如文字主色、卡片底色),不直接引用具体色值。这使得换肤或调整对比度时只需修改令牌层,组件代码不动。

字体同样令牌化。显示字体承担标题,正文等宽字体承担数据与标签。等宽字体用于数据的做法有其道理:数字在等宽字体下对齐,表格与指标不会被抖动的数字宽度干扰。

5.3 动效系统

官网的动效遵循一个明确原则:动效服务于层级关系,不服务于装饰。

所有入场动画共用一条缓动曲线,这是整个站点"像一套系统"的技术来源。

// src/lib/motion.ts
export const EASE = [0.16, 1, 0.3, 1] as const;

export const fadeUp = {
  hidden: { opacity: 0, y: 26 },
  show: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.9, ease: EASE },
  },
};

export const viewportOnce = { once: true, amount: 0.2, margin: '0px 0px -80px' };

这条曲线是指数衰减型:前段响应快、尾段平滑收敛。如果每个组件用不同的曲线,页面会显得由多个来源拼凑而成。

滚动触发的时机同样重要。触发点设在元素进入视口百分之二十的位置,并把判定边界上移八十像素——这意味着元素在完全可见之前就开始动效,用户感知到的是"元素在我看向它时就已经在动",而不是"元素突然出现然后才动"。

减少动效的适配是强制项而非可选项。 平滑滚动与入场动画都检测 prefers-reduced-motion 媒体查询:

const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduced) return;   // 完全不初始化 Lenis,回归原生滚动

前庭功能障碍用户会因大面积视差运动产生眩晕。这不是边缘情况,动画敏感人群的比例远高于预期。

平滑滚动的实现细节。 Lenis 采用指数衰减函数驱动滚动惯性:

export const LENIS_EASING = (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t));

这个函数在零点处的导数较大,之后迅速趋于平缓,产生"跟手但不生硬"的惯性感受。触摸设备上刻意关闭了平滑接管(syncTouch 设为 false),因为劫持原生触摸滚动在移动端会破坏系统级的滚动体验。

5.4 社区讨论模块

官网集成了基于 GitHub Discussions 的评论系统,技术选型上有几个值得说明的决定。

为什么选 Discussions 而不是第三方评论服务。 讨论数据托管在代码托管平台上意味着:它与代码版本控制同属一个权限体系(无需额外的第三方账户),它是永久公开的(第三方服务可能停止运营),它可以被搜索引擎索引(第三方服务通常有反爬)。

未配置时的降级是产品级的。 评论模块在检测到配置缺失时,不会渲染一个空白区域,而是显示一份三步配置指引与所需的环境变量清单:

const isConfigured = Boolean(repo && repoId && categoryId);

这个设计的价值在于:任何访问官网的开发者都能看到"评论系统已就绪,缺什么"以及"怎么补齐"。相比之下,一个空白评论区会让人误以为该功能不存在。

隐私参数的取舍。 评论系统关闭了元数据上报与访客身份标识:

emitMetadata="0"

关闭元数据意味着评论不会在 GitHub 上留下访问者与官网的关联记录。这是一个明确的隐私取舍——代价是无法统计评论转化率,收益是访客不需要担心被追踪。

5.5 交互式价值计算器

官网包含一个生产率价值计算器,是全站唯一的实时计算组件。它的实现体现了内容诚实的技术原则。

计算逻辑本身很简单:年节省工时等于每周工时乘以五十二周再乘以自动化替代率。

const annualHoursSaved = useMemo(() => {
  const h = parseFloat(hoursPerWeek) || 0;
  // 基于六款插件的端侧能力实测:自动化替代约 65% 的重复浏览器操作
  return Math.round(h * 52 * 0.65);
}, [hoursPerWeek]);

值得注意的是百分之六十五这个系数。它是实测估算值而非行业通用值,页面上也标注了数据来源说明。同时它被显式暴露为可调整的输入项而非隐藏的假设——用户可以质疑并调整这个数字,而不是被动接受一个看起来很精确但来源不明的结论。

云端 API 费用的对照项被设计为一个开关而非隐藏成本:

const tokenMultiplier = includeCloudTokens ? 1.25 : 1.0;

开启后计算本地方案时额外计入云端方案的成本溢价。这个设计的意图不是贬低云端产品,而是让用户在对比时把迁移成本纳入考量——本地方案的"零成本"优势,一部分来自省去了这些费用。

5.6 社区优先的内容策略

官网页面的信息架构遵循一个原则:先建立信任,再展示功能。

首屏之后的页面顺序是:宣言(四条不可妥协的工程原则)、能力矩阵(跨产品对比)、交互式价值计算、媒体展示、社区讨论、安装指引、常见问题。这个顺序的理由是:潜在用户在决定是否安装一个要读取"所有网站数据"的扩展前,首先关心的是这个扩展是否可信。宣言与能力矩阵回答这个问题,安装指引与常见问题排在后面,是因为只有相信了的人才会关心怎么装。


第六章 质量保障体系

6.1 三层测试结构

六款扩展的质量保障采用三层结构,每一层捕获不同类型的缺陷。

第一层:逻辑单元测试。 运行在模拟 DOM 环境中,直接调用模块导出的纯函数与状态操作。这一层的价值在于快速反馈——数百项断言在数秒内跑完。覆盖重点是算法正确性:分词边界、画质判定、相似度计算、Elo 积分。

第二层:真实浏览器端到端测试。 启动自动化浏览器实例,通过开发者协议注入真实事件(点击、键盘、移动),然后读取真实的状态对象与渲染结果。这一层捕获的是单元测试无法触及的问题:坐标映射错误、事件时序、Canvas 渲染、布局尺寸计算。

第三层:截图视觉审查。 在关键节点截图并人工审阅。这一层看起来最原始,但它是唯一能发现某些缺陷的方式——例如内容渲染在画布左上角的一个小区域内、或者整体构图失衡。

6.2 三层测试的分工实证

在实践中,三层测试的分工非常明确。以一次具体缺陷为例:某款游戏在锁定光标后出现了完全错误的显示——画面只占据左上角一小块,其余区域全黑。前两层测试全部通过,因为:逻辑测试验证的是状态机转换,而状态是正确的;端到端测试验证了元素存在性与像素非空,而左上角确实有像素。

只有截图审查能发现这个问题。 根因是画布的位图尺寸与坐标系变换不匹配——位图按设备像素比放大了,但绘制坐标系未同步缩放,导致整个画面被压缩到左上角。这个缺陷的隐蔽之处在于:它不会导致任何功能性错误,程序"看起来正常",只有视觉上不对。

另一个层次的分工体现为隐私边界验证。要验证"零数据上云"这一承诺,最可靠的方式不是阅读代码,而是打开浏览器的网络面板,执行扩展的全部操作,确认没有任何指向非本机域名的请求。代码审查可能遗漏某个第三方 SDK 的自动初始化,而网络面板不会。

6.3 断言设计的陷阱

在建立测试体系的过程中,有一个反复出现的陷阱值得记录:断言会自我欺骗。

在重构某个功能后,若未复查旧断言的前提条件,就会出现"测试通过但功能已坏"或"测试失败但功能没坏"的情况。实际遇到的案例包括:注释文档描述的阈值与代码中的计算公式不一致;新设计中某个操作从"立即生效"改为"延迟生效"而断言未同步;测试传入的参数意外触发了不同的代码分支。

这些问题的共同特征是:断言本身仍然在验证它当初想验证的东西,但那个东西的语义已经变了。解决方式是在每次重构后重新确认每条断言的意图,而不是机械地修改代码使其通过。

另一个陷阱是计分倒挂。在设计一个"切得越准分越高"的评分函数时,如果实现中"切得越碎"的得分反而更高(因为切得越多累积分越多),那么测试用"合理的切分得分应高于豆腐切分"这样的断言就能立刻暴露问题。这个案例说明:严格比较的断言比宽松比较的断言更有价值。

6.4 跨浏览器兼容策略

六款扩展面向 Chrome、Edge、Firefox 三个内核。跨浏览器的主要差异点在于:

能力 Chromium Gecko
侧边栏 Side Panel API 侧边栏(不同实现)
后台执行 Service Worker(短生命周期) 事件页(较稳定)
画布位图 Offscreen Document 不支持,需替代方案
浏览器存储 chrome.storage browser.storage

兼容策略的核心是能力检测而非版本检测。代码在运行时判断目标能力是否存在,而不是根据浏览器版本号走分支。这比版本检测更健壮,因为能力往往先于版本文档可用。


第七章 已知局限与未来方向

7.1 当前的技术局限

诚实记录局限比罗列优势更有价值。

OmniPic 的网页覆盖边界。 严格遵循通用信号意味着不做逐站定制。其后果是:做了严格反爬的站点(需要登录态、需要令牌、需要人机验证)无法处理,因为绕过反爬既不合法也不可靠;对使用 CSS 背景图而非图片标签的页面,需要额外的提取逻辑;对内容安全策略严格限制资源加载的页面,可能无法完成网络层拦截。

OmniPost 的平台适配脆弱性。 各平台的编辑器结构会随前端改版而变化。适配层本质上是与目标平台的私有实现对话,这种对话必然是脆弱的。这个局限无法通过工程手段消除,只能通过快速跟进更新来管理。

OmniSense 的硬件要求。 生成式模型对设备有明确门槛。八吉字节内存与 WebGPU 支持是官方建议。弱设备上的降级是诚实的边界声明,但意味着这部分用户在划词改写与毒舌点评上得不到任何价值——他们只能使用向量检索与广告净化。

OmniGame 的联机范围。 无服务器的对等连接在局域网内体验良好,跨网络场景依赖房间密钥交换。局域网的自动发现基于广播,这在部分网络环境下可能不可用(企业网络的广播抑制)。

7.2 未做的技术选择

记录未采用的方案及其理由,往往比记录已采用的方案更有信息量。

没有做云端同步。 尽管云端同步是常见需求,但引入它意味着重新引入隐私与成本问题。历史数据的可移植性通过导出功能解决,而不是通过中心化服务器。

没有做跨设备协同。 六款扩展都是单设备工具。协同需求存在,但协同的前提是信任,而信任的前提是数据归属清晰——这与本地优先存在张力。

没有引入遥测分析。 收集使用数据对产品迭代有巨大价值,但它与"零数据上云"直接冲突。这个冲突的解决方式不是妥协,而是接受迭代速度的损失。

没有做移动端。 浏览器扩展的移动端形态是另一个独立的产品形态,不应作为桌面扩展的附属功能。

7.3 后续的技术方向

更强的端侧模型。 随着浏览器推理能力的提升,端侧能承载的模型复杂度会增加。视觉特征抽取可以从当前的通用模型走向针对特定领域的轻量模型,语义匹配可以从词频走向句向量。

更完整的隐私验证。 当前"零数据上云"依赖架构设计与人工审查。可以考虑引入自动化验证手段:在构建流程中注入网络层拦截断言,任何指向非本机域名的请求直接导致构建失败。这把隐私承诺从文档变成了 CI 流程的一部分。

扩展能力的统一抽象。 六款扩展在权限管理、本地存储、网络拦截、界面注入上存在共性。虽然它们不应合并为一个扩展(单一扩展的权限清单会成为用户决策的障碍),但底层工具层可以共享。


结语

Omni Suite 的技术选择可以归结为一句话:把能在浏览器里做完的事情做完,不把能省的服务器成本省掉,而把省不掉的部分明确告知用户。

本地优先不是对云端的道德批判,而是一个架构判断:在浏览器扩展这个特定执行环境中,本地执行在隐私、成本、可用性三个维度上同时占优,因此没有理由不选它。

这个选择也有代价——设备要求更高、平台适配需要持续投入、无法用服务端算力换取体验提升。这些代价是真实的,产品文档中应当如实呈现,而不是用"零成本"的宣传语掩盖。

最终的判断标准很简单:当用户打开浏览器的网络面板,使用产品的全部功能,他应该看不到任何指向非本机域名的请求。 这条标准可以自我验证,不需要信任任何人的承诺。


附录 A:技术栈清单

层次 技术选型 用途
扩展 Manifest V3 扩展清单与权限声明
扩展 TypeScript 5 扩展与官网的语言基础
扩展 Shadow DOM 样式双向隔离
扩展 Offscreen Document DOM 与 Canvas 能力获取
扩展 WebAssembly / ONNX 端侧推理
扩展 Web Crypto 本地加解密
扩展 WebRTC DataChannel 无服务器对等连接
扩展 IndexedDB 大容量本地持久化
官网 Next.js 16 静态站点生成
官网 React 19 界面框架
官网 Tailwind CSS 4 原子化样式系统
官网 Framer Motion 12 动效编排
官网 Lenis 惯性滚动
官网 Giscus 社区讨论
官网 Lucide React 图标系统

附录 B:六款扩展的定位速查

产品 领域 核心算法 降级策略
OmniPic 媒体采集 多信号画质还原、感知哈希去重、特征向量检索 无法处理的站点明确说明不强行解析
ResumeRadar 职场效率 分层词边界匹配、技术别名归一、语境加权 中文分词能力缺失时降级为子串匹配并提示误报风险
OmniPost 创作者工具 平台适配层、行为限速、失败回执 验证码与超时降级为人工接管,不伪装成功
OmniSense 人工智能 端侧推理、双路径运行时、本地向量检索 弱设备降级为仅向量检索
OmniGame 休闲娱乐 确定性锁步同步、iframe 沙箱、Elo 积分 无联机时支持本地双人
OmniVideo 媒体处理 并发分片拉取、内存转封装、音画合流 高分辨率视频提示内存限制

附录 C:验证清单

用户可通过以下方式自行验证产品的核心承诺,而非依赖声明:

验证零数据上云。 打开浏览器开发者工具的网络面板,执行产品的全部功能,确认无指向非本机域名的请求。这是比任何隐私声明都可靠的验证方式。

验证本地优先。 断开网络连接后使用产品全部功能。若一切正常,则确认无云端依赖。

验证权限范围。 在扩展详情页查看权限清单,对照功能说明。每一项权限都应能对应到某个具体功能。

验证失败诚实性。 触发一个失败场景(如平台验证码、网络超时),确认产品如实报告失败并保留人工接管入口,而非显示成功。

posted @ 2026-10-07 11:28  SuperEden  阅读(4)  评论(1)    收藏  举报