mattcasters opened a new issue, #8432:
URL: https://github.com/apache/hop/issues/8432
### Apache Hop version?
2.20.0-SNAPSHOT
### Java version?
21
### Operating system
Linux
### What happened?
In **Hop Web**, the vector SVG canvas rendering infrastructure
(`CanvasSvgFacade`, `CanvasSvgRendererHandler`, and `canvas-svg.js`) only
permits **one active SVG canvas per UI session**.
When an interactive dialog or viewer opens in the foreground and draws an
SVG (e.g. the Kimball Bus Matrix viewer dialog, model preview dialogs, or
action/context dialogs using SVG rendering), any canvas already rendered in the
background editor tab (such as a Dimensional Model, Data Vault model, Pipeline,
or Workflow) **immediately loses its SVG overlay and becomes blank**.
Furthermore, after the foreground dialog is closed, the background graph is
unable to draw or recover its overlay until the user manually switches tabs
back and forth.
---
### In-Depth Cause Analysis
The failure is caused by a session-wide singleton pattern spanning both the
Java RAP bridge and the client-side JavaScript:
#### 1. Single RemoteObject in `CanvasGraphRegistry` &
`CanvasSvgRendererHandler`
In `org.apache.hop.ui.hopgui.canvas.CanvasGraphRegistry`:
```java
private RemoteObject svgRendererRemote;
private Canvas activeCanvas;
```
There is only **one** `RemoteObject` of type `"hop.CanvasSvgRenderer"`
created per UI session.
When a dialog canvas initializes and renders,
`CanvasSvgRendererHandler.notifyCanvasReady(canvas, revision)` reuses the
session's single `RemoteObject`, overwriting `canvasId` with the dialog's
widget ID:
```java
remoteObject.set("canvasId", WidgetUtil.getId(canvas));
remoteObject.call("attachListener", null);
```
At the same time, `registry.setActiveCanvas(canvas)` re-points
`activeCanvas` to the dialog canvas.
#### 2. DOM Overlay Hijacking in `canvas-svg.js`
In `canvas-svg.js`, the type handler for `"hop.CanvasSvgRenderer"` maintains
a single instance holding a single `this._overlay` DOM element.
When `canvasId` changes to the dialog canvas:
1. It blanks the existing SVG:
```javascript
if (widget._svgHost) {
widget._svgHost.innerHTML = "";
}
```
2. In `_attachToCanvas(canvas)`:
```javascript
var parent = canvas.parentElement;
if (parent && this._overlay.parentNode !== parent) {
parent.appendChild(this._overlay);
}
```
`parent.appendChild(this._overlay)` **physically moves** the existing DOM
overlay node out of the background model tab's container and into the dialog
shell's DOM container.
The background graph is left with no overlay element in the DOM.
#### 3. Background Repaints Blocked in `CanvasSvgFacadeImpl.java`
In `CanvasSvgFacadeImpl.publishSnapshotInternal`:
```java
Canvas active = registry.getActiveCanvas();
if (active == null || active.isDisposed() || active == canvas) {
CanvasSvgRendererHandler.notifyCanvasReady(canvas, revision);
}
```
Because `active` is now the foreground dialog canvas, any subsequent render
of the background graph is suppressed because `active == canvas` evaluates to
`false`.
#### 4. Overlay Destruction on Dialog Close
When the foreground dialog closes:
1. RAP disposes the dialog shell and purges all its DOM nodes from the
browser.
2. Because `this._overlay` was reparented inside the dialog's DOM, **the
client overlay DOM element is destroyed**.
3. In Java, `CanvasSvgFacade.unregisterCanvas(canvas)` removes the dialog
canvas from the registry maps, but:
- `activeCanvas` in `CanvasGraphRegistry` is left pointing to a disposed
canvas.
- The background canvas is never re-notified.
- The client-side JS instance is left with a destroyed/orphaned DOM
element.
The background graph remains completely dead and un-rendered until a tab
switch forces a rebind.
---
### Fix Proposal
To allow multiple canvases to co-exist (such as dialogs over editor tabs,
split-screen editor views, or side-by-side diff viewers), the SVG canvas
infrastructure should manage `RemoteObject`s and overlays **per canvas
instance**, similar to `ContextDialogSvgRendererHandler`:
1. **`CanvasGraphRegistry.java`**:
- Replace the single `svgRendererRemote` with a map keyed by `canvasId`:
```java
private final Map<String, RemoteObject> svgRendererRemotes = new
ConcurrentHashMap<>();
```
2. **`CanvasSvgRendererHandler.java`**:
- In `notifyCanvasReady(Canvas canvas, long revision)` (or during
`registerCanvas`):
Create a dedicated `RemoteObject("hop.CanvasSvgRenderer")` for that
specific canvas ID.
- In `unregister(Canvas canvas)`:
Call `remoteObject.destroy()`, allowing RAP to notify the client to
clean up that specific canvas's overlay.
3. **`canvas-svg.js`**:
- Each `hop.CanvasSvgRenderer` instance (one per `RemoteObject`) owns its
own `this._overlay` and permanently attaches it to its assigned `canvasId`.
- Remove the `this._overlay.parentNode !== parent` reparenting logic.
- In `destroy()`: remove only that instance's own `_overlay` and clean up
its listeners/timers.
- Map hover interactions by `canvasId`
(`hop._canvasInteractions[canvasId] = this;`) instead of using a single global
`hop._canvasInteractionInstance`.
4. **`CanvasSvgFacadeImpl.java`**:
- Remove the `active == canvas` check in `publishSnapshotInternal`,
allowing visible canvases to render independently.
### Issue Priority
Priority: 2
### Issue Component
Component: Hop Web
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]