Skip to content

Commit e67374c

Browse files
authored
修复导出预览独立窗口在 neko-pc 中最小化、最大化/恢复按钮无效的问题 (#1555)
* 修复导出预览独立窗口在 neko-pc 中最小化、最大化/恢复按钮无效的问题 * 检查 nekoWindowControl 是否已存在,并通过 waitForExportPreviewRewriteGate 做两段等待:先等 1.5s,失败后再等 6.5s;只有 shell 已 ready 或窗口控制 API 已注入时,才会 stop()/document.write()。如果仍然没满足条件,会关闭这次预览窗口并返回,不再抢断 preload 导航
1 parent 53dac61 commit e67374c

2 files changed

Lines changed: 123 additions & 1 deletion

File tree

static/app-chat-export.js

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,93 @@
122122
}
123123
}
124124

125+
function isExportPreviewShellReady(previewWindow, targetUrl) {
126+
if (!previewWindow || previewWindow.closed) return false;
127+
try {
128+
var href = previewWindow.location && previewWindow.location.href;
129+
if (!href || href === 'about:blank') return false;
130+
var current = new URL(href, window.location.href);
131+
var target = new URL(targetUrl, window.location.href);
132+
if (current.origin !== target.origin || current.pathname !== target.pathname) {
133+
return false;
134+
}
135+
var doc = previewWindow.document;
136+
return !!(doc && (doc.readyState === 'interactive' || doc.readyState === 'complete'));
137+
} catch (_) {
138+
return false;
139+
}
140+
}
141+
142+
function hasExportPreviewWindowControlApi(previewWindow) {
143+
if (!previewWindow || previewWindow.closed) return false;
144+
try {
145+
var api = previewWindow.nekoWindowControl;
146+
return !!(api && typeof api.minimize === 'function' && typeof api.maximize === 'function');
147+
} catch (_) {
148+
return false;
149+
}
150+
}
151+
152+
function waitForExportPreviewShell(previewWindow, targetUrl, timeoutMs) {
153+
return new Promise(function (resolve) {
154+
if (!previewWindow || previewWindow.closed) {
155+
resolve(false);
156+
return;
157+
}
158+
159+
var waitMs = Number(timeoutMs);
160+
if (!Number.isFinite(waitMs) || waitMs <= 0) waitMs = 1500;
161+
var settled = false;
162+
var pollTimer = null;
163+
var timeoutTimer = null;
164+
165+
function cleanup() {
166+
if (pollTimer) {
167+
window.clearInterval(pollTimer);
168+
pollTimer = null;
169+
}
170+
if (timeoutTimer) {
171+
window.clearTimeout(timeoutTimer);
172+
timeoutTimer = null;
173+
}
174+
try {
175+
previewWindow.removeEventListener('load', checkReady);
176+
} catch (_) {}
177+
}
178+
179+
function finish(ok) {
180+
if (settled) return;
181+
settled = true;
182+
cleanup();
183+
resolve(!!ok);
184+
}
185+
186+
function checkReady() {
187+
if (!previewWindow || previewWindow.closed) {
188+
finish(false);
189+
return;
190+
}
191+
if (isExportPreviewShellReady(previewWindow, targetUrl)) {
192+
finish(true);
193+
}
194+
}
195+
196+
try {
197+
previewWindow.addEventListener('load', checkReady);
198+
} catch (_) {}
199+
pollTimer = window.setInterval(checkReady, 40);
200+
timeoutTimer = window.setTimeout(function () { finish(false); }, waitMs);
201+
checkReady();
202+
});
203+
}
204+
205+
async function waitForExportPreviewRewriteGate(previewWindow, targetUrl) {
206+
var shellReady = await waitForExportPreviewShell(previewWindow, targetUrl, 1500);
207+
if (shellReady || hasExportPreviewWindowControlApi(previewWindow)) return true;
208+
shellReady = await waitForExportPreviewShell(previewWindow, targetUrl, 6500);
209+
return !!(shellReady || hasExportPreviewWindowControlApi(previewWindow));
210+
}
211+
125212
function showToast(key, fallback, duration) {
126213
if (typeof window.showStatusToast !== 'function') return;
127214
window.showStatusToast(translateLabel(key, fallback), duration || 3000);
@@ -2465,10 +2552,21 @@
24652552
if (isExistingWindow) {
24662553
disposePreviewModal(false);
24672554
}
2555+
state.previewWindow = previewWindow;
2556+
if (!isExistingWindow) {
2557+
var canRewritePreview = await waitForExportPreviewRewriteGate(previewWindow, getExportPreviewShellUrl());
2558+
if (!previewWindow || previewWindow.closed) return null;
2559+
if (!canRewritePreview) {
2560+
if (state.previewWindow === previewWindow) state.previewWindow = null;
2561+
try {
2562+
previewWindow.close();
2563+
} catch (_) {}
2564+
return null;
2565+
}
2566+
}
24682567
try {
24692568
if (typeof previewWindow.stop === 'function') previewWindow.stop();
24702569
} catch (_) {}
2471-
state.previewWindow = previewWindow;
24722570
var doc = previewWindow.document;
24732571
doc.open();
24742572
doc.write('<!DOCTYPE html><html lang="' + escapeHtml(document.documentElement.lang || 'en') + '"' + getPreviewThemeAttributesHtml() + '><head><meta charset="utf-8">'
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from pathlib import Path
2+
3+
4+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
5+
CHAT_EXPORT_JS = PROJECT_ROOT / "static" / "app-chat-export.js"
6+
7+
8+
def test_export_preview_waits_for_shell_before_rewriting_document():
9+
script = CHAT_EXPORT_JS.read_text(encoding="utf-8")
10+
11+
assert "function waitForExportPreviewShell(previewWindow, targetUrl, timeoutMs)" in script
12+
assert "function waitForExportPreviewRewriteGate(previewWindow, targetUrl)" in script
13+
assert "function hasExportPreviewWindowControlApi(previewWindow)" in script
14+
assert "function isExportPreviewShellReady(previewWindow, targetUrl)" in script
15+
assert "href === 'about:blank'" in script
16+
assert "previewWindow.addEventListener('load', checkReady)" in script
17+
assert "waitForExportPreviewShell(previewWindow, targetUrl, 6500)" in script
18+
assert "shellReady || hasExportPreviewWindowControlApi(previewWindow)" in script
19+
20+
gate_index = script.index("await waitForExportPreviewRewriteGate(previewWindow, getExportPreviewShellUrl());")
21+
guard_index = script.index("if (!canRewritePreview) {", gate_index)
22+
stop_index = script.index("if (typeof previewWindow.stop === 'function') previewWindow.stop();", gate_index)
23+
doc_open_index = script.index("var doc = previewWindow.document;", gate_index)
24+
assert gate_index < guard_index < stop_index < doc_open_index

0 commit comments

Comments
 (0)