写了一个 Chrome 插件,选中网页上的 Java 代码自动解释

写了一个 Chrome 插件,选中网页上的 Java 代码自动解释

为什么要做

工作中经常要看代码覆盖率报告,里面嵌着大量 Java 代码。作为测试,需要理解这些代码的逻辑才能判断覆盖率够不够、哪些分支没测到。之前的做法是复制代码,切到 AI 对话窗口,贴进去问一遍——一天要重复几十次,来回切窗口很烦。

想做一个 Chrome 插件:在覆盖率报告页面(或任意网页)上直接选中一段 Java 代码,旁边弹一个浮窗显示解释,不用离开当前页面。

技术栈

Chrome 插件(Manifest V3)、原生 JavaScript(不用框架)、Kimi API(Moonshot 的大模型接口)。整个项目 1400 行代码,5 个文件:

  • manifest.json —— 插件配置
  • content.js —— 约 1100 行,注入到网页里的脚本,负责代码检测、弹窗 UI、结果展示
  • background.js —— 约 100 行,Service Worker,负责调 API
  • options.html + options.js —— 设置页,填 API Key 和模型名

怎么识别选中的是 Java 代码

用户在网页上选中一段文本后,content.js 监听 mouseupselectionchange 事件。拿到选中文本后,先清洗(去掉 markdown 代码块标记、去掉前导注释行),然后用 isLikelyJava() 做语言判断。

判断逻辑是多因子打分。维护了 17 个正则模式,每个代表一个 Java 语言特征:

  • 关键词:publicprivatestaticvoidinterfaceimplementsextends
  • 结构:class Xxxfor (while (if (try {
  • 语法:分号、泛型(List<...>
  • API:System.out.println

选中文本至少匹配 2 个特征且长度超过 6 个字符才算 Java 代码。这种方式不需要语法树解析,速度快,误判率也可接受——Python 和 JavaScript 很少同时有 public 和分号。

弹窗怎么做的

识别到 Java 代码后,在页面右上角弹一个浮窗。浮窗用 Shadow DOM 实现——attachShadow({ mode: "open" })——CSS 和 DOM 完全隔离,不会被宿主页面的样式污染,也不会影响宿主页面。

浮窗结构:顶部标题栏(显示"已识别 N 行 / M 字符"),左侧是历史记录列表(最近 8 条,存在 Chrome 的本地存储 API 里),右侧是解释结果区域。结果按四个 tab 展示:核心作用、语法要点、风险提示、测试要点。每个 tab 有对应颜色的标签(蓝/紫/橙/绿)。

样式用了毛玻璃效果(backdrop-filter: blur)、渐变背景、z-index: 2147483647(最大值,确保浮窗永远在最上层)。支持暗色模式——@media (prefers-color-scheme: dark) 里写了 60 多行暗色适配。响应式布局在 860px 断点处从左右结构切换为上下结构。

还做了一个简单的 Java 语法高亮——关键词蓝色、字符串粉色、数字琥珀色,背景深色。

API 调用和消息传递

Chrome Manifest V3 的限制:content script 不能直接调外部 API,必须通过 background service worker 中转。

流程是:content.js 通过 chrome.runtime.sendMessage() 把代码发给 background.js,background.js 调 Kimi API(api.moonshot.cn/v1/chat/completions),拿到结果后回传给 content.js。

提示词要求模型返回严格的 JSON 格式,不要用 markdown 包裹。JSON 结构是一个 sections 数组,每个 section 包含 tag(核心作用/语法要点/风险提示/测试要点)、titledescription(支持 **加粗**)和 codeExample(Java 代码示例)。

温度设 0.2(尽量确定性输出),超时 30 秒(用 AbortController)。点击"解释代码"按钮后有 5 秒冷却期,防止重复请求。

响应解析做了两级 fallback:先尝试 JSON.parse,失败了再用正则匹配 【标题】内容 这种旧格式。因为有些模型不一定严格遵守 JSON 要求。

设置页

options 页面只有两个输入框:API Key(password 类型)和模型名(默认 moonshot-v1-8k)。数据存在 chrome.storage.sync 里,跨设备同步。

踩的坑

CSS 冲突。 最初浮窗直接用 document.createElement 插到页面 DOM 里,结果在 GitHub 上看的时候,GitHub 的 CSS reset 把浮窗的字体、间距全覆盖了。改成 Shadow DOM 后完全隔离,但代价是不能用宿主页面的工具库(比如 jQuery)。

选中文本的丢失。 浮窗弹出来之后,用户点击浮窗上的按钮,浏览器会取消页面上的文本选中状态。结果代码消失了——因为 content.js 是通过 window.getSelection() 拿代码的。解决办法是在识别到 Java 代码的瞬间就把文本缓存起来,不依赖选中状态。

Service Worker 生命周期。 Manifest V3 的 background script 是 Service Worker,浏览器会在空闲时休眠它。如果 API 请求在 Service Worker 休眠后才返回,回调就丢了。用 AbortController + 30 秒超时兜底,确保请求不会悬挂。但偶尔还是会遇到 Service Worker 刚被唤醒时第一次请求特别慢的情况。

模型不返回 JSON。 提示词明确要求"输出严格的 JSON,不要包裹 Markdown 代码块",但 Kimi 有时还是会返回 \``json ... ``` 包裹的格式。解析时先检测并去掉代码块标记再 parse。另外有些模型(非 Kimi)返回的是中文括号格式(【核心作用】解释内容`),所以保留了旧格式的正则解析作为 fallback。

历史记录的存储限制。 Chrome 的本地存储 API 有 5MB 上限。代码片段加上解释结果,每条大约 2-3KB。限制了最多保存 8 条历史记录,超过的按 FIFO 淘汰。

现在的样子

1400 行原生 JavaScript,零依赖(不用 React、不用 Webpack),Manifest V3。能在任意网页上选中 Java 代码触发解释,解释结果按四个维度分 tab 展示,支持历史记录、暗色模式、响应式布局。

用下来最大的感受是 Chrome 插件的 Manifest V3 限制确实比 V2 多——Service Worker 替代了常驻的 background page、content script 不能直接调 API、存储 API 异步化。但好处是权限模型更干净,只申请了 activeTabstorage 两个权限,用户安装时不会看到一堆吓人的权限请求。

posted @ 2026-04-16 18:37  难删亦删  阅读(14)  评论(0)    收藏  举报