fix(close): Edge fallback + better tryClose + honest hint
There is no JavaScript override for browser tab-close security:
``window.close()`` only succeeds on windows JS opened (Chrome --app
windows qualify; a regular browser tab does not). What we can do is
make the --app path easier to hit and the failure case more
actionable.
Three changes:
1. ``src/gui/__main__.py`` — extend browser detection. PATH lookup
now also looks for ``msedge`` / ``microsoft-edge``; Windows install
candidates include the Edge install path; macOS candidates include
Edge and Chromium. Edge is Chromium-based, supports ``--app``, and
ships on every Windows 10+ machine — so users without Chrome no
longer fall through to the regular browser tab. When the fallback
IS hit, print a warning to stderr explaining why Close-from-page
will require Ctrl+W. Renamed ``_find_chrome`` to
``_find_app_browser`` to reflect the broader scope.
2. ``_FAREWELL_SCRIPT_TEMPLATE`` in ``components/_legacy.py`` —
factor close attempts into a ``tryClose`` helper that runs three
escalating tries: standard ``win.close()``, the
``win.open('', '_self')`` history-rewrite trick (no-op in modern
Chrome but free), and ``win.top.close()``. Auto-close on paint AND
the manual button now both call this helper. Skip the manual hint
if the close eventually succeeded between the click and the 250 ms
timeout.
3. ``quit.close_hint`` in en/es i18n packs — rewrite the message to
tell the user honestly that this is a browser security restriction,
tell them the Ctrl+W keystroke that works, and point them at
``python -m src.gui`` for the auto-closing app-mode experience.
2008 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -159,15 +159,33 @@ _FAREWELL_SCRIPT_TEMPLATE = """
|
||||
'</div>';
|
||||
return overlay;
|
||||
}
|
||||
function tryClose(win) {
|
||||
// Three escalating attempts. Modern browsers only honour close()
|
||||
// for windows that JS opened (Chrome --app windows qualify; a
|
||||
// regular browser tab does not) — there is no JS override for
|
||||
// that policy. The window.open('', '_self') trick used to rewrite
|
||||
// a window's "opener" lineage and let close() through in older
|
||||
// Chrome; modern Chrome patched it but it costs nothing to try.
|
||||
try { win.close(); } catch (e) {}
|
||||
if (win.closed) return;
|
||||
try {
|
||||
var w = win.open('', '_self', '');
|
||||
if (w) {
|
||||
try { w.close(); } catch (e) {}
|
||||
}
|
||||
} catch (e) {}
|
||||
if (win.closed) return;
|
||||
try { win.top.close(); } catch (e) {}
|
||||
}
|
||||
function wireClose(doc, win) {
|
||||
var btn = doc.getElementById('datatools-close-btn');
|
||||
if (!btn) return;
|
||||
btn.onclick = function () {
|
||||
try { win.close(); } catch (e) {}
|
||||
try { win.top.close(); } catch (e) {}
|
||||
tryClose(win);
|
||||
// If after 250ms the window is still here, the browser
|
||||
// blocked the close — show the manual-close hint.
|
||||
setTimeout(function () {
|
||||
if (win.closed) return;
|
||||
var hint = doc.getElementById('datatools-close-hint');
|
||||
if (hint) hint.style.display = 'block';
|
||||
}, 250);
|
||||
@@ -180,9 +198,9 @@ _FAREWELL_SCRIPT_TEMPLATE = """
|
||||
doc.body.appendChild(buildOverlay(doc));
|
||||
}
|
||||
wireClose(doc, win);
|
||||
// Try to close the tab outright too — succeeds in Chrome --app
|
||||
// Auto-close attempt on first paint — succeeds in Chrome --app
|
||||
// windows, no-ops on regular tabs.
|
||||
try { win.close(); } catch (e) {}
|
||||
tryClose(win);
|
||||
} catch (e) {
|
||||
// Cross-origin access denied (shouldn't happen given Streamlit's
|
||||
// sandbox flags, but fall back gracefully): cover this iframe.
|
||||
|
||||
Reference in New Issue
Block a user