You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Prepare tools.dynamia.navigation / ZK navigation so a single HTTP session can reliably drive multiple independent ZK desktops at once — real browser tabs opened concurrently, and (the primary near-term target) a non-ZK front-end shell embedding several <iframe>s, each loading its own ZK page via /page-embed/**.
Current state
ZKNavigationManager is @Scope("zk-desktop"), so each ZK Desktop (tab/iframe) already gets its own instance — the "one current page per manager" model is correct for this use case and does not need to change.
Page events (sendEvent/onPageEvent) already use EventQueues.DESKTOP scope — already isolated per tab/iframe.
Root problem
NavigationManagerSession (platform/core/navigation/.../NavigationManagerSession.java) is @Scope("session"): a single shared slot (page, pageParams, runLaterQueue) per HTTP session used to hand off the initial page to whichever ZKNavigationManager desktop bean is constructed next (ZKNavigationManager.init() → NavigationManagerSession.getInstance().updateNavManager(this)).
PageNavigationController.navigate() and PageEmbedController (platform/core/web/.../navigation/, already built for iframe embedding from non-ZK fronts) call NavigationManager.setPageLater(page, params) on every HTTP request, writing into that same shared session slot.
With a single tab this works because at most one desktop init is pending at a time. With multiple tabs/iframes opened concurrently in the same session, each /page-embed/.../pageX request overwrites the previous one's pending page/params before the corresponding desktop's @PostConstruct consumes it — a race condition where iframes can end up showing the wrong page, duplicate pages, or silently drop one of the requested pages. Same risk applies to runLaterQueue.
There is also no stable identifier correlating a NavigationManager instance to "which tab/iframe" it belongs to, and no session-level registry of active navigation manager instances — needed later if a host shell wants to enumerate/target a specific iframe's navigation.
Key design insight
PageNavigationController/PageEmbedController resolve their ModelAndView through InternalResourceView, which renders via a servlet forward (same thread, same request) into the .zul view — ZK's Desktop/ZKNavigationManager bootstrap for that tab/iframe happens on that same thread. So the fix doesn't need a correlation token threaded through URLs: replacing the @Scope("session") bean with a ThreadLocal isolates each concurrent request naturally, with zero changes to the web controllers. See #108.
Docs: document the contract — NavigationManager.getCurrent() keeps resolving "the manager for the current desktop/iframe" (via zk-desktop scope, unchanged); setPageLater/runLater must be called on the same thread that forwards into the target ZK desktop (not across a redirect).
Out of scope (for this Epic)
The single-current-page-per-NavigationManager model itself — correct for 1 desktop = 1 active page; not a blocker for real tabs/iframes (each already gets its own manager instance).
Intra-desktop multi-tab UI (TabPanel/WorkspaceViewBuilder, ZK tabs within one browser tab) — separate, already-working concern, not required for this goal.
Goal
Prepare
tools.dynamia.navigation/ ZK navigation so a single HTTP session can reliably drive multiple independent ZK desktops at once — real browser tabs opened concurrently, and (the primary near-term target) a non-ZK front-end shell embedding several<iframe>s, each loading its own ZK page via/page-embed/**.Current state
ZKNavigationManageris@Scope("zk-desktop"), so each ZK Desktop (tab/iframe) already gets its own instance — the "one current page per manager" model is correct for this use case and does not need to change.sendEvent/onPageEvent) already useEventQueues.DESKTOPscope — already isolated per tab/iframe.Root problem
NavigationManagerSession(platform/core/navigation/.../NavigationManagerSession.java) is@Scope("session"): a single shared slot (page,pageParams,runLaterQueue) per HTTP session used to hand off the initial page to whicheverZKNavigationManagerdesktop bean is constructed next (ZKNavigationManager.init()→NavigationManagerSession.getInstance().updateNavManager(this)).PageNavigationController.navigate()andPageEmbedController(platform/core/web/.../navigation/, already built for iframe embedding from non-ZK fronts) callNavigationManager.setPageLater(page, params)on every HTTP request, writing into that same shared session slot.With a single tab this works because at most one desktop init is pending at a time. With multiple tabs/iframes opened concurrently in the same session, each
/page-embed/.../pageXrequest overwrites the previous one's pending page/params before the corresponding desktop's@PostConstructconsumes it — a race condition where iframes can end up showing the wrong page, duplicate pages, or silently drop one of the requested pages. Same risk applies torunLaterQueue.There is also no stable identifier correlating a
NavigationManagerinstance to "which tab/iframe" it belongs to, and no session-level registry of active navigation manager instances — needed later if a host shell wants to enumerate/target a specific iframe's navigation.Key design insight
PageNavigationController/PageEmbedControllerresolve theirModelAndViewthroughInternalResourceView, which renders via a servlet forward (same thread, same request) into the.zulview — ZK'sDesktop/ZKNavigationManagerbootstrap for that tab/iframe happens on that same thread. So the fix doesn't need a correlation token threaded through URLs: replacing the@Scope("session")bean with aThreadLocalisolates each concurrent request naturally, with zero changes to the web controllers. See #108.Scope of changes
NavigationManagerSession's session scope withThreadLocal, with a request-lifecycle filter to clear it. This is the blocking fix; no token/URL/controller changes needed.NavigationManager— a framework-agnostic id per manager instance, for correlation/debugging and the registry below.NavigationManagerinstances (phase 2) — enumerate/target open tabs/iframes for a session, needed for a host shell orchestrating multiple iframes.Propagate a token through— not needed, see key design insight above (was Propagate navigation token through PageNavigationController / PageEmbedController #111, closed as superseded).PageNavigationController/PageEmbedControllerNavigationManager.getCurrent()keeps resolving "the manager for the current desktop/iframe" (viazk-desktopscope, unchanged);setPageLater/runLatermust be called on the same thread that forwards into the target ZK desktop (not across a redirect).Out of scope (for this Epic)
NavigationManagermodel itself — correct for 1 desktop = 1 active page; not a blocker for real tabs/iframes (each already gets its own manager instance).TabPanel/WorkspaceViewBuilder, ZK tabs within one browser tab) — separate, already-working concern, not required for this goal.Affected modules
platform/core/navigation(NavigationManager,NavigationManagerSession,BaseNavigationManager)platform/core/web(request-lifecycle filter only;PageNavigationController/PageEmbedControllerunchanged)platform/ui/zk(ZKNavigationManager)