Skip to content

[CodeMirror] Selection points outside of document, from DOMObserver.onSelectionChange #1320

Description

@microbit-matt-hillsdon

Claude generated from Sentry error log analysis.

Sentry: PYTHON-EDITOR-NEXT-21F

RangeError: Selection points outside of document, 468 events since 31 August 2024, still present in v3.1.11 at a low but steady rate (4 events on v3.1.10, 4 on v3.1.11).

The suspected cause below is a hypothesis with no reproduction. Filing it mainly to record the browser/guard mismatch, which is the one concrete finding.

Not part of #1317

Worth stating up front, because it looks like the tile crashes and turns up in the same crash cascades. It is not one of them:

  • It first appeared in August 2024, long before the tile rewrite.
  • It occurs on v3.1.7 (23 events), which shipped @codemirror/view 6.26.3 — pre-tile.

It does co-occur with the #1317 group: one session on 10 Sep 2026 threw this, Invalid position, No tile at position and HierarchyRequestError within a few seconds. That is probably a fragile editor state producing several independent faults rather than a shared cause.

Stack

Outermost first; the last frame is where it threw.

DOMObserver.onSelectionChange (@codemirror/view)
  DOMObserver.flush
    applyDOMChange
      EditorView.dispatch
        resolveTransaction (@codemirror/state)
          Transaction.create
            new Transaction
              checkSelection        <- throws

No frames in our code. mechanism is an addEventListener handler, i.e. unhandled.

Suspected cause

onSelectionChange (domobserver.ts:170 upstream) maps the browser's current DOM selection onto document positions and dispatches it. checkSelection rejects the transaction because the resulting range is past the end of the document, so the DOM selection describes text the document no longer has.

Upstream already guards against precisely this ordering problem, but narrowly:

// Deletions on IE11 fire their events in the wrong order, giving
// us a selection change event before the DOM changes are
// reported.
// Chrome Android has a similar issue when backspacing out a
// selection (#645).
if ((browser.ie && browser.ie_version <= 11 || browser.android && browser.chrome) && ...)
  this.flushSoon()
else
  this.flush(false)

The finding: our traffic is not what the guard covers

The guard applies to IE11 and Chrome on Android. Our events are desktop Chromium, with no Android at all:

Browser Events
Chrome 150 73
Chrome 147 30
Chrome 151 26
Edge 150 18
Edge 151 16
Chrome 149 13
Chrome 142 9
Safari 17.6 4
OS Events
Windows >=10 80
macOS >=10.15.7 64
ChromeOS 51

So either the same event-ordering race occurs on desktop Chromium in a case upstream has not accounted for, or the cause is different and the resemblance is misleading.

Alternative explanation not ruled out

One of our own extensions could be dispatching a selection against a stale document. We have several that dispatch from async sources — the language server view, lint, and the structure-highlighting measure pass. The absence of our frames in the trace argues against it, since the throw happens inside the observer's own flush rather than in a transaction we initiated, but it is not proof.

Caveat on the numbers

Lifetime count is 468, but only 195 events carry a release tag: Sentry's tag index has 90-day retention, so the release breakdown is a recent window, not full history. The pre-tile v3.1.7 attribution is within that window and is safe to rely on.

Related

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions