fix(island): 胶囊"点不动"的根因——窗口必须始终可激活(否则 Windows 丢掉 mousedown)
真机复现(打包版 + 真实 OS 点击 + 渲染进程鼠标事件计数): - 胶囊态:渲染进程只收到 mouseup,**收不到 mousedown,因此永远不产生 click** → 点了没反应; - 展开态:完整 mousedown + mouseup + click → 正常。 根因:窗口创建时 `focusable: false`(WS_EX_NOACTIVATE),Windows 在窗口非活动时会丢掉鼠标 **按下**事件。结果是"岛只有在已经展开时才能被点开",即胶囊态/重启后/切到别的窗口后点不动 (用户反馈的"点不动 / 要点两下")。这也解释了为什么此前冒烟全绿 —— 冒烟用 DOM `.click()` 和直接调动作,绕过了 OS 输入层。 修复: - 窗口改为**始终可激活**(`focusable: true`);"不抢焦点"由 `showInactive()`(显示时不激活)保证; - `applyFocusable`:只有展开态才 `focus()`,收起/隐藏时 `blur()`——不占用户键盘焦点, 但保持可激活,点击才能送达; - `hideImmediately` 不再自己 `setFocusable(false)`,可激活性统一由 `applyFocusable` 维护。 自建穷举真机审计(`.cache/island-probe/audit.mjs`,真实 OS 点击,非合成事件):23 个场景 —— 普通/紧急通知、作业、普通/紧急叫人 × 上课/非上课、6 个停靠位置、展开→收起→再点、 课中收起后再点、下课补发、联合通知、已读、知道了、胶囊超时、关闭动画、大尺寸/半透明/ 毛玻璃/主题色、细缝态、预览态、运行中改位置尺寸、真实"点别处"失焦后再点、双广播、20s 回缩。 结果:dev 构建连续 3 轮 23/23,打包版 23/23。 回归:pnpm verify:desktop 82/82(新增回归用例「胶囊态窗口仍可激活(否则 Windows 丢掉 mousedown,胶囊"点不动")」,并把「点击屏幕任意位置回缩为胶囊」的断言改为"窗口始终可激活"); 打包版 verify:packaged 82/82、packaged=true、二次启动 74/74,全绿。
This commit is contained in:
1 parent
337d6eae53
commit
d900280760
2 files changed
+47
-11
No files matched your search
@@ -198,8 +198,16 @@ class IslandController {
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
hasShadow: false,
|
||||
// 不抢焦点:通知不应该打断学生正在做的事(键盘焦点仍留在原应用)
|
||||
focusable: false,
|
||||
/**
|
||||
* **必须可激活**(focusable: true)。
|
||||
*
|
||||
* Windows 下 `WS_EX_NOACTIVATE` 且窗口不是活动窗口时,鼠标**按下**事件会被丢掉:
|
||||
* 渲染进程只会收到 mouseup、收不到 mousedown/click,于是胶囊怎么点都没反应
|
||||
* (真机实测:胶囊态只有 mouseup;展开态 focusable 时是完整的 mousedown+mouseup+click)。
|
||||
* "不抢焦点"靠 `showInactive()`(显示时不激活)+ 收起时 `blur()` 保证,而不是靠
|
||||
* 关掉可激活性 —— 关掉可激活性的代价就是"点不动"。
|
||||
*/
|
||||
focusable: true,
|
||||
alwaysOnTop: true,
|
||||
type: 'toolbar',
|
||||
webPreferences: {
|
||||
@@ -637,7 +645,8 @@ class IslandController {
|
||||
this.emit();
|
||||
}
|
||||
if (!this.win || this.win.isDestroyed()) return;
|
||||
this.win.setFocusable(false);
|
||||
// 焦点/可激活性统一由 applyFocusable 维护(窗口必须始终可激活,否则丢 mousedown)
|
||||
if (this.win.isFocused()) this.win.blur();
|
||||
if (this.win.isVisible()) this.win.hide();
|
||||
this.win.setOpacity(1);
|
||||
}
|
||||
@@ -893,16 +902,21 @@ class IslandController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开态允许聚焦(用于"点击屏幕任意处收起"),其余时间不抢焦点。
|
||||
* 焦点策略:**窗口始终可激活**,只有展开态才真正持有键盘焦点。
|
||||
*
|
||||
* 注意:这个开关本身就是 Windows 上那次"假失焦"的来源(见 blur 处理器注释),
|
||||
* 因此收起时的判断不能只依赖 blur,必须结合指针位置。
|
||||
* 为什么不能把可激活性关掉(focusable=false):Windows 下 `WS_EX_NOACTIVATE` 且非活动窗口
|
||||
* 会**丢掉鼠标按下事件**(实测只到 mouseup、没有 mousedown/click),胶囊就"点不动"了。
|
||||
* 因此收起/隐藏时改为 `blur()`:既不占着用户的键盘焦点,又保持可激活(点击能正常送达)。
|
||||
* 展开态 `focus()` 是为了"点屏幕别处 → 失焦 → 回缩为胶囊"这条交互生效。
|
||||
*/
|
||||
private applyFocusable(): void {
|
||||
if (!this.win || this.win.isDestroyed()) return;
|
||||
const shouldFocus = this.state.mode === 'expanded';
|
||||
if (this.win.isFocusable() !== shouldFocus) this.win.setFocusable(shouldFocus);
|
||||
if (shouldFocus) this.win.focus();
|
||||
if (!this.win.isFocusable()) this.win.setFocusable(true);
|
||||
if (this.state.mode === 'expanded') {
|
||||
this.win.focus();
|
||||
return;
|
||||
}
|
||||
if (this.win.isFocused()) this.win.blur();
|
||||
}
|
||||
|
||||
private fadeIn(): void {
|
||||
|
||||
@@ -928,11 +928,33 @@ async function runIslandChecks(
|
||||
await sleep(700);
|
||||
const afterBlur = island.getState();
|
||||
record(
|
||||
'点击屏幕任意位置回缩为胶囊(展开时可聚焦 + 失焦收起)',
|
||||
focusable && islandWindow?.isFocusable() === false && afterBlur.mode === 'pill',
|
||||
'点击屏幕任意位置回缩为胶囊(失焦收起;窗口始终可激活)',
|
||||
focusable && islandWindow?.isFocusable() === true && afterBlur.mode === 'pill',
|
||||
`展开时 focusable=${focusable} 失焦后 mode=${afterBlur.mode} focusable=${islandWindow?.isFocusable() ?? '-'}`,
|
||||
);
|
||||
|
||||
// 4.5.2) 胶囊态窗口**必须始终可激活**:Windows 下 WS_EX_NOACTIVATE 且非活动窗口会丢掉
|
||||
// 鼠标**按下**事件(实测只到 mouseup、没有 mousedown/click)→ 胶囊"点不动"。
|
||||
// 这是用户反馈的根因,用"胶囊态 isFocusable() 必须为 true"固化下来。
|
||||
for (let index = 0; index < 10; index += 1) {
|
||||
const current = island.getState();
|
||||
if (!current.active && current.queued.length === 0) break;
|
||||
island.handleAction({ action: 'dismiss' });
|
||||
await sleep(120);
|
||||
}
|
||||
island.pushNotification(makeNotification('smoke-pill-focusable', 'NORMAL', '胶囊可激活校验'), {
|
||||
inClass: false,
|
||||
});
|
||||
await sleep(500);
|
||||
const pillFocusState = island.getState();
|
||||
record(
|
||||
'胶囊态窗口仍可激活(否则 Windows 丢掉 mousedown,胶囊"点不动")',
|
||||
pillFocusState.mode === 'pill' && (islandWindow?.isFocusable() ?? false) === true,
|
||||
`mode=${pillFocusState.mode} focusable=${islandWindow?.isFocusable() ?? '-'} 当前是否聚焦=${islandWindow?.isFocused() ?? '-'}`,
|
||||
);
|
||||
island.handleAction({ action: 'dismiss' });
|
||||
await sleep(300);
|
||||
|
||||
// 4.5.1) 展开动作自身引发的**假失焦**不能把刚展开的岛缩回胶囊。
|
||||
// 用户复现:"收起后再次点击灵动岛没反应" —— 点开胶囊后约 0.5s 被系统收回焦点,
|
||||
// 岛立刻缩回胶囊,看起来就像"点了没反应"。此时指针必然还在岛上,必须忽略该失焦。
|
||||
|
||||
Reference in new issue
Block a user