Skip to content

Browser: keep drag input with its page and refuse lost-channel settlement #482

Open

Description

@andersalm

User impact

An active mouse drag can continue on a replacement Browser page. A related ordering check can report success after the input channel disconnects, allowing a later Runtime input to proceed without proof that the earlier selection arrived.

Found in the latest local 0.8.0 train input changes during #93's audit. Proposed priority: P1 for page ownership, P2 for the lost-channel ordering case; triage before freezing the Browser candidate under #87.

Exact candidate and evidence

Candidate 460b1888d344f52cebd1c25866be88ff4085f035, tree 17aeaf23c640e2dec8f653fd16f3771f95a55b9c. It is local and unpublished. Two reviewers independently repeated deterministic in-memory fixtures against its actual JavaScript. These are source-level reproductions, not installed media proof.

Active drag changes owner

capsules/browser/browser/browser-input-surface.js:335-460 stores pointer and drag state without the originating page/generation. A queued animation-frame move and direct release use the page current when the callback runs. browser.js:1609 captures ownership at that later invocation.

Fixture: pointerdown at x=100 on old page; queue pointermove x=120; replace current page before the animation frame; flush frame; pointerup x=140.

Both runs produced:

[
  {"page":"old","type":"mouse","x":100,"y":100,"buttons":1},
  {"page":"replacement","type":"mouse","x":120,"y":100,"buttons":1},
  {"page":"replacement","type":"mouse","x":140,"y":100,"buttons":0}
]

Expected: later input stays bound to the original gesture owner or is retired. It cannot become a new page's gesture.

Disconnect is treated as completed drain

browser.js:1670 waits on buffer amount and elapsed time without binding the channel identity/open state. The real display getter returns zero when the channel is removed (browser-remote-display.js:1384, close at :281). The caller continues after its page check (browser.js:1735-1741); channel reconnect can occur without changing page identity.

Fixture: bufferedAmount=64; disconnect after 20 ms and return buffer=0. The helper resolved with the channel closed:

{"settled":true,"channelOpen":false,"elapsedMs":175}

The separate reviewer observed 180 ms. Elapsed time is incidental; successful settlement on a closed channel is the finding.

Acceptance

  • Capture gesture page/session/generation at pointerdown; retire queued callbacks when ownership ends.
  • Release the old gesture through its own valid owner, or terminate it without sending to a replacement.
  • Cover page replacement, close, navigation, blur, pointer capture loss and a fresh gesture after retirement.
  • Bind the input ordering barrier to the actual channel/generation and distinguish confirmed drain from disconnect/unknown delivery.
  • Cover pending bytes then disconnect/reconnect with the same page ID; later input must receive a clear refusal/recovery outcome.
  • Add regression cases using the real source callback boundaries, then run the installed Mac selection/paste/close/reconnect journey on the signed candidate.

Existing input/order tests and the overall 1,819-test behaviour suite pass; these transitions are absent from that evidence. Coordinate with draft PR #421 (deferred wheel/touch ownership), #439 (adapter uncertainty) and #87. This issue owns the new mouse-drag and datachannel-barrier slice; it does not duplicate those broader tasks. The exact no-file reproduction commands are retained in the audit's local evidence packet and can become the regression fixtures.

Activity

  1. added this to the After 0.8.0 milestone on Oct 22, 2026
  2. andersalm commented on Oct 22, 2026

    @andersalm
    ContributorAuthor

    Triage (22 Oct, OMP security lane): known gap for 0.8.0, fixed after the release, or earlier if a new candidate is cut for another reason.

    Both mechanisms are confirmed in source on 460b1888d. Both came with the new drag-select code: 26f7c7589 has no drag state and no drain barrier. Severity is Low:

    • Page owner: only the user replaces a Browser page during a drag (address bar fallback, the engine or exit menus, startup). Navigation, new windows and tabs inside the guest keep the same page and display, so a website cannot move a drag to another page. The new page receives a press, drag and release at the pointer position, for the same user, on the page that user asked for.
    • Closed channel: the display does not reconnect under the same page ID; a failed display closes the display or starts closing the page. A waiting paste or click still goes to the user's own page.

    Fix shape: keep the page owner in the drag state at pointerdown and end the drag silently when the owner changes (about 20 lines and 2 tests); remember the channel of the last send and refuse instead of settling when that channel has closed (about 10 lines and 1 test). The same change adds a test for a release sent while Runtime text waits at the barrier, and checks what the guest receives when a release finds the channel closed.

  3. andersalm commented on Oct 22, 2026

    @andersalm
    ContributorAuthor

    Disposition update (22 Oct, OMP). Main's Browser lane now has a fix for both mechanisms in this issue: 22e40f1f2 on local branch fix/080-browser-copy-protocol, after the Browser Copy fix 787d1b44d, both on 460b1888d. It is not on the train yet; Main integrates it after its gate. Codex reviewed 22e40f1f2 (tree cfef92b9db42): PASS WITH CHANGES, with no blocking finding. That review did not run builds or tests.

    The review left two notes. Both are recoverable, and both stay open here as follow-ups after 0.8.0:

    1. Copy can offer older text. After Home asks the Browser page for its clipboard, the page accepts the first valid clipboard message. A delayed earlier clipboard update can arrive before the reply, so Home can offer that older text instead of the current selection. Home's approval prompt still asks before anything reaches the device clipboard. The prompt names the action and does not display the text, so the user sees older text only after pasting it, and can then copy again. (Corrected 22 Oct: this sentence first said that the prompt shows the text.) Fix direction: match the guest's reply to the request.
    2. The first click after an Engine or Exit switch can fail once. The input-ordering barrier keeps state from the old page, so the new page's first click can get a delivery error. The next action works. Fix direction: tie the barrier to its page owner and retire it when that page closes.

    The review's smoke supplies the clipboard text and stands in for the approval, so the real guest-to-Mac Copy journey still needs Anders's recheck on a rebuilt test Home (#87).

  4. andersalm commented on Oct 22, 2026

    @andersalm
    ContributorAuthor

    Status (22 Oct): fixed on the 0.8.0 train in 22e40f1f2 (Codex copyproto1 PASS WITH CHANGES; remote gate PASS).

    • A drag stays with its page. A page change ends it, and no move or release reaches the replacement page.
    • A closed or rebound input channel no longer counts as drained. The next Runtime input fails visibly instead.
    • The same branch fixes Browser Cmd+C copy (787d1b44d). The window now accepts the guest's clipboard reply.

    Follow-ups after 0.8.0 (non-blocking review notes):

    • A delayed clipboard-monitor push can complete a pending copy with older text before the read reply arrives. Home's prompt still gates the write; the fix is request-correlated replies.
    • The drain barrier is page-global, so after an Engine/Exit switch the first input on the new page can get the delivery error once. Scope the barrier to its page owner.
    • When the page changes mid-drag, the release to the old page is dropped. If that guest is still alive, its button stays held until the next press there.

    Still open for acceptance: the installed Mac select, copy, paste, close and reconnect journey on the signed candidate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions