Skip to content

Safari/WebKit 下设置面板无法打开:侧边栏分层规则造成的层叠上下文困住了 position:fixed 面板 #40

Description

@dingyi580

现象

装上 maid-atelier 皮肤后,在 Safari 里点击侧栏「设置」,面板不会出现。屏幕会变暗(蒙层正常出现),但面板本身看不见,观感像是界面卡住了。停用皮肤后一切正常,Chrome / Edge 等 Blink 内核浏览器也一直正常。

复现环境:macOS 26.6 + Safari 26.6,DSH 0.1.0-rc.6,dsh --profile web。在当前 main(873f5c6)上仍可复现。

根因

SettingsPanel 不是挂在 document 根上的 portal,它挂在侧边栏内容根节点内部,靠 position: fixed 浮出到视口。

而皮肤的侧边栏分层规则给这个节点加了 position: relative + z-index: 2:

body[data-dsh-maid-atelier] :is([data-pane='sidebar'], [class*='sidebarCol']) > div > :not(
    [data-skin-chrome='sidebar-mascot'],
    [data-skin-chrome='sidebar-corners'],
    [role='tooltip']
  ) {
  position: relative;
  z-index: 2;
}

这两条合起来让该节点成为一个层叠上下文(stacking context)。Blink 允许 position: fixed 后代逃出祖先的层叠上下文,WebKit 不允许——面板因此被困在侧边栏那一层里,画不出来。

为什么现有的规则没能解决

maid-atelier.module.css 里已经有一条规则察觉到了面板位置特殊,在设置打开时把该节点提升到 z-index: 1000,注释也写明了「SettingsPanel is mounted inside that root instead of a document portal」。

但提升 z-index 只是抬高了这个层叠上下文本身,上下文依然存在,困在里面的 fixed 面板出不来。在 Blink 上本来就不需要它生效,所以这个缺口一直没暴露。

建议的修改

设置打开时让该节点退回 position: static; z-index: auto,即不再产生层叠上下文。面板自身的 z-index 随即在页面层级生效——这正是它在 Blink 上能盖住 sticky Markdown 横幅和会话头部的原因,行为不变。

 body[data-dsh-maid-atelier][data-maid-settings-open]
   :is([data-pane='sidebar'], [class*='sidebarCol'])
   > div
   > :has(
     [data-slot='sidebar.settings']
       > :is(button, [role='button'])[aria-expanded='true']
   ) {
-  z-index: 1000;
+  position: static;
+  z-index: auto;
 }

选择器不变,仍然只在 [data-maid-settings-open] 存在时匹配,设置面板关闭时零影响,侧边栏描金边框、Q 版娘、上下花边均不受影响。

排查记录(留给后来人)

这个 bug 会骗过几乎所有常规诊断手段,在 Safari 的 Web Inspector 里逐项测量,结果全是「正常」:

检查项 结果
getBoundingClientRect() 800×401,尺寸与位置都正确
elementFromPoint() 打在面板中心 返回面板自己,「无遮挡」
面板自身计算样式 opacity: 1、visibility: visible、display: flex
整条祖先链计算样式 无任何可使元素隐藏的属性
控制台 无报错(只有 sourcemap 404)

几个当时看着很像、但实测都被排除的方向::has() 在 WebKit 失效、backdrop-filter 合成差异、侧边栏 overflow: hidden 裁剪、/api 同源围栏返回 403。

最终定位靠的是整体开关对照 + 二分:先确认「关掉皮肤全部样式表则面板正常」,再筛出与面板祖先链匹配的皮肤规则(约 11 条),逐条单独关闭,命中上述分层规则。

需要特别注意 elementFromPoint 的误导性:皮肤所有装饰层都带 pointer-events: none,命中测试会直接跳过它们,所以「点击测不到遮挡」不等于「视觉上没被遮挡」,这两件事在本例中必须分开看。

已验证

改动已基于当前 main(873f5c6)实现并验证:

  • 全量测试通过(89 项,含新增的 1 项回归测试)
  • lib/client.js 已随源码重新构建,两次构建逐字节一致,产物中无绝对机器路径、无远程资源依赖
  • 新增回归测试断言 position: static 与 z-index: auto 同时存在,并断言 z-index: 1000 不再出现,防止回退
  • 同时更新了 keeps root-level relational selectors out of the skin scope 一例中写死旧值的断言
  • Safari 26.6 实机验证:设置面板可正常打开,侧边栏各装饰层在面板开/关两种状态下均正常

现成分支在这里,如果合适我可以开 PR,或者你直接取用:

https://github.com/dingyi580/dsh-deep-whale/tree/fix/webkit-settings-dialog

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions