1. 项目背景
在持续时间很长、包含大量代码的 ChatGPT 对话中, 页面可能逐渐出现明显的性能问题。
典型表现包括:
- 打开历史长对话时卡住十几秒甚至几十秒;
- 页面已经显示部分内容,但浏览器长时间无法正常操作;
- 滚动、恢复历史位置和重新打开旧对话明显变慢;
- 代码块越多、越长,问题越严重;
- CPU 使用率较高,但内存仍有大量余量。
本项目的目标不是节省内存,而是利用现代浏览器的渲染机制, 尽可能减少离屏内容参与样式计算、布局和绘制。
核心目标: 在保留完整聊天 DOM 和内容的前提下, 让浏览器少做当前用户根本看不到的工作。
2. 原始网页为什么会慢
2.1 长对话不仅仅是文字
用户看到的几百行代码,在浏览器中往往不是一个简单文本节点。 经过语法高亮之后,其内部可能存在大量 DOM 节点。
<pre>
<code>
<span>const</span>
<span>value</span>
<span>=</span>
<span>...</span>
...
</code>
</pre>
一个代码量很大的长对话中, 浏览器可能需要维护成千上万个甚至更多页面元素。
2.2 下载数据并不是主要瓶颈
页面收到聊天内容以后,浏览器还必须完成大量工作,例如:
- Style Recalculation;
- Layout;
- Forced Style/Layout;
- 元素尺寸测量;
- 滚动位置恢复;
- 页面总高度计算;
- Paint;
- 代码块及其内部节点的布局。
性能记录表明,真正最昂贵的部分并不是油猴脚本自身的 JavaScript, 而是 ChatGPT 前端自己的长任务触发了极其昂贵的同步布局计算。
2.3 关键瓶颈:Forced Style/Layout
在早期测试中,一个关键长任务可以持续约 40 秒, 其中绝大多数时间都属于 Forced Style/Layout。
性能记录器还显示, Ultra 脚本自身的函数耗时通常只有个位数到十几毫秒。
因此可以得到一个非常重要的结论:
优化重点不应该是继续微调 JavaScript, 而应该减少浏览器需要真正参与布局的页面内容。
3. 优化思路的演进
flowchart TD
A[长对话严重卡顿]
B[最初隐藏旧消息]
C[HOT / WARM / COLD]
D[高内存保留完整 DOM]
E[性能记录器]
F[发现 Forced Style/Layout 才是核心瓶颈]
G[Turn content-visibility]
H[PRE content-visibility]
I[PRE intrinsic 参数实验]
J[稳定版 Ultra 3.0]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
3.1 早期:隐藏旧消息
最早的版本尝试将较旧消息直接设置为:
display: none;
后续又发展出 HOT / WARM / COLD 分层, 试图只保留最近消息参与渲染。
这类方法能够减少页面工作量, 但也会带来新的复杂度和风险:
- 页面搜索可能无法找到隐藏消息;
- 滚动高度突然改变;
- ChatGPT 自己可能依赖这些 DOM;
- 动态切换状态本身会产生布局变化;
- 页面升级后维护成本较高。
3.2 高内存策略
因为测试机器拥有充足内存, 后续优化方向调整为:
宁可保留完整 DOM 和代码内容在内存中, 也尽量减少 CPU、Style Recalculation 和 Layout。
因此后续版本逐渐停止主动销毁代码 DOM, 转向浏览器原生渲染跳过机制。
4. 性能记录器与关键发现
4.1 内置 PerformanceObserver
实验版本加入了浏览器性能记录器, 主要采集:
- Long Animation Frame;
- Long Task;
- Forced Style/Layout Duration;
- Hydration 时间窗口;
- 关键阻塞脚本;
- Ultra 自身函数耗时。
4.2 典型结果
某次测试中:
- 总记录时间约 51.5 秒;
- Hydration 约 44.8 秒;
- 关键单帧约 40.4 秒;
- Hydration Forced Style/Layout 约 39.9 秒;
- Stable 之后的 Forced Layout 只剩几十毫秒;
- Ultra 自身 JavaScript 约 9.6 毫秒。
这证明几乎所有主要性能损失都发生在 ChatGPT 页面 Hydration 阶段。
Stable 阶段的 Ultra JavaScript 并不是性能问题。
5. Turn 级 content-visibility
一个重要突破是给整条对话消息使用浏览器原生:
[data-testid^="conversation-turn-"] {
content-visibility: auto;
contain-intrinsic-size: auto 560px;
}
可以把它理解为:
如果某条消息离当前屏幕很远, 浏览器暂时不必完整渲染它, 只需要先保留一个大致尺寸。
这与直接使用 display:none 不同:
- 消息 DOM 仍然存在;
- 聊天内容没有被删除;
- 浏览器可以在需要时自动恢复真实渲染;
- 对 ChatGPT 页面逻辑的侵入更小。
6. 最大突破:代码块 PRE 级 content-visibility
后续实验发现,仅优化整条 Turn 还不够。
即使一个 Turn 已经需要参与页面布局, 它内部可能仍包含巨大代码块和大量语法高亮节点。
因此增加第二层优化:
[data-testid^="conversation-turn-"] pre {
content-visibility: auto;
contain-intrinsic-size: auto 320px;
}
结构可以理解为:
flowchart TD
T[Conversation Turn]
A[普通文字]
P1[PRE 代码块]
B[普通文字]
P2[PRE 代码块]
T --> A
T --> P1
T --> B
T --> P2
P1 --> C1[大量 code/span 节点]
P2 --> C2[大量 code/span 节点]
即使父 Turn 已经参与布局, 浏览器仍有机会跳过屏幕外 PRE 内部的大型 subtree。
6.1 A/B/C/D 实验
| Profile | Turn intrinsic | PRE CV | Hydration | Hydration Forced | 主阻塞帧 |
|---|---|---|---|---|---|
| A Baseline | 560px | OFF | 约 44.77s | 约 39.93s | 约 40.26s |
| B DeepCode | 560px | ON | 约 15.75s | 约 10.98s | 约 11.47s |
| C LargeTurn | 1200px | OFF | 约 44.73s | 约 40.02s | 约 40.52s |
| D Combined | 1200px | ON | 约 15.73s | 约 10.99s | 约 11.56s |
6.2 实验结论
结果呈现出非常清晰的模式:
PRE CV OFF → 约 40 秒级 Forced Layout
PRE CV ON → 约 11 秒级 Forced Layout
与此同时,将 Turn intrinsic 从 560px 改成 1200px 基本没有作用。
真正产生数量级提升的是 PRE 级 content-visibility。
7. Wrapper containment 实验
为了尝试进一步优化, 后续测试了代码块父容器和两级父容器的 content-visibility。
| Level | 策略 | Hydration | Hydration Forced |
|---|---|---|---|
| L1 | PRE Only | 约 15.99s | 约 10.93s |
| L2 | PRE + Direct Parent | 约 15.91s | 约 10.90s |
| L3 | PRE + Two-Level Wrapper | 约 16.30s | 约 11.06s |
L2 相对 L1 的差异仅几十毫秒, 完全可以视为运行噪声。
L3 甚至略慢。
因此:
Wrapper containment 没有值得普通用户承担的额外收益。
8. PRE intrinsic size 实验
接下来只改变一个变量:
contain-intrinsic-size: auto Xpx;
Turn 固定 560px, PRE content-visibility 始终开启。
| Profile | PRE intrinsic | Hydration | Hydration Forced | 主阻塞帧 | 主 Forced |
|---|---|---|---|---|---|
| P1 | 160px | 17.658s | 11.965s | 12.484s | 11.882s |
| P2 | 320px | 15.736s | 10.967s | 11.451s | 10.901s |
| P3 | 640px | 15.044s | 10.149s | 10.633s | 10.086s |
| P4 | 1200px | 14.632s | 9.779s | 10.243s | 9.716s |
8.1 观察到的趋势
160 → 320 明显改善
320 → 640 继续明显改善
640 → 1200 仍有改善,但边际收益减小
这说明 intrinsic size 并不是完全无关的占位参数。
一个合理解释是: 如果临时尺寸估计更接近大型代码块的真实高度, ChatGPT 在恢复滚动位置、测量元素和进行几何计算时, 浏览器需要进行的高度修正会更少。
不过因为该轮测试顺序恰好也是从小到大, 最终没有继续进行反向顺序复验。
因此正式版采用更稳妥的折中策略。
9. 最终面向普通用户的 Ultra 3.0
实验结束后,项目不再继续堆叠复杂功能, 而是将已经验证最有价值的部分压缩成一个非常简单的稳定版。
9.1 核心 CSS
[data-testid^="conversation-turn-"] {
content-visibility: auto;
contain-intrinsic-size: auto 560px;
}
[data-testid^="conversation-turn-"] pre {
content-visibility: auto;
contain-intrinsic-size: auto 640px;
}
正式版的性能优化本质上就是这两条规则。
9.2 为什么默认使用 640px
1200px 在单轮测试中最快, 但 640px 已经取得明显提升, 同时占位高度更温和, 更适合作为普通用户的默认值。
因此最终提供三档:
| 模式 | PRE intrinsic | 用途 |
|---|---|---|
| 兼容模式 | 320px | 更保守,优先兼容性 |
| 平衡模式 | 640px | 默认,适合大多数用户 |
| 速度模式 | 1200px | 适合超长代码对话 |
10. 为什么正式版反而非常简单
实验阶段曾经包含大量功能:
- HOT / WARM / COLD;
- 旧消息隐藏;
- 代码缓存;
- 流式长代码冻结;
- 代码代理;
- MutationObserver;
- SPA 路由处理;
- PerformanceObserver;
- Hydration 分析器;
- Wrapper containment;
- 实验 Profile 系统;
- 悬浮控制面板。
这些功能对于实验和定位问题非常有价值, 但普通用户长期使用并不需要。
实验最终证明:
真正产生主要性能收益的是浏览器原生 content-visibility, 尤其是 PRE 代码块这一层。
因此 Ultra 3.0 选择删除所有没有稳定额外收益的复杂组件。
11. 正式版不会做什么
Ultra 3.0 不会:
- 删除聊天记录;
- 删除旧消息 DOM;
- 把消息发送到第三方服务器;
- 读取账号密码;
- 修改 ChatGPT API 请求;
- 自动发送消息;
- 持续扫描整个页面;
- 持续监听大量 DOM 变化;
- 运行性能记录器;
- 强行隐藏大量历史消息;
- 修改模型生成的文本;
- 替换代码内容。
它的核心只是静态 CSS。
12. 为什么不继续使用 display:none 隐藏旧消息
直接隐藏旧消息虽然简单, 但会改变页面真实布局结构。
可能造成:
- 滚动高度变化;
- 历史位置恢复异常;
- 页面搜索失效;
- ChatGPT 内部逻辑无法访问对应节点;
- 动态显示和隐藏引发额外布局。
content-visibility 更温和:
DOM 保留,只把当前不需要立即完成的渲染工作推迟。
13. content-visibility 的直观理解
flowchart LR
A[超长页面]
B[当前视口]
C[附近内容]
D[很远的离屏消息]
E[离屏巨大 PRE]
A --> B
A --> C
A --> D
D --> E
B --> F[正常完整渲染]
C --> G[需要时渲染]
D --> H[可暂时跳过]
E --> I[继续单独跳过]
浏览器不需要立刻完整计算用户完全看不到的内容。
当用户真正滚动到附近时, 浏览器再恢复真实布局与绘制。
14. contain-intrinsic-size 的作用
如果浏览器暂时不渲染一个大型代码块, 页面仍然需要知道:
“这里大概应该占多高?”
这就是 contain-intrinsic-size 的作用。
它不是最大高度,也不会裁剪代码。
它只是内容尚未真正参与布局时使用的临时尺寸估计。
15. 性能优化过程中得到的主要工程结论
- 长对话真正的主要瓶颈不是 Ultra 自身 JavaScript, 而是 ChatGPT 页面触发的大量 Forced Style/Layout。
- Stable 阶段的 JavaScript 微优化收益极小。
- Turn 级 content-visibility 有价值。
- PRE 级 content-visibility 是整个实验中最重要的突破。
- Turn intrinsic 从 560px 增加到 1200px 基本没有额外收益。
- PRE intrinsic size 会影响性能, 过小的估算值明显不利。
- Wrapper containment 没有显示出值得保留的收益。
- 激进隐藏旧 DOM 对普通用户不值得承担其复杂度和兼容风险。
- 浏览器原生能力通常比复杂 JavaScript 虚拟化更稳健。
- 正式版本应该尽量简单。
16. 从约 40 秒到约 10 秒
整个实验最直观的结果可以概括为:
flowchart TD
A[原始主要 Forced Layout
约 40 秒]
B[Turn content-visibility]
C[PRE content-visibility]
D[调整 PRE intrinsic]
E[约 10 秒级]
A --> B
B --> C
C --> D
D --> E
其中真正最大的一步来自:
[data-testid^="conversation-turn-"] pre {
content-visibility: auto;
}
这说明代码块内部的大型 DOM subtree, 是超长代码对话中的关键渲染成本来源之一。
17. 对普通用户的使用建议
平衡模式
Turn intrinsic = 560px
PRE intrinsic = 640px
推荐绝大多数用户使用。
速度模式
Turn intrinsic = 560px
PRE intrinsic = 1200px
推荐超长代码对话使用。
兼容模式
Turn intrinsic = 560px
PRE intrinsic = 320px
如果感觉滚动定位不自然, 或未来 ChatGPT 页面更新后出现兼容问题, 可以使用这一档。
18. 可能出现的正常现象
当用户快速滚动到一个之前完全离屏的大型代码块时, 浏览器会从临时尺寸估计切换到真实尺寸。
如果两者差异较大, 页面可能出现轻微的滚动位置修正。
这是 content-visibility 与 intrinsic placeholder 工作方式的一部分, 并不意味着内容被删除。
19. 隐私与安全
最终稳定版:
- 不收集聊天内容;
- 不上传聊天数据;
- 不创建自己的远程网络连接;
- 不读取账号密码;
- 不修改模型请求;
- 不修改回答文本;
- 不包含遥测系统。
用户配置只保存在用户脚本管理器的本地存储中。
20. 兼容性与维护
脚本依赖 ChatGPT 当前页面中类似以下属性:
data-testid="conversation-turn-..."
如果未来 ChatGPT 对页面结构进行大规模重构, 对应选择器可能需要更新。
由于稳定版本身非常简单, 即使选择器失效, 通常也只意味着优化不再生效, 而不会破坏聊天数据。
21. 项目方法论
这次优化过程中最重要的经验并不是某一条具体 CSS, 而是一套性能优化方法:
- 先测量,不凭感觉猜瓶颈;
- 区分脚本自身耗时和宿主页面耗时;
- 找到真正占据几十秒的主路径;
- 一次实验只改变少量变量;
- 对有效方案做重复测试;
- 对无效方案及时停止投入;
- 实验版本可以复杂,生产版本应该简单;
- 优先使用浏览器原生机制,而不是模拟浏览器行为。
22. 最终架构
flowchart TD
A[Tampermonkey document-start]
B[注入静态 CSS]
C[Turn content-visibility:auto]
D[PRE content-visibility:auto]
E[浏览器自动决定离屏内容是否跳过]
F[用户滚动到附近]
G[恢复真实布局和绘制]
A --> B
B --> C
B --> D
C --> E
D --> E
E --> F
F --> G
最终版本无需:
MutationObserver
PerformanceObserver
DOM virtualization
旧消息销毁
代码缓存系统
动态 wrapper
复杂状态机
持续页面扫描
23. 最终核心代码
[data-testid^="conversation-turn-"] {
content-visibility: auto;
contain-intrinsic-size: auto 560px;
}
[data-testid^="conversation-turn-"] pre {
content-visibility: auto;
contain-intrinsic-size: auto 640px;
}
整个项目的大量实验, 最终收敛到了这两条核心规则。
24. 一句话解释给普通用户
ChatGPT Long Chat Ultra 不会删除旧消息, 而是让浏览器在你看不到它们的时候, 少做一些没有必要立刻完成的渲染工作。
25. 项目最终结论
对超长、代码密集型 ChatGPT 对话而言, 性能问题很大一部分来自浏览器为大量离屏内容进行同步样式与布局计算。
使用浏览器原生的
content-visibility:auto
对整条消息进行第一层优化,
再对
<pre>
代码块进行第二层优化,
可以显著降低这种成本。
在本项目使用的超长代码对话测试中, 主要 Forced Style/Layout 从约 40 秒级下降到了约 10 秒级。
最终面向普通用户的版本因此选择:
- 保留完整 DOM;
- 不主动删除历史内容;
- 不持续执行复杂 JavaScript;
- 不进行激进虚拟化;
- 只使用经过实验筛选后的核心浏览器原生优化。
最终的工程价值不在于代码有多复杂, 而在于找到真正昂贵的那一步, 然后用最小的改动避开它。