Files
Hubert Gancarczyk b781dc0e91 fix(tool-server): catch a Chromium page that refuses the typed characters
`delivered` counts ARRIVALS, from a capture listener that sees each `beforeinput`
before the element's own handler can cancel it. That is what makes it the right
evidence for "did focus move", and it is why it cannot say whether anything took
EFFECT: a field that refuses every insertion in place reads as fully delivered
while holding nothing. Both halves of the split rule then agree the wrong way —
the value is wrong, the arrivals are complete — so neither guard fires.

Reproduced against a real Chromium renderer at this head, `<input
value="old-value-seeded">` whose `beforeinput` handler `preventDefault()`s every
`insert*`, focus retained: `{ clear: true, text: "abc" }` returned 200
`{ typed: "abc", keys: 3, cleared: true }` over an EMPTY field.

A second capture listener counts the `input` events, which Blink dispatches only
for an insertion it applied, and the new guard fires on the corner where the two
disagree completely: every character arrived, none took effect, the field holds
nothing. A page refusing SOME insertions is normalising, and a page that cancels
the event and writes the value itself leaves the field non-empty — both keep
passing, verified against the same renderer.

Reported under its own code: focus never moved, so nothing was split across
fields, and the caller needs to know the field is empty rather than go looking in
a neighbour.

Three further fixes in the same two files:

- The abort signal reached the head of the per-device chain and stopped there.
  `runChromium` took no signal and its dispatch loop slept plainly, so a client
  that hung up mid-call left Chromium typing out every remaining character and
  holding the chain for the whole run — the same abandonment window that made the
  chain-head check necessary. It is now checked between characters with the
  cadence wait yielding to it, as the iOS backend already does, and kept out of a
  character's own three events and out of the clear's select-all/delete pair,
  which must stay atomic. Verified over HTTP: hanging up 2s into a 20-character
  run at a 300ms cadence stopped it at 6 characters and left the rest untyped,
  while the identical call held open typed all 20.

- `nodes` is dropped from the focused-element probe. It was computed, declared on
  `FocusedEditable` and shipped, but nothing on the server read it back — the
  after-probe reports the surviving stamps as `residue`. `stampEmbeds` stays, for
  the side effect that count was a by-product of, and the test that used the field
  to prove a form control is never asked for its embeds now counts the calls
  instead: the probe swallows a throwing `querySelectorAll`, so the throw alone
  was never the pin.

- `delayMs`'s ceiling is pinned AT the boundary. `5000`-accepted plus
  `600000`-rejected left the number free — raising `.max(5000)` to `.max(10000)`
  kept the whole keyboard suite green — so a `5001` probe now holds it.
2026-08-17 15:02:23 +02:00
..
2026-08-10 19:36:14 +02:00
2026-02-25 16:35:06 +01:00