Class AppTab
UI it currently renders
in — the delivery target for Timer fires,
SwingWorker callbacks, EHelper.callSwing(Runnable)
continuations, and blocking-dialog resume — plus the browser tab
(TabScope) that hosts it, whose close ends the app. One app per
session (D_active_ui_pointer), so one AppTab per session, held under a session
attribute.
// MainWindowRoute, on first load AND every F5 rebind — bind this UI + its tab:
AppTab.markAppUI(ui);
// EHelper.singleLiveUI, from a background thread under the session lock:
UI target = AppTab.forSession(session).liveUI(); // null if the app UI is gone
// SwingBridgeEmulatorsBootstrap's tab-destroy listener:
AppTab.onTabClosed(session, destroyedScope); // closes the session iff it was the app tab
The tab identity is F5-stable: a refresh keeps the same window.name
→ the same TabScope, so re-binding the fresh UI to the same tab is
idempotent and only a real tab close destroys the scope. When no tab scope is
available (browserless Karibu without opt-in, or a deployment that didn't wire
SwingBridgeEmulatorsBootstrap) the tab is null: UI identity
still drives delivery, only tab-close detection goes inert.
The single home for app-identity state — the live app UI (delivery) and
its browser tab (close detection) — resolved by EHelper.singleLiveUI(VaadinSession),
bound by MainWindowRoute, and torn down by SwingBridgeEmulatorsBootstrap's tab-destroy listener. See D_session_scoped_pools/D_active_ui_pointer. It also
carries AppInstance's handle on the tab scope's values, which is what
lets app-instance state resolve on the request-less teardown threads.
-
Method Summary
Modifier and TypeMethodDescriptionstatic AppTabforSession(com.vaadin.flow.server.VaadinSession session) The session's app tab, ornullif none is recorded yet.com.vaadin.flow.component.UIliveUI()The app's UI if it is still live — inVaadinSession.getUIs()and notisClosing()— elsenull(a stale record; a later tab can then claim the app).static voidmarkAppUI(com.vaadin.flow.component.UI ui) Recordsuias the session's app UI — on first load and on every F5 rebind — binding it to the current browser tab.static voidonTabClosed(com.vaadin.flow.server.VaadinSession session, com.github.mvysny.vaadin.tabscope.TabScope scope) Tab-scope destroy hook (wired bySwingBridgeEmulatorsBootstrap): on a real app-tab close — the destroyedscopeis this app's tab — fires the D_shutdown_lifecycleWINDOW_CLOSINGdispatch and ends the session; a curtained (non-app) tab's close is ignored, leaving the app running.booleanownsTab(com.github.mvysny.vaadin.tabscope.TabScope scope) True ifscopeis the browser tab hosting this app.
-
Method Details
-
markAppUI
public static void markAppUI(com.vaadin.flow.component.UI ui) Recordsuias the session's app UI — on first load and on every F5 rebind — binding it to the current browser tab. Idempotent across F5 (samewindow.name→ same tab): re-binds the fresh UI in place. A fresh tab claiming the app after the previous one is gone replaces the record. Must run under the session lock (a UI attach does). -
forSession
The session's app tab, ornullif none is recorded yet. -
liveUI
public com.vaadin.flow.component.UI liveUI()The app's UI if it is still live — inVaadinSession.getUIs()and notisClosing()— elsenull(a stale record; a later tab can then claim the app). An F5 rebind has already moved the record to the fresh UI by the time the old one closes, so this never spuriously drops the app mid-refresh. Must be called under the session lock. -
ownsTab
public boolean ownsTab(com.github.mvysny.vaadin.tabscope.TabScope scope) True ifscopeis the browser tab hosting this app. -
onTabClosed
public static void onTabClosed(com.vaadin.flow.server.VaadinSession session, com.github.mvysny.vaadin.tabscope.TabScope scope) Tab-scope destroy hook (wired bySwingBridgeEmulatorsBootstrap): on a real app-tab close — the destroyedscopeis this app's tab — fires the D_shutdown_lifecycleWINDOW_CLOSINGdispatch and ends the session; a curtained (non-app) tab's close is ignored, leaving the app running. Best-effort — a session already tearing down is fine.Runs on the tab-scope reaper thread. The dispatch goes through
JFrame.dispatchShutdownFromTabClose(VaadinSession)directly rather than leaning onsession.close()→VaadinSession-destroy: that thread is request-less, soclose()only sets stateCLOSINGandfireSessionDestroy(hence the session-destroy backstop dispatch) would not run until the HTTP-session timeout. We still callclose()for the eventual teardown; the dispatch dedups so the backstop won't re-fire.
-