Skip to content

The interaction between composition and updateText/updateSelection should be more clearly defined #111

Description

@marijnh

At the moment, in Chromium's implementation of EditContext, if you push a text/selection update into the edit context while a composition is in progress, further composition updates will continue to happen at the old selection position. See also this issue.

In a situation (which I think is typical of an EditContext-based editor) where the editor library will push changes made to its document via sources other than the edit context into the edit context in order to keep that synced with its own document model, this is going to cause issues.

I would expect updates to the edit context's text/selection model that don't directly impact the text around the composition to move the context's understanding of where the composition happens. Updates that do touch the composed text should probably abort the composition.

The current interface, where updateText and updateSelection are two separate methods, even though the data they interact with is deeply entangled, makes addressing this somewhat awkward, because a state update isn't a single atomic thing, but two imperative calls, with the context being in a bogus state in between them.

I was actually surprised to see that (at least in Chrome's implementation), updateText does not affect selectionStart/selectionEnd—i.e. when the selection is at position 10 and you call updateText(0, 6, ""), it stays at 10 (or is clipped to the end of the document) rather than moving to position 4 along with the text that it points at. Is that an intentional decision, or something that just fell out of the most straightforward implementation? If updateText did adjust the selection (which seems like it will be a welcome behavior in almost every situation), it provides more of an atomic way to push updates into the context, and may make it easier to define composition behavior in response to such an update.

Given all that, my proposal would be:

  • Make updateText affect the selection start and end, moving them by text.length - (rangeEnd - rangeStart) when pos >= rangeEnd, or to rangeStart + text.length when pos > rangeStart && pos < rangeEnd.
  • When a composition is active while updateText is called
    • If the updated range does not overlap with the composed range, move the composition position in the same way
    • If it does overlap, abort the composition
  • When a composition is active while updateSelection is called with a selection that differs from the current selection, the composition should probably be aborted

Activity

  1. added
    Agenda+Queue this item for discussion at the next WG meeting
    on Dec 11, 2024
  2. dandclark commented on Dec 12, 2024

    @dandclark
    Contributor

    I agree that this behavior seems surprising, and your proposed changes are reasonable. I'm not aware of a deliberate decision having been made here, and am assuming that, like you say, the current behavior was just the most straightforward implementation.

    If we want to change this behavior the main issue would be that it's a breaking change to the Chromium implementation. Chromium will generally try to avoid changes that might break code in the wild without very strong justification.

    In building a justification, it would be good to understand better how hard it is to work around this. For the failure to update the selection position, I assume devs can work around this by just ensuring that after calling updateText they always call updateSelection if needed. But the active composition case looks like it can't really be worked around.

    I'm tempted to try to split the difference and only change the behavior in the second and third bullet points: adjusting composition positions if updateText is called during an active composition, or aborting composition if there's an overlap. I assume that the developer impact of this would be smaller, and it's also more measurable; we can add a use counter specifically checking how often updateText and updateSelection are called when there's an active composition.

    However, maybe it would be confusing for developers if we change that behavior without also changing the behavior in the first bullet point (updating selection positions when updateText is called).

  3. marijnh commented on Dec 12, 2024

    @marijnh
    Author

    In building a justification, it would be good to understand better how hard it is to work around this.

    It is extremely awkward. If your text model changes through some event that doesn't originate in the edit context during a composition, you cannot touch the edit context to sync it up, because that will cause it to emit unusable events. So you have to somehow accommodate for the fact that your edit context and your actual text model have diverged—and because they can diverge in arbitrary ways, this is very difficult to model. My current workaround consists of some crude, unsound hacks (codemirror/view@9658d5e, codemirror/view@e7d6eeb) because modeling this properly would just be too much added complexity.

    If we want to change this behavior the main issue would be that it's a breaking change to the Chromium implementation.

    Yes, but only for a case where the current behavior is not very compelling (landing the selection in a more or less arbitrary place after you call updateText). My feeling is that improving this at this relatively early stage is going to be less of a headache than standardizing the existing behavior, and might even accidentally fix code that's currently subtly broken. (But this isn't a deal breaker for me either way, I can work around it by making sure I always follow up with an updateSelection call, if necessary.)

  4. johanneswilm commented on Dec 12, 2024

    @johanneswilm
    Contributor

    From meeting minutes (2024-12-12):

    RESOLUTION:
    we write up changes to the spec, and then put up a spec PR
    dandclark to add a non-normative note to the spec noting that changes may be coming to selection offset update behavior

  5. removed
    Agenda+Queue this item for discussion at the next WG meeting
    on Dec 12, 2024
  6. marijnh commented on Jan 28, 2025

    @marijnh
    Author

    @dandclark Do you know when you'll be able to get around to drafting the spec update?

    (It's looking like I'm going to be turning off EditContext in CodeMirror again, because this issue is impossible to properly work around.)

  7. added
    Agenda+Queue this item for discussion at the next WG meeting
    on Feb 12, 2025
  8. johanneswilm commented on Feb 12, 2025

    @johanneswilm
    Contributor

    @dandclark Is CodeMirror the only known user of EditContext in DOM mode? If yes, it would seem like it shouldn't be too difficult to change. If not, do we know what workarounds other users employ?

  9. dandclark commented on Mar 13, 2025

    @dandclark
    Contributor

    This is what the discussed spec change would look like: #115.

    There remain concerns with making this non-backwards-compatible change to the shipped Chromium implementation. The change would also affect non-DOM-mode users of EditContext, which includes Google Docs. I talked to the engineer from Docs who worked on the EditContext integration and there are definitely concerns about compatibility there.

    So there are a few ways to approach this.
    One idea is to make the behavior opt-in, for example by passing option EditContext:
    const ec = new EditContext({syncSelectionWithTextUpdate: true}).

    This is not really satisfying since the default behavior would not be the best behavior, so another thing that could be explored in conjunction with this is adding use counters to the Chromium implementation to detect when the proposed change would have affected pages that did not request this opt-in. If that use count is sufficiently low, the old behavior could be removed and the opt-in would become a no-op.

  10. johanneswilm commented on Mar 13, 2025

    @johanneswilm
    Contributor

    @dandclark sounds good. I agree that adding a counter would be preferable as we don't have that many known users right now. Will that count only number of times a browser uses the feature or will it also include things like number of domains it is used on?

  11. dandclark commented on Mar 13, 2025

    @dandclark
    Contributor

    Discussed in Editing WG on 2025-03-13:

    08:26 next: #111
    08:27 dandclark: the main issue is shipping due to compat concerns
    08:27 dandclark: I talked with gdocs eng, they have some concerns
    08:27 dandclark: few ways forward: build way to opt in to behavior change
    08:27 dandclark: (not desirable, would be good for new behavior to be the default)
    08:28 dandclark: another alternative: origin trials? CodeMirror, GDocs can try it out
    08:28 dandclark: next steps — implement in Chromium behind flkag
    08:28 flag
    08:28 dandclark: from this group, we already have agreement to move forward with it
    08:28 dandclark: no other changes from what we previously discussed
    08:29 johanneswilm: how long would it take to go ahead with one of the above?
    08:30 dandclark: in terms of shipping something to the public, I think we'll have an update on what the rollout plan is by the next monthly WG meeting
    08:30 dandclark: maybe couple months if we were to ship behind flag
    08:30 feasible that we would start rolling out on experimental basis on next few months
    08:31 dandclark: we'd recommend other browsers just start by implementing the new behavior
    08:31 (we all agree it's better)
    08:32 dandclark: worst case scenario is that old needs to be the default due to compat
    08:32 ...with Chrome

  12. marijnh commented on Mar 14, 2025

    @marijnh
    Author

    While it's absolutely possible for this to regress something, it seems like the code would have to do something very specific, like set the selection before updating the text (which seems problematic when the new selection position is outside of the range of the old text) or storing the selection position in a variable before updating the text, then calling updateText, and then using that stored position to determine that no call to updateSelection is needed because the stored position is the same as the desired position. Any other reasonable code that I can imagine is going to want to re-sync the selection after calling updateText because the current behavior will, in many situations, not give you a reasonable selection.

    It is of course entirely possible that I'm failing to account for some other pattern. But I expect that if you can get some examples of existing EditContext code and review them for how they'd handle this change, most would continue to work or even have a bug they didn't know they had solved by the change.

  13. removed
    Agenda+Queue this item for discussion at the next WG meeting
    on May 7, 2025
  14. ashishkum-ms commented on Nov 12, 2025

    @ashishkum-ms
    Contributor

    Here is the page load percentage telemetry data (from June 2025) collected to analyze the impact of implementing the change #115 in chromium:

    1. When updateText is called and the update range precedes or overlaps the selection: 0.0037%
    2. During an active composition:
      a. When updateText is called and the update range precedes the composition range: Less than 0.0001%
      b. When updateText is called and the update range overlaps with the composition range: Less than 0.0001%
      c: when updateSelection is called: Less than 0.0001%
  15. johanneswilm commented on Dec 11, 2025

    @johanneswilm
    Contributor

    TPAC 2025:

    Ashish
    Demonstration of the issue
    To propose the solution, make changes in #115
    The proposed solution is likely not backwards compatible
    We collected telemetry and impact will not be big, but not trivial also
    Johannes
    EditContext has been requested by a number of libraries, but no investment enough to make it work.
    One tried it and found a big issue
    Change is not backwards compatible, but the current behavior is not what one would expect
    You want to experiment this, do you want to add this to the spec?
    Ashish
    Yes
    Johannes
    What do you mean experimental? Behind a flag?
    Ashish
    Yes
    Smaug
    The spec change is a draft, do you want a review?
    Johannes
    Experimentation, behind a flag -> review -> testing, if successful then we’ll go to merging the spec change.
    Firefox not against, what about Apple?
    Megan
    It should be fine.
    Johannes
    When do you need this?
    Today no one is objecting, please continue.

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

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions