perf(web-ui): FlowChat 的 ::selection/::highlight() 规则使 WebKit 走慢速 CSS 匹配,渲染主线程持续满载
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 64/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- css
- Domain
- frontend, performance, web-dev
Research direction
Start by inspecting the generated rules in frontend/dist, especially assets/index-Dg5Vb1KW.css and assets/ChatPane-BtnTdl19.css, then trace them back to the FlowChat source that produces them. Reproduce the issue on Linux/WebKitGTK and sample WebKitWebProcess as described. Done means the costly selection/highlight matching is reduced without losing the intended behavior, with the reported stack and rendering performance rechecked.
Written by the indexing model from the issue text.
Description
FlowChat 的 ::selection / ::highlight() 规则导致渲染主线程持续满载(Linux / WebKitGTK)
可直接提交到 https://github.com/GCWing/OpenBitFun/issues
标题建议:perf(web-ui): FlowChat 的 ::selection/::highlight() 规则使 WebKit 走慢速 CSS 匹配,渲染主线程持续满载
环境
| 项 | 值 |
|---|---|
| OpenBitFun | 1.0.1(RPM 安装,open-bit-fun-1.0.1-1.x86_64) |
| 系统 | Fedora 44 / KDE Plasma / Wayland |
| GPU | Intel Arc B390(Panther Lake)+ Mesa(gallium 26.x) |
| WebKit | 系统 webkit2gtk4.1-2.52.5-1.fc44(非 AppImage 内置版) |
| 现象会话 | 单会话较大(约 9 MB,含数条 100–380 KB 的工具输出) |
现象
- 打开聊天界面后,渲染进程(
WebKitWebProcess)的主线程长期占用 44% ~ 99% 单核(实测峰值 105% 整体),UI 明显卡顿、输入延迟。 - 卡顿程度与会话长度 / 屏幕上同时渲染的文本量成正比 —— 越聊越卡;流式输出("思考中")时最重。
- 即使不操作(仅窗口在重绘 / 焦点变化触发状态刷新),占用仍有 core ~25% + renderer 21%~50%;同期
ai.log无增长(即没有任何模型调用、没有后台代理在跑)。 - 与 GPU 无关:合成器线程仅占 ~6%,
SkiaGPUWorker正常,无llvmpipe/swrast软光栅。
根因(gdb 栈证据)
对渲染进程主线程反复采样,8 次采样中 6 次落在同一条路径:
#0-3 WebCore::Style::ElementRuleCollector::collectMatchingRulesForListSlow(...)
#4 WebCore::Style::Resolver::styleForPseudoElement(...)
#5 WebCore::RenderElement::getUncachedPseudoStyle(...)
#6 WebCore::MarkedText::collectForHighlights(WebCore::RenderText const&, ...)
另有样本落在:
#0 WebCore::CSS::mix(...) ← 每次重算 color-mix()
#1 WebCore::Style::BuilderCustom::applyValueColor(...)
#2-4 WebCore::Style::Builder::applyProperty / applyCascadeProperty
即:每个文本框、每次重绘,WebKit 都要未缓存地解析高亮伪元素样式,并因此对整套样式表做线性(慢速)规则匹配,还会重复求值 color-mix()。
触发这些规则的 CSS(assets/index-Dg5Vb1KW.css 33 条、assets/ChatPane-BtnTdl19.css 10 条等,共 46 条):
[data-flowchat-selection-root]::selection,
[data-flowchat-selection-root] ::selection, /* ← 后代组合符 */
[data-openbitfun-product-component=conversation-excerpt]::selection,
[data-openbitfun-product-component=conversation-excerpt] ::selection {
background: color-mix(in oklab, var(--…-accent) …);
}
::highlight(openbitfun-flowchat-excerpt) { background-color: color-mix(…); }
::highlight(openbitfun-flowchat-annotations) { background-color: color-mix(…); }
::highlight(openbitfun-flowchat-search-match) { … }
::highlight(openbitfun-flowchat-search-current) { … }
问题点:
X ::selection(带后代组合符)无法被 WebKit 的规则分桶优化,只能落到collectMatchingRulesForListSlow。该样式表总规模约 2.5 MB / ≈14 482 条规则(含 778 处:is()/:where()/:not()),于是"文本框数量 × 规则总量"的线性扫描在每次重绘发生。color-mix()在这些规则里每解析一次就要重新求值(栈中单独出现在WebCore::CSS::mix)。::highlight(...)同理:只要应用注册了高亮范围(摘录/批注/搜索命中),每次重绘都会为每个文本框解析其伪元素样式。
实验验证(同一条件、8 个栈样本/组)
| 变体 | collectForHighlights |
getUncachedPseudoStyle |
collectMatchingRulesForListSlow |
|---|---|---|---|
| 原版 | 6 | 6 | 14 |
| 仅拦掉高亮注册(JS,注册点置为 no-op) | 6 | 6 | 16 |
↑ + bind mount 覆盖 frontend/dist |
4 | 4 | 13 |
剥离那 46 条 ::selection/::highlight() 规则 |
0 | 0 | 4(−71%) |
剥离方式:按 CSS 规则(花括号配对)解析后丢弃选择器含 ::selection 或 ::highlight( 的规则(校验:花括号平衡、目标规则清零)。补丁落在
~/.config/openbitfun/data/frontend-workbench/revisions/<activeRevision>/assets/*.css(该副本即实际被加载的前端)。
代价:选中文本回落到系统默认底色,摘录/批注/搜索命中的高亮底色不再显示。
建议修法
- 不要对
::selection/::highlight()使用后代组合符选择器(A ::selection)。改为在需要定制选区的元素上加一个类,用单个复合选择器(X::selection),或直接避免定制::selection。 - 这些规则里不要用
color-mix():换成预计算的静态颜色或 CSS 变量(var(--x-selection-bg)),避免每次解析都求值。 - 把这些规则移出大样式表:慢速匹配的代价与规则总量成正比,放进一个独立的小样式表(或按需注入)可显著降低成本。
- 可选:没有选区/高亮时不要保留活跃的 marked range,避免
MarkedText::collectForHighlights每帧都有活干。
复现步骤
- 在 Linux(WebKitGTK)上启动 OpenBitFun 1.0.1,打开一个内容较多的会话;
R=$(pgrep -f WebKitWebProcess | ...)找到渲染进程,运行
gdb -p $R -batch -ex "bt 8"连续采样若干次;- 多数样本会落在
MarkedText::collectForHighlights → getUncachedPseudoStyle → collectMatchingRulesForListSlow; - 按上文剥离那 46 条规则并重启,栈中该路径消失、主线程占用明显下降。
English summary
On Linux/WebKitGTK, OpenBitFun 1.0.1's web process main thread stays pegged (44–99% of one core) whenever the FlowChat pane is open; the lag scales with transcript size. gdb sampling of the web process main thread repeatedly shows:
MarkedText::collectForHighlights
→ RenderElement::getUncachedPseudoStyle
→ Style::Resolver::styleForPseudoElement
→ ElementRuleCollector::collectMatchingRulesForListSlow
The trigger is 46 ::selection / ::highlight() rules in the bundled stylesheets (e.g. [data-flowchat-selection-root] ::selection, ::highlight(openbitfun-flowchat-excerpt)). Their descendant-combinator selectors cannot be bucketed, so WebKit falls back to a linear scan of the whole (~2.5 MB / ~14.5k rules) stylesheet for every text box on every paint, and color-mix() is re-evaluated each time. Stripping those 46 rules drops the hotspot from 6/8 to 0/8 samples and cuts slow rule matching by ~71%.
Suggested fix: avoid descendant combinators on ::selection/::highlight(), avoid color-mix() in them, and keep them in a small dedicated stylesheet.
- Dominant language
- Rust
- Stars
- 2.3k
- Forks
- 236
- Avg merge
- 3h 7m
- Merged PRs (30d)
- 596
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from GCWing/OpenBitFun
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
GCWing/OpenBitFun#2363 ·
-
question
Difficulty 1/5 Under an hour Newbie friendliness 78/100
GCWing/OpenBitFun#2340 ·
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
GCWing/OpenBitFun#3179 · 1 reaction ·
-
Difficulty 4/5 3-5 days Newbie friendliness 65/100
GCWing/OpenBitFun#3165 · 1 comment ·
-
[Bug]: 小应用无卸载功能 Open
Difficulty 3/5 1-2 days Newbie friendliness 55/100
GCWing/OpenBitFun#3158 ·
All issues in GCWing/OpenBitFun
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
gitbutlerapp/gitbutler#15998 · 1 comment ·
-
bug triage:deciding
Difficulty 1/5 Under an hour Newbie friendliness 88/100
open-telemetry/otel-arrow#4132 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100