Skip to content

Commit de7854c

Browse files
committed
reveal() 失败/返回 false 没有 fallback:有效,已加 return-ball-reveal-failed 桌面状态和 neko:return-ball-reveal-failed 事件
await reveal 后缺少 drag token 复查:当前分支确实有这个风险;我没有加 token 复查,而是更小地移除了阻塞式 await,所以这个 async gap 不再存在
1 parent e027020 commit de7854c

2 files changed

Lines changed: 50 additions & 9 deletions

File tree

static/app-ui.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,6 +1472,7 @@
14721472
const MULTI_WINDOW_RETURN_BALL_DRAG_SHRINK_SIZE = 160;
14731473
const MULTI_WINDOW_RETURN_BALL_DRAG_SHRINK_FALLBACK_MS = 220;
14741474
const MULTI_WINDOW_RETURN_BALL_DRAG_RESTORE_FALLBACK_MS = 600;
1475+
const MULTI_WINDOW_RETURN_BALL_REVEAL_FALLBACK_MS = 600;
14751476
let multiWindowReturnBallDragState = null;
14761477
let idleReturnBallDesktopDragStateFrame = 0;
14771478
let idleReturnBallDesktopDragStatePending = null;
@@ -2347,14 +2348,47 @@
23472348
restoreSavedReturnBallStyle(container, state);
23482349
}
23492350

2350-
async function revealReturnBallDragWindow() {
2351+
function dispatchReturnBallRevealFailed(reason, error) {
2352+
scheduleIdleReturnBallDesktopBridge('return-ball-reveal-failed', container);
2353+
window.dispatchEvent(new CustomEvent('neko:return-ball-reveal-failed', {
2354+
detail: {
2355+
reason: reason || 'unknown',
2356+
container: container,
2357+
errorMessage: error && (error.message || String(error))
2358+
}
2359+
}));
2360+
}
2361+
2362+
function revealReturnBallDragWindow() {
23512363
if (!window.nekoPetDrag || typeof window.nekoPetDrag.reveal !== 'function') {
2364+
dispatchReturnBallRevealFailed('bridge-unavailable');
23522365
return false;
23532366
}
2367+
let settled = false;
2368+
const fallbackTimer = setTimeout(() => {
2369+
if (settled) return;
2370+
dispatchReturnBallRevealFailed('reveal-timeout');
2371+
}, MULTI_WINDOW_RETURN_BALL_REVEAL_FALLBACK_MS);
23542372
try {
2355-
return await window.nekoPetDrag.reveal();
2373+
const revealResult = window.nekoPetDrag.reveal();
2374+
Promise.resolve(revealResult).then((ok) => {
2375+
settled = true;
2376+
clearTimeout(fallbackTimer);
2377+
if (ok === false) {
2378+
dispatchReturnBallRevealFailed('reveal-failed');
2379+
}
2380+
}).catch((error) => {
2381+
settled = true;
2382+
clearTimeout(fallbackTimer);
2383+
console.warn('[App] 返回球拖拽渲染完成后恢复窗口显示失败:', error);
2384+
dispatchReturnBallRevealFailed('reveal-rejected', error);
2385+
});
2386+
return true;
23562387
} catch (error) {
2388+
settled = true;
2389+
clearTimeout(fallbackTimer);
23572390
console.warn('[App] 返回球拖拽渲染完成后恢复窗口显示失败:', error);
2391+
dispatchReturnBallRevealFailed('reveal-threw', error);
23582392
return false;
23592393
}
23602394
}
@@ -2694,12 +2728,12 @@
26942728
if (!isActiveDragToken(dragToken)) return;
26952729
const expectedWidth = restoreBounds ? restoreBounds.width : state.savedWindowW;
26962730
const expectedHeight = restoreBounds ? restoreBounds.height : state.savedWindowH;
2697-
waitForViewportSize(dragToken, expectedWidth, expectedHeight, async () => {
2731+
waitForViewportSize(dragToken, expectedWidth, expectedHeight, () => {
26982732
restoreSavedBallStyle();
26992733
delete document.body.dataset.nekoBallDrag;
27002734
container.setAttribute('data-dragging', 'false');
27012735
scheduleIdleReturnBallDesktopBridge('return-ball-drag-click', container);
2702-
await revealReturnBallDragWindow();
2736+
revealReturnBallDragWindow();
27032737
dispatchReturnBallClick();
27042738
}, {
27052739
fallbackMs: MULTI_WINDOW_RETURN_BALL_DRAG_RESTORE_FALLBACK_MS,
@@ -2734,7 +2768,7 @@
27342768

27352769
const expectedWidth = finalBounds ? finalBounds.width : state.savedWindowW;
27362770
const expectedHeight = finalBounds ? finalBounds.height : state.savedWindowH;
2737-
waitForViewportSize(dragToken, expectedWidth, expectedHeight, async () => {
2771+
waitForViewportSize(dragToken, expectedWidth, expectedHeight, () => {
27382772
if (shouldRestoreSavedBallStyle) {
27392773
restoreSavedBallStyle();
27402774
container.setAttribute('data-dragging', 'false');
@@ -2747,7 +2781,7 @@
27472781
movedDistancePx: movedDistancePx
27482782
}
27492783
}));
2750-
await revealReturnBallDragWindow();
2784+
revealReturnBallDragWindow();
27512785
return;
27522786
}
27532787
// 先同步恢复球 opacity,再删除 nekoBallDrag 显示页面内容,
@@ -2765,7 +2799,7 @@
27652799
movedDistancePx: movedDistancePx
27662800
}
27672801
}));
2768-
await revealReturnBallDragWindow();
2802+
revealReturnBallDragWindow();
27692803
// 延迟恢复 transition,避免恢复瞬间触发动画
27702804
state.transitionCleanupTimer = setTimeout(() => {
27712805
state.transitionCleanupTimer = null;

tests/unit/test_avatar_return_button_idle_tiers_static.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,15 +232,22 @@ def test_desktop_return_ball_drag_lifecycle_waits_for_restored_viewport_before_r
232232

233233
assert "MULTI_WINDOW_RETURN_BALL_DRAG_SHRINK_FALLBACK_MS = 220" in source
234234
assert "MULTI_WINDOW_RETURN_BALL_DRAG_RESTORE_FALLBACK_MS = 600" in source
235+
assert "MULTI_WINDOW_RETURN_BALL_REVEAL_FALLBACK_MS = 600" in source
235236
assert "continueOnFallback" in source
236237
assert "waitForViewportSize timed out; continuing best-effort cleanup" in source
237238
assert "keeping return-ball hidden until viewport is restored" in source
238239
assert "waitForViewportSize hard timeout; continuing best-effort cleanup" in source
239240
assert "clearMultiWindowReturnBallDeferredWork(state)" in source
240241
assert "state.viewportWaitFallbackTimer = setTimeout(pollViewportRestore, 50)" in source
241242
assert "runWhenStable({ timedOut: true })" in source
242-
assert "async function revealReturnBallDragWindow()" in source
243+
assert "function revealReturnBallDragWindow()" in source
243244
assert "window.nekoPetDrag.reveal" in source
245+
assert "function dispatchReturnBallRevealFailed(reason, error)" in source
246+
assert "'return-ball-reveal-failed'" in source
247+
assert "'neko:return-ball-reveal-failed'" in source
248+
assert "Promise.resolve(revealResult)" in source
249+
assert "dispatchReturnBallRevealFailed('reveal-timeout')" in source
250+
assert "await revealReturnBallDragWindow()" not in source
244251
assert "function isNativeReturnBallDragDisabled()" in source
245252
assert "isNativeReturnBallDragDisabled() || !window.nekoPetDrag" in source
246253
assert "const dragStarted = window.nekoPetDrag.start(screenX, screenY)" in source
@@ -258,7 +265,7 @@ def test_desktop_return_ball_drag_lifecycle_waits_for_restored_viewport_before_r
258265
no_move_end = source.index("const finalBounds = await resolveFinalWindowBounds", no_move_start)
259266
no_move_block = source[no_move_start:no_move_end]
260267

261-
assert no_move_block.index("await revealReturnBallDragWindow();") < no_move_block.index("dispatchReturnBallClick();")
268+
assert no_move_block.index("revealReturnBallDragWindow();") < no_move_block.index("dispatchReturnBallClick();")
262269

263270

264271
def test_return_button_drag_has_single_owner_per_runtime_path():

0 commit comments

Comments
 (0)