From b279901825cb8cbdaa151dbd36f0ae80897a43e6 Mon Sep 17 00:00:00 2001 From: Dan Clark Date: Fri, 25 Aug 2023 15:55:52 -0700 Subject: [PATCH] Clarify active EditContext behavior (#60) Fix several issues related to the active EditContext and updates with Text Input State. - Currently the "active EditContext" concept is nebulous: it's not clear whether it's per-user-agent, per-document, etc, and the reference to a "cached active EditContext" in https://w3c.github.io/edit-context/#update-the-editcontext is not well defined. In this PR, define that it's per-document, which seems to work well with how focus is managed in documents and is compatible with the behaviors we've already tried to specify. What we really want to end up with is to have at most one active EditContext per [top-level traversable navigables](https://html.spec.whatwg.org/multipage/document-sequences.html#top-level-traversable), but [top-level traversable navigable](https://html.spec.whatwg.org/multipage/document-sequences.html#top-level-traversable) is not really its own type and [navigable](https://html.spec.whatwg.org/multipage/document-sequences.html#navigable) isn't typically used to store this kind of thing. - Perform some plumbing through various algos so they use the one from the document receiving input rather than the generic "active EditContext". - Change the update of the Text Edit Context in the Update the Rendering steps to happen in a posted task per feedback in #41 . - Also update the active EditContext at that time if there were focus changes. - Remove a `dirty flag` and `character bounds updated flag` that are never set anywhere and don't seem to be necessary. --- index.html | 142 ++++++++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 68 deletions(-) diff --git a/index.html b/index.html index 1653398..210ffa9 100644 --- a/index.html +++ b/index.html @@ -96,14 +96,6 @@

The EditContext Model

When changes are made to the [=Text Edit Context=] by the [=Text Input Service=], those changes are reflected to the author asynchronously in the form of events which are dispatched against the [=active EditContext=]. When the author makes changes to the [=active EditContext=], those changes will be reflected in the [=Text Edit Context=] during the next lifecycle update.

-

- To reflect changes made by the author to the [=active EditContext=] since the last lifecycle update, run the steps to [=update the Text Edit Context=] - with the [=active EditContext=]'s [=text state=]'s [=text=], [=selection start=], [=selection end=], [=control bounds=], [=selection bounds=], and [=codepoint locations=]. -

-

- When the [=Text Input Service=] updates the state of the [=Text Edit Context=], run the steps to [=update the EditContext=] - with the [=Text Edit Context=]'s [=text state=]'s [=text=], [=text formats=], [=selection start=], [=selection end=], [=is composing=], [=composition start=], and [=composition end=]. -

EditContext state

Both the [=Text Edit Context=] and {{EditContext}} have a [=text state=] which holds the information exchanged in the aforementioned updates. The text state consists of: @@ -138,11 +130,6 @@

EditContext state

It would be good if the API could be simplified to require just the positions of the actively composed text along with the [=selection bounds=] and [=control bounds=]. Exploration is needed to see if that is possible.

-

In addition to [=text state=], an {{EditContext}} also has: -

Association and activation

An {{EditContext}} has an associated element, an {{HTMLElement}}. @@ -182,18 +169,16 @@

Association and activation

- A [=top-level traversable=] should have at most one active EditContext, which is determined - by running the steps to [=determine the active EditContext=] for that [=top-level traversable=]. + A {{Document}} has an active EditContext, which may be null.

- When an {{EditContext}} that was previously the [=active EditContext=] stops being - the [=active EditContext=], run the steps to [=deactivate an EditContext=] - on that {{EditContext}}. + When a {{Document}} receives text input from the [=Text Input Service=] and that + {{Document}} has a non-null [=active EditContext=] |editContext|, the user agent + must run [=Update the EditContext=] given |editContext| and the + [=Text Edit Context=]'s [=text state=]'s [=text=], [=text formats=], + [=selection start=], [=selection end=], [=is composing=], [=composition start=], + and [=composition end=].

-

- When, exactly? Do we need a step for this in the Event Loop? -

-

When there is an [=active EditContext=] and there is a text input from the [=Text Input Service=], the user agent must run [=Update the EditContext=].

Differences for an EditContext editing host

@@ -218,12 +203,12 @@

Differences for an EditContext editing host +

Event loop changes

+

+ A new step will be introduced as a substep within the [=Update the rendering=] step + in the HTML Event Loops Processing Model, immediately following step 15 (which runs + the focusing steps for {{Document}}s whose + focused areas + become non-focusable). The step is: For each + fully active + {{Document}} |doc|, [=queue a global task=] on the [=DOM manipulation task source=] + given |doc|'s [=relevant global object=] to run + the [=Update the Text Edit Context=] steps given |doc|. +

Examples

Using an {{EditContext}}, an author can mark a region of the document editable by associating an {{EditContext}} object with an element as shown in the example below:

@@ -653,7 +650,8 @@

Extensions to the HTMLElement interface

  • Set [=this=]'s internal [[\EditContext]] slot to be |editContext|.
  • -
  • If |oldEditContext| is not null and |oldEditContext| is the [=active EditContext=], then: +
  • If |oldEditContext| is not null and |oldEditContext| is the [=this=]'s + node document's [=active EditContext=], then:
    1. Run the steps to [=deactivate an EditContext=] with |oldEditContext|.
    2. If |editContext| is not null, run the steps to [=activate an EditContext=] with |editContext|.
    3. @@ -667,6 +665,7 @@

      Update the EditContext

      Input
      +
      |editContext|, an {{EditContext}}
      |text|, a string
      |textFormats|, a structure that has an array of text format info from the [=Text Input Service=]
      |selectionStart|, the new position for the start of the selection
      @@ -678,64 +677,58 @@

      Update the EditContext

      None
        -
      1. If the [=active EditContext=] != cached active EditContext -
          -
        1. Return
        2. -
        -
      2. - If |text| is not empty and [=is composing=] is false + If |text| is not empty and |editContext|'s [=is composing=] is false.
          -
        1. [=Fire an event=] named compositionstart at the [=active EditContext=] using {{CompositionEvent}}. +
        2. [=Fire an event=] named compositionstart at |editContext| using {{CompositionEvent}}.
        3. -
        4. set [=is composing=] to true
        5. +
        6. set |editContext|'s [=is composing=] to true.
      3. - If |text| is empty and [=is composing=] is false + If |text| is empty and |editContext|'s [=is composing=] is false
          -
        1. Return
        2. +
        3. Return.
      4. - If [=composition start=] is 0 and [=composition end=] is 0 + If |editContext|'s [=composition start=] is 0 and [=composition end=] is 0
          -
        1. Set [=composition start=] to |selectionStart|
        2. -
        3. Set [=composition end=] to |selectionEnd|
        4. +
        5. Set |editContext|'s [=composition start=] to |selectionStart|.
        6. +
        7. Set |editContext|'s [=composition end=] to |selectionEnd|.
      5. - Replace the substring of [=text=] in the range of [=composition start=] and [=composition end=] with |text| + Replace the substring of |editContext|'s [=text=] in the range of |editContext|'s [=composition start=] and [=composition end=] with |text|
      6. -
      7. Set [=selection start=] to |selectionStart|
      8. -
      9. Set [=selection end=] to |selectionEnd|
      10. -
      11. [=Dispatch text update event=] with |text|
      12. -
      13. Set [=composition end=] to [=composition start=] plus the length of |text|
      14. -
      15. [=Dispatch text format update event=] with |textFormats|
      16. -
      17. [=Dispatch character bounds update event=]
      18. +
      19. Set |editContext|'s [=selection start=] to |selectionStart|.
      20. +
      21. Set |editContext|'s [=selection end=] to |selectionEnd|.
      22. +
      23. [=Dispatch text update event=] given |editContext| and |text|.
      24. +
      25. Set |editContext|'s [=composition end=] to |editContext|'s [=composition start=] plus the length of |text|.
      26. +
      27. [=Dispatch text format update event=] given |editContext| and |textFormats|.
      28. +
      29. [=Dispatch character bounds update event=] given |editContext|.

      Update the Text Edit Context

      -

      - This processing step must occur as a substep within the [=Update the rendering=] step in the HTML Event Loops Processing Model. - The step is: Run the [=Update the Text Edit Context=] steps. - It must occur just after substep 13: run the animation frame callbacks steps. -

      Input
      -
      None
      +
      |document|, a {{Document}}
      Output
      None
        -
      1. Let |editContext| be the currently [=active EditContext=].
      2. -
      3. If |editContext| is null, abort these steps.
      4. -
      5. If |editContext|'s [=dirty flag=] is false, abort these steps.
      6. -
      7. If |editContext|'s [=character bounds updated flag=] is false, run the steps to [=Dispatch character bounds update event=].
      8. -
      9. If the [=active EditContext=] is no longer |editContext|, abort these steps.
      10. -
      11. Update the [=Text Edit Context=]'s [=text state=] to match the values in |editContext|'s [=text state=].
      12. +
      13. Let |oldActiveEditContext| be |document|'s [=active EditContext=].
      14. +
      15. Let |newActiveEditContext| be the result of running the steps to [=determine the active EditContext=] given |document|.
      16. +
      17. If |oldActiveEditContext| is not null, then run the steps to [=deactivate an EditContext=] given |oldActiveEditContext|.
      18. +
      19. If |newActiveEditContext| is not null, then: +
          +
        1. Run the steps to [=activate an EditContext=] given |newActiveEditContext|.
        2. +
        3. Update the [=Text Edit Context=]'s [=text state=] to match the values in |editContext|'s [=text state=].
        4. +
        +
      20. +
      21. Set the |document|'s [=active EditContext=] to |newActiveEditContext|.

      Note that the steps to update the [=Text Edit Context=]'s [=text state=] are dependent on the nature of the abstraction created over a platform-specific [=Text Input Service=]. @@ -747,18 +740,19 @@

      Dispatch text update event

      Input
      +
      |editContext|, an {{EditContext}}
      |text|, a string
      Output
      None
      1. - [=Fire an event=] named "textupdate" at the [=active EditContext=] using {{TextUpdateEvent}}, with + [=Fire an event=] named "textupdate" at |editContext| using {{TextUpdateEvent}}, with {{TextUpdateEvent/text}} initialized to |text|, - {{TextUpdateEvent/compositionStart}} initialized to [=composition start=], - {{TextUpdateEvent/compositionEnd}} initialized to [=composition end=], - {{TextUpdateEvent/selectionStart}} initialized to [=selection start=], and - {{TextUpdateEvent/selectionEnd}} initialized to [=selection end=]. + {{TextUpdateEvent/compositionStart}} initialized to |editContext|'s [=composition start=], + {{TextUpdateEvent/compositionEnd}} initialized to |editContext|'s [=composition end=], + {{TextUpdateEvent/selectionStart}} initialized to ||editContext|'s [=selection start=], and + {{TextUpdateEvent/selectionEnd}} initialized to |editContext|'s [=selection end=].
      @@ -767,6 +761,7 @@

      Dispatch text format update event

      Input
      +
      |editContext|, an {{EditContext}}
      |textFormats|, a structure that has an array of text format info from the [=Text Input Service=]
      Output
      None
      @@ -787,7 +782,7 @@

      Dispatch text format update event

  • - [=Fire an event=] named "textformatupdate" at the [=active EditContext=] using {{TextFormatUpdateEvent}} with + [=Fire an event=] named "textformatupdate" at |editContext| using {{TextFormatUpdateEvent}} with the {{TextFormatUpdateEvent}}'s [=text format list=] initialized to |formats|.
  • @@ -797,15 +792,15 @@

    Dispatch character bounds update event

    Input
    -
    None
    +
    |editContext|, an {{EditContext}}
    Output
    None
    1. - [=Fire an event=] named "characterboundsupdate" at the [=active EditContext=] using {{CharacterBoundsUpdateEvent}} with - {{CharacterBoundsUpdateEvent/rangeStart}} initialized to [=composition start=] and - {{CharacterBoundsUpdateEvent/rangeEnd}} initialized to [=composition end=]. + [=Fire an event=] named "characterboundsupdate" at |editContext| using {{CharacterBoundsUpdateEvent}} with + {{CharacterBoundsUpdateEvent/rangeStart}} initialized to |editContext|'s [=composition start=] and + {{CharacterBoundsUpdateEvent/rangeEnd}} initialized to |editContext|'s [=composition end=].
    @@ -844,16 +839,27 @@

    Determine the active EditContext

    Input
    -
    |traversable|, a [=top-level traversable=]
    +
    |document|, a {{Document}}
    Output
    An {{EditContext}}, or null.
      +
    1. Let |traversable| be |document|'s [=node navigable=]'s [=top-level traversable=].
    2. +
    3. If |traversable| is null, return null.
    4. Let |focused| be the DOM anchor of the currently focused area of a top-level traversable given |traversable|.
    5. +
    6. +

      If |focused| is null or if the [=shadow-including root=] of |focused| is not |document|, return null.

      +

      + The purpose of getting |focusable| through the [=top-level traversable=] is that + we want there to be only one [=active EditContext=] at a time per + [=top-level traversable=]. So if system focus is in some other document, this + document can't have an [=active EditContext=]. +

      +
    7. Let |editContext| be null.
    8. While |focused| is not null and |focused| is editable: