Public Access
f997e87c5fe0ee3bb321ac6aadb31d788477aad3
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1908189100 |
Make the terminal's right button copy and paste, and never open a menu
Selecting output and right-clicking it opened the WebView's own context menu with Copy on it, which is correct behaviour for a web page and wrong for a terminal. What a terminal's right button has meant since the Windows console got QuickEdit, and since PuTTY shipped, is this: with something selected it copies and drops the selection, with nothing selected it pastes. No menu, no second click. The gesture can only live in the page. The selection is drawn into a canvas the host never sees, and once a terminal has focus the host's window receives no input events at all — the same reason the Ctrl+plus chords are handled there. ◆ THE CLIPBOARD IS THE HOST'S IN BOTH DIRECTIONS, AND THE READING IS WHY. navigator.clipboard.readText() is gated behind the clipboard-read permission in every Chromium, and an embedded WebView with no PermissionRequested handler answers a permission request by putting a prompt on the screen. Trading a context menu for a permission dialog is not this change. So the page asks and the host answers, exactly as requestFontSizeStep does. Writing does not go the other way either, although navigator.clipboard.writeText would have worked from the page on Windows: a clipboard that is the host's in one direction and the page's in the other is two mechanisms to keep working across WebView2, WebKitGTK and Android's WebView. The host has owned a clipboard delegate since the keychain grew its COPY buttons, so each App.axaml.cs grows a ClipboardReader beside its ClipboardWriter and MainWindowViewModel takes it. Three opcodes: client ClipboardCopy and ClipboardPaste, server Clipboard. ◆ Clipboard IS NOT Paste, AND THE DIFFERENCE IS THE BRACKETING. Paste carries a snippet off the sidebar, and the page deliberately *types* a single-line one rather than bracketing it, because bash marks a bracketed paste as an active region and a snippet should read as though it had been typed. A clipboard paste is the case those markers are for — readline uses them to decide it has been pasted into — so Clipboard is always bracketed and never followed by an Enter. One opcode for both would have needed a flag meaning "which of the two kinds of paste is this", on a payload whose existing flag byte means something else. Both client frames are dispatched before the session lookup, which is what keeps the gesture working in a pane whose shell has just ended: squinting at the message a remote left behind is exactly when somebody copies the line. Dispatch was past MA0051's length once it grew a second such branch, so the frames no pump needs to see moved into Announce() and the font-size chord went with them. ◆ CAPTURED AND STOPPED, RATHER THAN MERELY PREVENTED. preventDefault alone suppresses the menu and still lets xterm's own contextmenu handler run, which moves its helper textarea under the pointer at z-index 1000 and leaves the selection sitting in it, selected. Both of those exist to serve the native menu. Taking the event in the capture phase on the pane and stopping it there is what keeps the gesture wholly the page's. rightClickSelectsWord is now false, which on macOS is a change of default rather than a restatement of it: xterm sets it true there alone, so a right-click would have selected the word under the pointer and copied that instead of pasting — wrong on one platform only, which is the worst way for it to be wrong. The renderer harness grows the pane it has been inviting since RendererReconnectionTests: a fake Terminal, the two addons, and a real UTF-8 codec, because both directions run through the page's own TextEncoder and TextDecoder and a Latin-1 stub would pass a test with an em dash in the selection. Its warning about declaring names terminal.js declares now covers the half that does not SyntaxError — a harness frame() was silently replaced by the page's frame() and failed six tests on a type error a long way from the cause. Manual checks 1.11 and 11.13 are what is left for a person: that a real contextmenu event reaches a capture listener, that no native menu is drawn once it is prevented, and that xterm's real paste() brackets. 11.13 also records what is unverified — whether Android's WebView delivers a pointer's secondary click as a contextmenu event at all. ClipboardReader is wired on that head regardless, because the alternative is a head where the paste half silently does nothing and nobody finds out; that head is not compiled here either, this machine having no android workload. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
963cb7f670 |
Reconnect the terminal view when somebody comes back to it
Going back to a terminal left alone for a while found it stuck on "Reconnecting the terminal view…", and stuck is the right word: the banner stayed and nothing behind it was reconnecting. The page's whole recovery story was a setTimeout chain, and a chain is exactly what a WebView is entitled to stop running. Chromium throttles timers in a page nobody is looking at — down to once a minute once it has been hidden five minutes — and a renderer that is frozen, or reclaimed and not yet reloaded, runs none of them. So the socket drops while nobody is watching, the banner goes up, the retry is scheduled, and the retry is then the one thing not running. Three defects, each of which leaves that banner up for the rest of the page's life. ◆ NOTHING LISTENED FOR THE PAGE COMING BACK. The only thing that could clear the banner was a timer that may never fire. terminal.js now reconnects on visibilitychange, focus and online — the events that mean somebody is looking again, and the ones that cannot be throttled — cancelling the pending timer and resetting the backoff. Over a healthy socket all three do nothing, which is what makes them safe to fire as often as clicking a window does. ◆ A HANDSHAKE THAT NEVER FINISHED WAS INVISIBLE. Every retry was scheduled by a close or an error, so an attempt parked in CONNECTING — which is what a suspended renderer leaves behind — scheduled nothing at all, ever. There is now a five-second watchdog on the handshake. ◆ STALE SOCKETS SCHEDULED RETRIES, AND THAT ONE IS A LOOP RATHER THAN A STALL. connect() never detached the old socket's handlers, and the host aborts the displaced socket on takeover — TerminalDataPlane.UpgradeAsync, doing exactly what it should. That close read as a fresh failure and scheduled a retry against the socket that had just succeeded, whose own close scheduled the next: no fixed point, reconnecting every second forever with the banner up for most of it. Every handler now asks whether it is still the page's own attempt, and connect() closes what it abandons. ◆ WHICH OF THE PLATFORM BEHAVIOURS ACTUALLY BIT IS NOT ESTABLISHED, and the fix does not depend on knowing. Throttled timers, a frozen renderer and a reclaimed one all end at the same dead timer; guessing between them would have produced a narrower fix for one of the three. THE TEST RUNS terminal.js ITSELF, in a fake browser, inside dotnet test. RendererPage loads the file the shell project ships — not a transcription of its logic into C#, which would be a copy that stays correct while the page rots — into a Jint engine, one per test, over a harness that fakes a WebSocket and a clock and nothing else. Jint rather than a node script because CI would run the node one and nobody's inner loop would; the cost is that Jint is not Chromium, so this proves the page's logic and nothing about how a WebView behaves. That line is drawn in RendererPage's remark and picked up by two new manual checks, 1.10 for the desktop and 11.12a for the phone, which own the platform half. Four of the nine tests fail against the page as it stood — the stale close, the parked handshake, and the two wake-ups. Two more assert that a wake-up over a healthy socket does nothing, and pass against either version on purpose: they are what stops the cure being worse. Left alone deliberately: a socket that is open and dead shows no banner at all, because readyState still reads OPEN. That looks like a terminal that swallows what is typed, needs a liveness probe rather than a faster retry, and is written down at the end of 11.12a rather than quietly bundled in here. |