Summary
src/webview/graph.tsx:192 guards the message listener with:
if (event.source !== window) return;
The comment above it asserts that in a VS Code webview the source field is the webview's own window. Measured, it is not. Every message the extension host posts is dropped before the payload is read, so the ER diagram panel never updates after it is first opened.
Measurements
Instrumented the listener to log before the guard, then toggled a model's checkbox in the sidebar (which pushes a fresh index):
MSG sameWindow=false sameParent=false sourceNull=false type="index"
So the message genuinely arrives, carries the right type, and source is a real object that matches neither window nor window.parent. The guard rejects it.
Worth stressing that this was measured, not reasoned about. An earlier attempt to fix it by widening the check to event.source !== window && event.source !== window.parent was a guess, and the numbers above show it would not have worked either.
Why the panel still renders at all
Initial state does not come over the message channel. renderInto embeds it directly in the HTML at src/graphWebview.ts:147:
window.__INITIAL_INDEX__ = ${serialised};
GraphApp seeds useState from that (graph.tsx:178). So the first paint is always correct and the bug is invisible until something tries to update an open panel.
What is broken as a result
Two paths, both postMessage and both dead:
graphWebview.ts:235 — the index push when showGraph is called on an already-open panel.
graphWebview.ts:258 — the reply to the webview's ready handshake.
- The
theme message handled at graph.tsx:197, so a theme switch does not reach an open panel.
The sidebar visibility toggles hit this first. They are currently worked around at extension.ts:148 by re-assigning the panel HTML on every checkbox change — correct, but it re-renders the whole diagram and visibly flashes. That workaround is the only reason the feature works at all today.
Where the check came from
It was added to satisfy CodeQL's js/missing-origin-check. Any fix has to keep that alert quiet, so simply deleting the line is not a solution.
Directions worth investigating
Not yet verified — listing so the next person does not start from zero:
- Find out what
event.source actually is. Log event.source === window.top, its location, and event.origin. The panel runs inside nested frames (active-frame inside the VS Code shell), so the host frame may be neither the immediate parent nor window.
- Check whether
event.origin is stable enough to check instead of source. VS Code has changed webview origins between versions, so this needs testing across a range, not just the current build.
- Consider validating the payload shape rather than the sender. The CSP at
graphWebview.ts:134 is already default-src 'none' with a script nonce, which is the real barrier to a hostile frame executing anything here; a discriminated-union check on data.type may satisfy CodeQL on its own.
Acceptance
- An open panel updates via
postMessage — no HTML reassignment.
- The
extension.ts:148 workaround is removed and toggling a checkbox no longer re-renders the whole diagram.
- CodeQL reports no
js/missing-origin-check on graph.tsx.
- Verified by actually toggling a checkbox in a running Extension Development Host, not by reading the code.
Summary
src/webview/graph.tsx:192guards the message listener with:The comment above it asserts that in a VS Code webview the
sourcefield is the webview's own window. Measured, it is not. Every message the extension host posts is dropped before the payload is read, so the ER diagram panel never updates after it is first opened.Measurements
Instrumented the listener to log before the guard, then toggled a model's checkbox in the sidebar (which pushes a fresh
index):So the message genuinely arrives, carries the right
type, andsourceis a real object that matches neitherwindownorwindow.parent. The guard rejects it.Worth stressing that this was measured, not reasoned about. An earlier attempt to fix it by widening the check to
event.source !== window && event.source !== window.parentwas a guess, and the numbers above show it would not have worked either.Why the panel still renders at all
Initial state does not come over the message channel.
renderIntoembeds it directly in the HTML atsrc/graphWebview.ts:147:GraphAppseedsuseStatefrom that (graph.tsx:178). So the first paint is always correct and the bug is invisible until something tries to update an open panel.What is broken as a result
Two paths, both
postMessageand both dead:graphWebview.ts:235— theindexpush whenshowGraphis called on an already-open panel.graphWebview.ts:258— the reply to the webview'sreadyhandshake.thememessage handled atgraph.tsx:197, so a theme switch does not reach an open panel.The sidebar visibility toggles hit this first. They are currently worked around at
extension.ts:148by re-assigning the panel HTML on every checkbox change — correct, but it re-renders the whole diagram and visibly flashes. That workaround is the only reason the feature works at all today.Where the check came from
It was added to satisfy CodeQL's
js/missing-origin-check. Any fix has to keep that alert quiet, so simply deleting the line is not a solution.Directions worth investigating
Not yet verified — listing so the next person does not start from zero:
event.sourceactually is. Logevent.source === window.top, itslocation, andevent.origin. The panel runs inside nested frames (active-frameinside the VS Code shell), so the host frame may be neither the immediate parent norwindow.event.originis stable enough to check instead ofsource. VS Code has changed webview origins between versions, so this needs testing across a range, not just the current build.graphWebview.ts:134is alreadydefault-src 'none'with a script nonce, which is the real barrier to a hostile frame executing anything here; a discriminated-union check ondata.typemay satisfy CodeQL on its own.Acceptance
postMessage— no HTML reassignment.extension.ts:148workaround is removed and toggling a checkbox no longer re-renders the whole diagram.js/missing-origin-checkongraph.tsx.