Skip to content

fix(web): 修复 Shadow DOM 隔离渲染下自定义 HTML 深浅色失效的问题 - #5890

Merged
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
olwater:upstream-pr/html-dark-mode-fix
Jul 6, 2026
Merged

fix(web): 修复 Shadow DOM 隔离渲染下自定义 HTML 深浅色失效的问题#5890
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
olwater:upstream-pr/html-dark-mode-fix

Conversation

@olwater

@olwater olwater commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Important

请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

核心修复:深浅色模式适配
修复了自定义 HTML 页面在 Shadow DOM 隔离渲染模式下,无法随系统自动切换深浅色主题的问题。
在 Shadow DOM 内部增加了一层包装容器(wrapper),并通过 MutationObserver 实时监听外部主文档(document.documentElement)的 class 变化,将 dark 类名动态同步至该包装容器上。这使得被克隆进 Shadow DOM 的 Tailwind 深色模式 CSS 规则能够重新匹配生效。

🔍 问题背景与根因排查
关于自定义页面的渲染逻辑,最近经历了数次变更,导致了样式连环失效。经过在本地的详细排查与测试,还原了完整的原因:

  1. 最初的初衷 (fix(web): 修复自定义 HTML 样式被过滤及排版间距异常的问题 #5795): 最初的考量是,这几个自定义页面完全由管理员在后台控制,本着“充分信任管理员”的原则,应当赋予他们最大的配置自由度。当时采用的直接渲染模式能让自定义 HTML 无缝继承主应用的排版与深浅色变量,表现完美。
  2. 样式隔离的副作用 (Commit 1bff599): 出于防止恶意代码影响全局的考虑,核心团队将渲染方案重写为了 Shadow DOM 隔离渲染 并严格限制了 DOMPurify 规则。虽然杜绝了外部污染,但 Shadow DOM 的“绝对隔离”特性使得系统原本的 Tailwind 样式彻底进不去自定义内容中,导致了“样式白板”现象。
  3. fix(web): inject app styles into isolated HTML #5860 修复的局限性: 随后合并的 fix(web): inject app styles into isolated HTML #5860 试图通过把全局的 <style> 标签克隆进 Shadow DOM 来恢复排版。但遗漏了 Tailwind 深浅色生效的核心机制(依赖祖先元素上的 .dark 类名)。由于 Shadow DOM 内部无法感知外部标签的 class,这些深色 CSS 永远无法命中触发,导致深浅色切换失效。
    本 PR 补齐了这“最后一块拼图”。

🤔 关于技术路线的架构探讨
虽然本 PR 已彻底修复当前 Shadow DOM 下的深浅色问题,但借此想向核心团队提出一个产品层面的抉择探讨:
采用目前的 Shadow DOM + 强过滤 机制,意味着管理员在后台添加的任何包含 <script> 的代码都必定无法执行。这将导致以下常见场景永远无法在自定义页面中实现:

  • 📈 嵌入百度统计 / Google Analytics 等分析代码
  • 💬 挂载 Crisp / Intercom 等客服悬浮窗插件
  • 🐦 嵌入 Twitter 动态组件或第三方表单

如果团队认为安全性高于一切,本 PR 提供了完善的深浅色修复,可直接合并(预期内隔离)。
如果团队认为应将选择权交还给管理员(毕竟是最高权限后台配置),建议后续考虑退回到 #5795 最初的逻辑,允许管理员自行对其配置的外部代码安全负责。抛砖引玉,供各位定夺。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 Issues 与 PRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

  • 已在本地手动验证:在自定义主页配置 HTML 后,多次切换系统深浅色主题,Shadow DOM 内部样式随之流畅切换,无闪烁。
  • 检查 bun run typecheckbunx oxlint 均通过无异常。

Summary by CodeRabbit

  • New Features
    • Isolated HTML content now stays in sync with the app’s dark mode setting.
    • Embedded content is wrapped more consistently, improving theme handling during updates.
  • Bug Fixes
    • Fixed isolated HTML rendering so the dark class updates automatically when the page theme changes.

Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd7f963b-1bc1-4540-9b21-de7154234783

📥 Commits

Reviewing files that changed from the base of the PR and between b6e8ff9 and bd3f654.

📒 Files selected for processing (1)
  • web/default/src/components/html-content.tsx

Walkthrough

Adds dark-mode class synchronization to isolated HTML rendering. A new syncDarkClass helper toggles a dark class on a wrapper element based on the document root's class list. IsolatedHtmlContent now wraps rendered HTML in a dedicated div, applies initial sync, and uses a MutationObserver to keep it updated, disconnecting on cleanup.

Changes

Dark-mode Sync for Isolated HTML Content

Layer / File(s) Summary
Dark class sync helper and shadow DOM wiring
web/default/src/components/html-content.tsx
Adds syncDarkClass helper that toggles a dark class based on document.documentElement's class list; reworks IsolatedHtmlContent to render content into a wrapper div, apply initial dark-class sync, observe document.documentElement class changes via MutationObserver to re-sync, and disconnect the observer during cleanup.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
    participant DocumentElement
    participant MutationObserver
    participant IsolatedHtmlContent
    participant Wrapper

    IsolatedHtmlContent->>Wrapper: create wrapper div
    IsolatedHtmlContent->>Wrapper: syncDarkClass(wrapper)
    IsolatedHtmlContent->>Wrapper: set innerHTML to props.html
    IsolatedHtmlContent->>MutationObserver: observe DocumentElement class attribute
    DocumentElement-->>MutationObserver: class attribute changed
    MutationObserver->>Wrapper: syncDarkClass(wrapper)
    IsolatedHtmlContent->>MutationObserver: disconnect on cleanup
Loading

Poem

A rabbit hops through shadow DOM light,
Toggling "dark" class, day or night. 🌙
Watching the root with a careful eye,
Syncing colors as classes fly.
Clean up done, no leaks in sight—
Hop, hop, hooray, the theme feels right! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了 Shadow DOM 隔离渲染下自定义 HTML 深浅色失效的修复。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Calcium-Ion

Copy link
Copy Markdown
Member

script部分应该参考 https://www.newapi.ai/zh/docs/guide/wiki/basic-concepts/analytics-setup 设计

@Calcium-Ion
Calcium-Ion merged commit 17465b8 into QuantumNous:main Jul 6, 2026
2 checks passed
52assert added a commit to 52assert/new-api that referenced this pull request Jul 7, 2026
…codes

* origin/main: (180 commits)
  fix(billing): extend quantity validation and saturating conversions to remaining paths
  fix(billing): validate quantity parameters and harden quota calculations
  Fix/build date dns error (QuantumNous#5945)
  fix: avoid stale stream writes after client disconnect (QuantumNous#5710)
  feat(group): enhance group ratio editor with improved visibility rules and JSON parsing
  feat: optimize legacy top-up warning banner copy (QuantumNous#5851) (QuantumNous#5855)
  fix(web): redirect authenticated users away from sign-up page (QuantumNous#5910)
  feat(ssrf): implement SSRF protection in HTTP clients and validation functions
  feat(user): better messages for redeem failures
  fix(html): 修复 Shadow DOM 隔离渲染下深浅色模式无法自动切换的问题 (QuantumNous#5890)
  fix(web): sync home iframe theme and language (QuantumNous#5917)
  fix: 任务差额结算后 quota 和阿里视频时长优化 (QuantumNous#5923)
  fix(web): refine mobile user cards
  test(user): cover self-service password update guard
  feat(session): support opt-in Secure session cookies
  fix(auth): allow read-only access for non-disabled tokens
  fix(user): harden account email and password handling
  fix: align dynamic pricing style with log details dialog sections
  fix(channels): show field passthrough controls for Codex (QuantumNous#5902)
  fix(user): trim whitespace from username and validate input
  ...

# Conflicts:
#	controller/redemption.go
#	controller/user.go
#	model/option.go
#	model/redemption.go
#	model/user.go
#	web/default/src/features/auth/api.ts
#	web/default/src/features/redemption-codes/api.ts
#	web/default/src/features/redemption-codes/components/redemptions-provider.tsx
#	web/default/src/features/redemption-codes/components/redemptions-table.tsx
#	web/default/src/features/redemption-codes/constants.ts
#	web/default/src/hooks/use-sidebar-data.ts
#	web/default/src/i18n/static-keys.ts
#	web/default/src/routes/__root.tsx
liulixin-lex pushed a commit to liulixin-lex/xy-api that referenced this pull request Jul 7, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
xiaomingchen pushed a commit to xiaomingchen/new-api that referenced this pull request Jul 10, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
Jacobinwwey pushed a commit to Jacobinwwey/new-api that referenced this pull request Jul 11, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
ruanhangjian pushed a commit to ruanhangjian/new-api that referenced this pull request Jul 11, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
lizhongyi1209 pushed a commit to lizhongyi1209/new-api that referenced this pull request Jul 13, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。

(cherry picked from commit 17465b8)
noah-wung pushed a commit to noah-wung/new-api that referenced this pull request Jul 17, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
zhaodechao2008 pushed a commit to zhaodechao2008/new-api that referenced this pull request Jul 27, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
@olwater
olwater deleted the upstream-pr/html-dark-mode-fix branch July 30, 2026 21:22
330079598 pushed a commit to 330079598/new-api that referenced this pull request Aug 19, 2026
Shadow DOM 的样式隔离特性导致外部 html 元素上的 dark class 无法被
Shadow DOM 内部的 Tailwind dark: 选择器匹配到。通过 MutationObserver
监听 document.documentElement 的 class 变化,将 dark class 同步到
Shadow DOM 内的包装容器上,使深色模式样式正常生效。
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