Public Access
Harden the WebView collapse, and replace its evidence with a measurement
An adversarial review of 0500e43 did not refute the fix but closed the gap I
had left open and found three hazards around it. A standalone spike — a 60-line
Avalonia app with no DodoSSH code — reproduces the airspace bug on a 340,* grid,
and a second harness mirroring the data plane's handshake measures what I had
only reasoned about: with IsVisible=false set before the window is ever shown,
the adapter is created, the page is fetched and the WebSocket 101 is sent, with
frames arriving over the socket while hidden. A cold WebView2 profile behaves
the same. Revealing recomputes bounds in about 7 ms.
So the docs no longer cite "35 msedgewebview2 processes" as the confirmation
that the renderer attaches. A process count cannot show that a socket was
accepted — the same shape of mistake, one level down, as the one that entry was
already correcting. It now cites the handshake, quotes Avalonia's maintainer on
airspace being by design, and links the still-open upstream issue.
Three changes to the fix itself:
- terminal.js skips the fit below 40px in either axis. The vendored fit addon
floors its proposal at 2 columns by 1 row rather than refusing, so a
degenerate viewport reflows the *remote* pty through window-change and
mangles wrapped scrollback unrecoverably. Reachable today by minimising, and
by dragging a splitter to the edge once splits land — a guard where the sizes
arrive, not a special case for one caller.
- FallbackValue=False on the binding. A compiled binding with no DataContext
yields UnsetValue, IsVisible falls back to true, and the occlusion returns
silently. Not reachable at runtime; it is what the previewer does.
- The comment now says why it must be IsVisible on this control: detaching
destroys the native control and the whole WebView2 process tree, so
conditional content would pay a cold start per unlock, and hoisting the
binding to an ancestor is unverified because NativeWebView's own
bounds-and-scaling re-push fires only for its own IsVisible.
Also recorded, not fixed: hiding does not suspend the page (visibilityState
stays "visible" and rAF keeps firing at ~115/s, which is *why* the handshake
completes while hidden); the conflict log can squeeze the terminal row toward
nothing; and nothing hands the terminal Win32 focus after Connect, so the first
keystrokes go to the shell's UI rather than the remote shell.
This commit is contained in:
@@ -220,8 +220,20 @@
|
||||
|
||||
IsVisible is load-bearing rather than cosmetic — see the note on the root Panel. Without it the
|
||||
native child window paints over the setup and unlock screens and swallows their input.
|
||||
|
||||
It must stay IsVisible on this control specifically, and two nearby alternatives are wrong.
|
||||
Removing the control from the tree instead — conditional content, a template swap — detaches it,
|
||||
and detaching destroys the native control and the whole WebView2 process tree, so every unlock
|
||||
would pay a cold start. Hoisting the binding to an ancestor looks tidier and is unverified:
|
||||
NativeControlHost does watch ancestors, but NativeWebView's own bounds-and-scaling re-push fires
|
||||
only for its own IsVisible.
|
||||
|
||||
FallbackValue, because a compiled binding with no DataContext yields UnsetValue, IsVisible then
|
||||
falls back to its default of true, and the occlusion comes back silently. Not reachable at
|
||||
runtime — the DataContext is set before the window is shown — but it is what the previewer does.
|
||||
-->
|
||||
<NativeWebView Grid.Row="2" x:Name="Terminal" IsVisible="{Binding IsUnlocked}" />
|
||||
<NativeWebView Grid.Row="2" x:Name="Terminal"
|
||||
IsVisible="{Binding IsUnlocked, FallbackValue=False}" />
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -135,7 +135,21 @@ function activate(sessionId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Below this, a pane is not being looked at — it is minimised, dragged to nothing, or the host has
|
||||
// hidden its window. Fitting anyway would be actively harmful rather than merely useless: the fit addon
|
||||
// floors its proposal at 2 columns by 1 row, so a degenerate viewport reflows the *remote* pty to 2x1
|
||||
// through window-change, and the wrapped scrollback that produces cannot be recovered when the pane comes
|
||||
// back. A guard rather than a fix for one caller, because several paths reach here — a minimised window, a
|
||||
// splitter dragged to the edge, and a host that hides the WebView while the vault is locked.
|
||||
const MINIMUM_FITTABLE_PIXELS = 40;
|
||||
|
||||
function resize(session, sessionId) {
|
||||
const pane = session.pane;
|
||||
|
||||
if (pane.clientWidth < MINIMUM_FITTABLE_PIXELS || pane.clientHeight < MINIMUM_FITTABLE_PIXELS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fit() throws if the pane has no layout yet, which happens on the very first frame.
|
||||
try {
|
||||
session.fit.fit();
|
||||
|
||||
Reference in New Issue
Block a user