Skip to content

某些特殊情况下,白色主题会出现黑色背景 - #512

Draft
kekxv wants to merge 2 commits into
xintaofei:mainfrom
kekxv:main
Draft

某些特殊情况下,白色主题会出现黑色背景#512
kekxv wants to merge 2 commits into
xintaofei:mainfrom
kekxv:main

Conversation

@kekxv

@kekxv kekxv commented Aug 18, 2026

Copy link
Copy Markdown
image

@kekxv

kekxv commented Aug 18, 2026

Copy link
Copy Markdown
Author

fix: #363

@xintaofei

Copy link
Copy Markdown
Owner

感谢 PR,也感谢你在 #363 里做的那份排查 —— 问题是真实存在的,而且你贴的 DOM 数据其实已经把根因指到门口了 🙌

我照着复现验证了一遍,结论是:这个问题值得修,但这版补丁暂时还不能合 —— 它会给「开启工作区背景图」的用户带来一个必现的黑块回归,同时没有触到真正的根因。下面把验证过程写清楚,方便你调整。

一、根因不在 CSS,在传给 xterm 的那个颜色字符串

terminal-view.tsxresolveBackgroundColor() 会把祖先元素的 computed background-color 原样交给 xterm 当主题背景色。本仓库是 Tailwind v4,--background: oklch(1 0 0),浏览器 getComputedStyle().backgroundColor 返回的就是字符串 "oklch(1 0 0)"

而 xterm 6 的 css.toColor() 只用正则认两种写法:#rgb/#rgba/#rrggbb/#rrggbbaa,以及逗号式 rgb()/rgba()。其余一律走 canvas 兜底(ctx.fillStyle = 渐变; ctx.fillStyle = 颜色;getImageData 读像素),这条兜底有两个失败口:canvas 不认该语法时抛错;alpha !== 255 时也抛错。

ThemeService 这边则是:

function v(e, t) { if (e !== undefined) try { return css.toColor(e) } catch {} return t }
// ...
t.background = v(e.background, /* fallback */ css.toColor("#000000"))

解析失败 → 静默回落 #000000 这正是你在 #363 里看到的 background-color: rgb(0, 0, 0)

我用真实的 xterm 6.0.0 + headless Chrome 实测:

"oklch(1 0 0)"        ->  oklch(1 0 0)    (现代 Chromium 的 canvas 能兜住)
"oklch(1 0 0 / 0.3)"  ->  rgb(0, 0, 0)    ← 静默变黑(alpha ≠ 255)
"notAColor(1 2 3)"    ->  rgb(0, 0, 0)    ← 静默变黑

所以「某些特殊情况」= WebView 的 canvas 不认 oklch。WebKit 的 CSS 层支持 oklch 早于 canvas 层支持,中间那段版本窗口就会出现「界面全对、只有终端全黑」。这跟你在 #363 里说的「Chrome 正常、手机也正常,就 Safari / PC 不正常」完全对得上;「不是完全的暗色,看不清」也对得上 —— LIGHT_THEME 里其它颜色都是 hex、解析都正常,只有 background(以及被 getTerminalTheme 设成同一个字符串的 cursorAccent)解析失败变黑,于是就成了「亮色主题的深色文字画在纯黑底上」。

二、这版补丁的三个问题

1)开启背景图时终端会变全黑(阻塞项)

xterm 6 的实际 DOM 是这样的(两者是兄弟,不是嵌套):

.xterm
├── .xterm-viewport            position:absolute; inset:0; background:#000   ← xterm.css 硬编码
└── .xterm-scrollable-element  position:relative; 背景由 xterm 写内联 style
    └── .xterm-screen

.xterm-viewport 是一块铺满整个终端的黑色底板。这版补丁把「让它透明」那条规则删掉了,同时又把 .xterm-scrollable-element 强制 transparent !important —— 两层叠起来就是纯黑。实测(<html data-workspace-bg="on"> + 本 PR 的两条规则 + 真实 xterm):

.xterm-viewport            computed = rgb(0, 0, 0)      ← 铺满整个终端区域
.xterm-scrollable-element  computed = rgba(0, 0, 0, 0)  ← 全透

也就是说,所有开了工作区背景图的用户,终端会从磨砂玻璃变回黑块 —— 正是被删掉那条规则当初要解决的问题。这个回归跟 WebView 版本无关、必现,影响面比它想修的 bug 还大一些。

2):not([data-workspace-bg="on"]) 实际上没有起到门控作用

这个属性打在 <html> 上,而 .xterm 有一长串祖先。后代组合器只要任意一个祖先不带该属性就算匹配,所以开着背景图时第一条规则照样命中(实测 el.matches(':not([data-workspace-bg="on"]) .xterm .xterm-scrollable-element') === true)。

两条规则的特异性都是 (0,3,0):not() 取参数的特异性)、又都带 !important,最后是靠书写顺序分胜负的。现在看着对属于巧合 —— 换个顺序、挪个文件、包进 @layer,背景图那条就会静默失效。真要门控得写成 html:not([data-workspace-bg="on"])

3)只盖住了底色,xterm 内部的 colors.background 还是黑的

!important 压内联样式这个判断是对的,但 xterm 自己算出来的 colors.background 仍然是 #000000,而它还会往外派生颜色:

  • 反显 ESC[7m:xterm 生成的是 .xterm-fg-INVERTED_DEFAULT_COLOR { color: color.opaque(background) } → 亮色主题下画成「黑字 + #1a1a1a 底」,基本看不见;
  • cursorAccentgetTerminalTheme 把它设成了同一个字符串)同样回落到黑 → 块状光标下的字符看不见。

另外硬编码 var(--background) 也架空了 resolveBackgroundColor() 逐级上溯匹配真实祖先底色的设计:今天 .ws-surface 恰好就是 --background,所以看不出来;哪天终端面板换成 ws-surface-muted / ws-surface-sidebar 就会静默错色。

三、建议的改法(CSS 可以完全不用动)

terminal-view.tsx 里,把 computed 值先归一成 #rrggbb 再交给 xterm,归一不了才退回主题自带的 hex 常量:

const raw = resolveBackgroundColor(container)
const background = raw ? toHexColor(raw) : null   // 复用 lib/custom-style.ts 的 toHexColor
if (!background) return baseTheme
return { ...baseTheme, background, cursorAccent: background }

有个小坑:现有的 toHexColor()src/lib/custom-style.ts:481)只回读 ctx.fillStyle 字符串,而现代 Chrome 对 oklch 会原样回读 "oklch(1 0 0)",所以目前会返回 null。需要补一步 fillRect(0, 0, 1, 1) + getImageData 把像素读成 #rrggbbalpha !== 255 直接判失败,避开预乘 alpha 失真)。

这样做的好处:

  • 修的是 colors.background 本身,反显和光标一起恢复正常;
  • 能转换的引擎上仍然精确跟随祖先面板底色 —— 不像一刀切退回常量:暗色下 --backgroundoklch(0.145 0 0)(≈ #0a0a0a),而 DARK_THEME.background#1a1a1a,一刀切会让终端和面板出现可见色差;
  • CSS 一行都不用改,零回归。

如果你还是想加一层 CSS 兜底,也完全可以,只是建议用加法:门控写成 html:not([data-workspace-bg="on"]),并且保留 [data-workspace-bg="on"] .xterm .xterm-viewport { background-color: transparent } 那条。再往前一步的话,其实可以把 .xterm .xterm-viewport 无条件设成透明 —— 本应用里终端底下永远有 .ws-surface 面板兜底,这样能一次性消掉「底板黑边 / 黑块」这一类问题(包括 fit 余量导致的底部黑条)。

顺带一提,格式方面没问题,prettier 是过的 👍

再次感谢你把 #363 排查得这么细 —— 那份 DOM dump 基本就是决定性证据。方便的话按上面的思路调整一版,我们很乐意继续 review 🙏

@kekxv
kekxv marked this pull request as draft August 18, 2026 12:27
@kekxv

kekxv commented Aug 18, 2026

Copy link
Copy Markdown
Author

@xintaofei 你们能复现就行,你们修复就可以了,感谢你们的贡献

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants