关于 integrated-browser
面向 VS Code 集成浏览器贡献者的架构技能,介绍 Chromium 页面、进程边界、状态管理与安全设计,并说明如何扩展浏览器行为。
主要功能
- 01通过 Electron WebContentsView 嵌入 Chromium
- 02说明主进程、共享进程和渲染进程职责
- 03基于 IPC 管理页面状态与操作
- 04区分会话、组和 CDP 隔离
- 05通过编辑器贡献模型扩展功能
适用场景
- 01修改代码前理解浏览器架构
- 02添加页面状态或操作
- 03检查页面共享与信任边界
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ vscode --skill integrated-browser安装后在 Claude Code 中输入/integrated-browser确认已加载。
详细介绍
集成浏览器架构
集成浏览器(“browserView”)通过 Electron WebContentsView 在 VS Code 中嵌入了一个真正的 Chromium 浏览器。它可渲染实时页面,将每个页面显示为一个编辑器标签页,并让 Agent 通过工具操控这些页面。它为产品内的浏览器标签页和 Agent“浏览器”工具提供支持。它不是旧版 extensions/simple-browser(一个嵌入在 webview 中的 iframe);现在在桌面端,它会将请求委托给这里的实现。
这是一个重量级、对安全性要求很高的多进程基础组件,几乎所有设计决策都由此而来。 本文件介绍那些很少变化的关键理念。这里有意不罗列当前的功能、工具、命令和设置——这些内容变化频繁;应以 features/ 和 tools/ 目录中的最新内容为准。先在这里建立心智模型,再去查看你要修改的具体代码。
一切设计所遵循的核心理念
页面是一个原生 WebContentsView,只能由主进程创建、拥有并定位。其他任何对象都不能直接操作它。 页面由主进程拥有,由操作系统合成器绘制在工作台 DOM 上方,而不是绘制在其中。其他一切都围绕这一点展开:
- 渲染进程(编辑器 UI 和 Agent 工具)无法持有页面;它持有模型/代理,并通过 IPC 与主进程通信。
- Playwright 依赖重且生命周期长,因此运行在共享进程中,也通过 IPC 访问页面。
- 页面会绘制在 DOM 上方,因此工作台必须手动协调对齐、z 顺序、焦点和屏幕截图。
三个进程,以及这样划分的原因
| 进程 | 位置 | 此处包含的内容 |
|---|---|---|
| 主进程 | platform/browserView/electron-main | WebContentsView、会话、信任、权限、历史记录、CDP、屏幕截图——权威的页面状态。只有主进程能创建原生视图。 |
| 共享进程 | platform/browserView/node | Playwright + 远程/分组自动化服务。将重量级依赖从主进程中移出(以保证稳定性),也从渲染进程中移出(以避免影响生命周期)。 |
| 渲染进程 | workbench/contrib/browserView + platform/browserView/electron-browser | 编辑器窗格、UI 功能扩展、Agent 工具、页面预加载脚本。只持有轻量代理。 |
分层规则: platform 不得导入 workbench;Agent 宿主不得导入 workbench;共享类型应放在 platform/common 中。渲染进程通过 ProxyChannel IPC 访问主进程/共享进程,绝不通过导入实现来访问。浏览器事件共用一个按宿主窗口作用域的即时订阅通道,用于接收所有事件;工作台会先应用初始快照,再将实时变更路由为模型本地事件。缓冲区作为元数据旁的元组/数组元素传递,而不是嵌套在 JSON 对象中。通道在 electron-main/app.ts 和 electron-utility/sharedProcess/sharedProcessMain.ts 中注册。划分如下:页面操作/状态 → 主进程;Agent 自动化 → 共享进程;CDP 连接管理使用独立通道,渲染进程和共享进程都通过它访问主进程。
渲染进程持有的是一面镜子,而非真相
渲染进程模型是主进程视图所拥有状态的只读副本。数据流是单向的:
renderer feature ──command──▶ model ──IPC──▶ main BrowserView ──▶ Chromium
▲ │
└──────────── event (state changed) ◀──────────┘
要执行操作,请调用方法(往返主进程);要响应状态变化,请监听模型事件。切勿在渲染进程中计算页面状态——它无法访问网页内容。
- 新状态(url/title/loading/zoom/…):在主进程的
BrowserView中将其设为权威状态,发出变更事件,然后在渲染进程模型中镜像该字段和事件,并将其添加到BrowserViewMainService的类型化转发映射中。必须在导航前安装转发;不要添加按浏览器分别订阅的 IPC。每个窗口订阅都会以当前快照作为起始状态,这定义了工作台就绪状态,无需单独的初始化 RPC。工作台拥有每个浏览器的事件发送器映射,将其传入模型,并按浏览器 ID 和事件名称直接分发;模型只负责接收事件。 - 新操作(navigate/reload/focus/…):在主进程中定义,并在渲染进程模型上公开一个精简的代理方法,通过通道往返调用。
如果你想让渲染进程从页面上“直接读取”某些内容,这就说明你需要添加新的镜像状态和来自主进程的事件。
原生视图浮在 DOM 上方(覆盖层问题)
这是最容易出错的领域,也是几乎所有“无法移动/错位/菜单透出/无法聚焦”问题的根源。页面由主进程以屏幕坐标绘制在渲染进程之上,因此工作台会模拟一个普通 DOM 元素。请将页面的矩形区域、可见性、焦点和图像视为工作台需要协调的内容,而非 DOM 的事实:
- 对齐。 编辑器会渲染一个空的 DOM 占位元素,测量其屏幕矩形区域,并将边界发送给主进程。CSS 缩放与屏幕像素并不一致,因此边界会对齐到像素。任何会移动页面的新布局,都必须参与这项边界计算,而不能只移动 DOM 盒子。
- 层级顺序。 原生视图绘制在所有工作台 UI 之上,因此本应显示在页面上方的菜单/弹出窗口/悬停提示/对话框都会被遮挡。工作台会检测重叠并隐藏原生视图,改为显示占位内容。任何覆盖在页面上的新浮动 UI 都必须能被这套机制检测到——较高的 CSS z-index 无济于事。
- 闪烁遮罩。 隐藏或重新定位期间,会用定时截图代替页面。截图也是页面图像传递到渲染进程/Agent 的唯一合理方式——没有人会读取原生像素。
- 焦点与键盘。 焦点通过显式桥接。一个预加载脚本(注入到每个页面的隔离世界中)负责决定页面保留哪些按键,以及将哪些按键转发给 VS Code 键绑定。请将其视为信任边界:假设页面具有恶意,保持脚本精简且无副作用。
浏览器标签页是真正的编辑器,并由扩展贡献功能
页面是一个普通编辑器——在标准注册表中,一个只读、可序列化的 EditorInput + EditorPane,并会惰性地解析为视图模型(无需关闭标签页即可卸载)。因此,它自然具备标签页、拆分、持久化、焦点和键绑定作用域等功能。
编辑器窗格本身很精简。行为通过本地贡献模型(与工作台贡献分离)添加:这些小型类会附加到编辑器、获取 DI,并接入一组固定的生命周期钩子(模型附加/分离、布局覆盖、调整大小/可见性、焦点、UI 插入)。每个贡献的生命周期都限定于附加的模型,因此每页的可释放对象会在导航/关闭时清理。即使是原生视图渲染,也只是一个贡献。
要添加行为,请参照同类贡献编写一个新贡献——不要扩展编辑器或主视图。 影响页面矩形区域的贡献通过带优先级的布局覆盖组合(优先级较低的先运行;例如,仿真功能先设置视口大小,像素对齐最后运行),因此顺序很重要——绝不要硬编码像素值。
身份与隔离:会话、组和 CDP
请明确区分以下三者:
- 会话 = 存储身份。 每个 Electron 会话都与一个
BrowserSession一对一映射(Cookie、缓存、存储),其 ID 同时用作 CDP 浏览器上下文 ID。作用范围可以是全局、按工作区或临时;多个标签页可以共用一个会话。安全控制在此实施:file://、证书信任和权限都由会话管控(例如,访问本地文件需要工作区信任)。扩展页面访问范围的新能力应归属于会话,而非某项功能。 - 组 = 自动化可见范围。 一个组会集成一组动态视图,并将其作为一个逻辑上的 CDP“浏览器”公开。组会引用视图,但不拥有视图;一个视图可以属于多个组。借此,不同客户端(聊天会话、DevTools、扩展)可以各自只看到所需的子集。
- CDP 通过代理转发,绝不直接暴露。 一个了解协议的代理负责实现浏览器级/目标级域(发现、自动附加、扁平化会话、上下文),并将其余请求按目标转发。这样,一个逻辑浏览器就能由随时加入或离开的视图组成,Playwright 也能在不直接接触 Chromium 的情况下连接。
Cookie/登录/存储 → 会话。“此客户端能看到哪些页面” → 组。协议本身 → 代理。
Agent 在隐私保护下与用户共用页面
- 共用同一页面,协同操作。 Playwright 通过 CDP 操作用户看到的同一个
WebContentsView,每个聊天会话使用一个连接。用户和 Agent 的操作可能发生冲突;冲突时以用户为准(用户触发的提示框/对话框可以中断自动化)。设备模拟由工作台(而非 Playwright)负责,因此除 Agent 执行操作期间外,Playwright 的自动模拟会被抑制。请假设用户可能同时与同一页面交互。 - 分享前,页面内容保持私密。 默认情况下,Agent 无法看到页面内容。页面的共享状态会控制内容访问;另有一个可用性门控(聊天是否启用、Agent 模式、设置)决定是否注册完整工具集——未满足时,仅提供一种有限能力:打开 URL,但无法访问内容。URL 会经过网络筛选器检查,被阻止时会进行屏蔽。应将页面内容视为不可信的模型输入(提示词注入)。任何新增的 Agent 界面都必须遵守这些门控。(工具名称位于
platform中,这样不依赖workbench的 Agent 主机也能引用这些名称。)
远程页面
浏览器上下文(及其存储)会绑定到一个网络:本地网络,或按权威来源确定的远程网络——窗口的远程网络(远程工作区窗口)或远程 Agent 主机(Agents 窗口会话)。标签页会保存其请求的会话(存储范围、亲和性和网络);创建时会应用当前信任和代理限制,但不会覆盖该请求。弹出窗口和自动化上下文会继承父级的网络。远程流量通过窗口本地扩展主机中的 HTTPS 代理传输;若代理启动失败,视图创建也会失败,而不会回退到本地网络。若已知某主机不支持代理,新标签页将使用本地网络,并显示警告。
网络属于浏览器上下文,而非其所有者、受众或策略。全局存储始终位于本地。工作区存储使用窗口的网络;其他网络则需要通过存储亲和性(Agent 主机加文件夹根目录)选择工作区存储文件夹中的一个分区,否则会使用不与其他视图共享的内存存储。清除工作区存储时也会清除这些分区。
测试策略
让每一层测试都精简并聚焦场景。 保护主要功能,避免其故障破坏核心浏览器场景,并采用仍能覆盖实际风险的最低成本测试层。不要在不同层重复测试同一行为,也不要将偶然的实现细节固化为测试。
- 单元测试覆盖重要的独立逻辑,例如状态转换、协议转换、持久化规则、安全门控和故障处理。使用有代表性的用例,不必穷举琐碎分支或私有结构。
- 组件测试覆盖主要的渲染器交互、命令、由上下文键控制的可见性,以及无需原生页面的核心渲染行为。除非结构本身就是契约,否则避免断言像素级细节或 DOM 结构。
- 扩展 API 测试位于
extensions/vscode-api-tests/src/singlefolder-tests/browser*.test.ts,是浏览器 API、CDP 行为、浏览器工具、扩展宿主连接,以及向扩展开放的跨进程契约的首选集成测试层。 - 其他端到端集成测试覆盖需要真实服务、但不需要完整工作台流程的重要进程边界或运行时集成。
- 冒烟测试仅覆盖那些有意义的故障模式需要实际 Electron 工作台、渲染器/主进程/共享进程连接,或原生
WebContentsView的主要用户流程。这包括预加载脚本键盘路由、原生焦点/可见性/生命周期、Electron 权限、弹出式编辑器、原生视图上的工作台 UI,以及实时页面到聊天的附件等核心风险。每项测试都应只覆盖主流程,将相关断言归为一个连贯的流程,并将变体、边缘情况和视觉细节留给较低层测试。
好的断言应体现用户契约,并在不破坏行为的更改和重构中保持稳定。测试应保护那些故障会实质性破坏主要浏览器场景的行为,并放在仍能覆盖该故障模式的最低成本层。只要保持连贯,冒烟测试覆盖应扩展现有流程;否则,主要流程最多使用少量场景。
实践指南
- 仅限桌面端。 Web 端不会运行任何内容;将代码添加到
electron-*/node,并让browser/存根抛出 "not available in web"。 - 遵循现有模式。 新增能力 → 新的功能贡献和/或工具,参照同类实现。跨进程状态 → 使用主进程中的模型方法 + 事件,而不是渲染器本地状态。布局更改 → 使用优先级覆盖,而不是像素值。
- 注意信任边界: 预加载脚本(恶意页面)、会话(存储/权限/文件访问)、Agent 门控(共享 + 可用性 + 网络过滤器)以及聊天附件(提示词注入)。