fix(gui): inject farewell overlay into parent DOM on shutdown
Replaces the data:-URL navigation (blocked by Chrome since v60 for top-frame navigation) with a direct DOM-append of a full-screen overlay onto the parent document. Uses z-index 2147483647 so it sits above Streamlit's connection-error banner when the websocket drops. Note: still doesn't fully suppress the connection-error banner in testing — the next iteration will render the overlay through Streamlit's own page rather than via a component iframe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,53 @@ def hide_streamlit_chrome() -> None:
|
||||
# Clean shutdown
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_FAREWELL_SCRIPT = """
|
||||
<script>
|
||||
(function () {
|
||||
// Strategy: append a full-screen overlay directly to the parent's
|
||||
// document.body (Streamlit's component iframes carry
|
||||
// allow-same-origin, so cross-frame DOM access is permitted).
|
||||
// The overlay uses z-index 2147483647 (max int32) so it sits on
|
||||
// top of Streamlit's "Connection error" banner when the websocket
|
||||
// drops a moment later. It's appended as a sibling of React's
|
||||
// root, so React's re-renders won't touch it.
|
||||
//
|
||||
// We don't navigate to a data: URL here because Chrome blocks
|
||||
// top-frame navigation to data: URLs (anti-phishing, Chrome 60+).
|
||||
function buildOverlay(doc) {
|
||||
var overlay = doc.createElement('div');
|
||||
overlay.id = 'datatools-farewell-overlay';
|
||||
overlay.style.cssText =
|
||||
'position:fixed;inset:0;background:#0f1115;color:#e8eaed;' +
|
||||
'z-index:2147483647;display:flex;align-items:center;' +
|
||||
'justify-content:center;font-family:system-ui,-apple-system,sans-serif;';
|
||||
overlay.innerHTML =
|
||||
'<div style="text-align:center;padding:32px 40px;border:1px solid #252a36;' +
|
||||
'border-radius:12px;background:#161922;max-width:480px;">' +
|
||||
'<h1 style="margin:0 0 8px 0;font-weight:600;letter-spacing:-0.01em;">' +
|
||||
'DataTools has shut down</h1>' +
|
||||
'<p style="opacity:0.7;margin:0;">You can close this browser tab.</p>' +
|
||||
'</div>';
|
||||
return overlay;
|
||||
}
|
||||
try {
|
||||
var doc = window.top.document;
|
||||
if (!doc.getElementById('datatools-farewell-overlay')) {
|
||||
doc.body.appendChild(buildOverlay(doc));
|
||||
}
|
||||
// Try to close the tab outright too — browsers only honour this
|
||||
// for tabs a script opened, so it usually no-ops, but it's free.
|
||||
try { window.top.close(); } catch (e) {}
|
||||
} catch (e) {
|
||||
// Cross-origin access denied (shouldn't happen given Streamlit's
|
||||
// sandbox flags, but fall back gracefully): cover this iframe.
|
||||
document.body.appendChild(buildOverlay(document));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
def quit_button(label: str = "Quit app", *, key: str = "quit_app_button") -> None:
|
||||
"""Render a Quit button that terminates the Streamlit server.
|
||||
|
||||
@@ -87,12 +134,15 @@ def quit_button(label: str = "Quit app", *, key: str = "quit_app_button") -> Non
|
||||
process (SIGTERM/SIGINT) does not reliably terminate it — Streamlit
|
||||
installs its own handlers and the tornado/asyncio loop swallows or
|
||||
defers the signal, so the browser sees the websocket drop while the
|
||||
python process stays alive. To shut down cleanly we schedule
|
||||
``os._exit(0)`` on a daemon thread after a short delay; the delay
|
||||
lets the current rerun finish painting the "shutting down" message
|
||||
before the process is hard-killed.
|
||||
python process stays alive. We schedule ``os._exit(0)`` on a daemon
|
||||
thread to hard-kill the process, and inject a small JS shim that
|
||||
either closes the tab (when allowed) or replaces the top frame with
|
||||
a self-contained "App closed" page so the user never sees
|
||||
Streamlit's red connection-error overlay.
|
||||
"""
|
||||
if st.session_state.get("_app_shutting_down"):
|
||||
from streamlit.components.v1 import html as _components_html
|
||||
_components_html(_FAREWELL_SCRIPT, height=0)
|
||||
st.success("Shutting down… you can close this browser tab.")
|
||||
st.stop()
|
||||
|
||||
@@ -100,7 +150,7 @@ def quit_button(label: str = "Quit app", *, key: str = "quit_app_button") -> Non
|
||||
st.session_state["_app_shutting_down"] = True
|
||||
|
||||
def _hard_exit() -> None:
|
||||
time.sleep(0.5)
|
||||
time.sleep(1.0)
|
||||
os._exit(0)
|
||||
|
||||
threading.Thread(target=_hard_exit, daemon=True).start()
|
||||
|
||||
Reference in New Issue
Block a user