Add OpaqueRange interface#1404
Conversation
dandclark
left a comment
There was a problem hiding this comment.
Great to see a spec for this coming together!
|
FWIW, I'd rather extend |
We explored extending Range, including shadow host support, but found compatibility and encapsulation issues that made it risky to change existing Range semantics. Based on feedback across discussions, we went with a dedicated FormControlRange instead. The trade-offs and alternatives are covered in the explainer. |
Changing the behavior of |
dandclark
left a comment
There was a problem hiding this comment.
This is looking good! Next big steps will be defining the right integration points from the HTML spec, and working towards consensus on the API shape.
| <a>this</a>'s <a for=FormControlRange>end offset</a> to <var>endOffset</var>. | ||
| </ol> | ||
|
|
||
| <p>If an {{HTMLInputElement}}'s <code>type</code> changes to a type that does not |
There was a problem hiding this comment.
(this, too, will likely need to be something that we'll need to put in the HTML spec)
@rniwa Thanks for the feedback! Yeah, there are a lot of ways this could be done. This suggestion sounds closer to the 2nd considered approach here. There are enough differences in how a Range inside a builtin element would work vs a "normal" range that we believe it will be overall cleaner and less confusing to split the functionality into a different type. For example the behavior we propose here is that a FormControlRange can't have one boundary point inside a Anyway, whatwg/html#11478 is a better place to have this discussion -- more folks are following that issue vs this draft PR. @stephanieyzhang it might be helpful if you could update the PR description for this to point to that issue, as well as a direct link to the explainer. |
| interface OpaqueRange : AbstractRange { | ||
| DOMRectList getClientRects(); | ||
| DOMRect getBoundingClientRect(); | ||
| }; |
There was a problem hiding this comment.
Is there a reason why OpaqueRange offsets are readonly? Or there isn't any method to update them?
There was a problem hiding this comment.
The offsets are readonly since they're inherited from AbstractRange. Currently the HTML PR defines createValueRange(start, end) on the element for creation. We haven't yet added an API for updating, so that's an open question. Some options could be an element-side API (e.g. updateValueRange(range, start, end)) to keep OpaqueRange generic, or setters directly on OpaqueRange.
Would appreciate your thoughts @annevk @smaug----
There was a problem hiding this comment.
I think we actually don't want to expose the offsets as per my other comment. I'm not sure if we want to expose collapsed. Perhaps it should just be separate from AbstractRange.
Implement disconnect() per WHATWG DOM spec PR #1404 [0]. Detaches the range from its element, stopping live offset updates and zeroing offsets. Calling it multiple times is safe and has no additional effect. Adds WPT tests covering disconnect behavior. Automatic disconnection when the element is removed from the tree or changes type will be handled in a follow-up CL. [0] whatwg/dom#1404 (comment) Low-Coverage-Reason: COVERAGE_UNDERREPORTED Bug: 421421332 Change-Id: I6f0bb55d57f1eb1876779faad018a19d23b1d665 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7595177 Reviewed-by: Dan Clark <daniec@microsoft.com> Commit-Queue: Stephanie Zhang <stephanie.zhang@microsoft.com> Reviewed-by: Ana Sollano Kim <ansollan@microsoft.com> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1588113}
Implement disconnect() per WHATWG DOM spec PR #1404 [0]. Detaches the range from its element, stopping live offset updates and zeroing offsets. Calling it multiple times is safe and has no additional effect. Adds WPT tests covering disconnect behavior. Automatic disconnection when the element is removed from the tree or changes type will be handled in a follow-up CL. [0] whatwg/dom#1404 (comment) Low-Coverage-Reason: COVERAGE_UNDERREPORTED Bug: 421421332 Change-Id: I6f0bb55d57f1eb1876779faad018a19d23b1d665 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7595177 Reviewed-by: Dan Clark <daniec@microsoft.com> Commit-Queue: Stephanie Zhang <stephanie.zhang@microsoft.com> Reviewed-by: Ana Sollano Kim <ansollan@microsoft.com> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1588113}
…estonly Automatic update from web-platform-tests Add OpaqueRange disconnect() method Implement disconnect() per WHATWG DOM spec PR #1404 [0]. Detaches the range from its element, stopping live offset updates and zeroing offsets. Calling it multiple times is safe and has no additional effect. Adds WPT tests covering disconnect behavior. Automatic disconnection when the element is removed from the tree or changes type will be handled in a follow-up CL. [0] whatwg/dom#1404 (comment) Low-Coverage-Reason: COVERAGE_UNDERREPORTED Bug: 421421332 Change-Id: I6f0bb55d57f1eb1876779faad018a19d23b1d665 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7595177 Reviewed-by: Dan Clark <daniec@microsoft.com> Commit-Queue: Stephanie Zhang <stephanie.zhang@microsoft.com> Reviewed-by: Ana Sollano Kim <ansollan@microsoft.com> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1588113} -- wpt-commits: 60a655bbbf88ba39ae3959de9e405c4284dd0eba wpt-pr: 57954
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
| interface AbstractRange { | ||
| readonly attribute Node startContainer; | ||
| readonly attribute Node? startContainer; | ||
| readonly attribute unsigned long startOffset; |
There was a problem hiding this comment.
Does it make sense to expose startOffset and endOffset if this range potentially spans multiple nodes? They will not have meaningful values I think for opaque ranges.
There was a problem hiding this comment.
For form controls, they're indices into the value string, even when the range spans multiple nodes. I'm unsure of how this would look for custom elements though.
I've gotten feedback from people testing OpaqueRange on Chromium (e.g. Microsoft Editor SDK) that they rely on the offsets, using value.substring(range.startOffset, range.endOffset) in place of toString(), and comparing the caret index against the offsets to test inside/outside. Dropping them would require a workaround.
There was a problem hiding this comment.
I think that means we need a valueFromRange() API or some such on input and textarea. Since the input element or textarea element allocated the OpaqueRange they will know what it contains and can thus answer such a question (if they want to). That would correctly preserve encapsulation and keep the design generic.
And as this range will have to point to the conceptual Text node of the element, the element can naturally only answer the API call for ranges that it created itself.
| </pre> | ||
|
|
||
| <p>Objects implementing the {{OpaqueRange}} interface are known as {{OpaqueRange}} objects. | ||
| {{OpaqueRange}} objects cannot be constructed directly; they are created by specifications defining |
There was a problem hiding this comment.
I thought we would have a constructor so people can create these for custom elements?
There was a problem hiding this comment.
For custom elements, I was thinking something like ElementInternals.createOpaqueRange(...) instead of a constructor. It ties creation to the element that owns the value (mirroring createValueRange() on form controls) and avoids having a "host-less" OpaqueRange.
There was a problem hiding this comment.
We can have a constructor with a mandatory argument?
There was a problem hiding this comment.
To make sure I'm understanding things correctly and that we're aligned:
- Are we keeping
createValueRange()on<input>and<textarea>as the vending API for form controls? - And then adding
new OpaqueRange({startContainer: node, ...})as a separate constructor for custom elements? - Where does
ElementInternals.createOpaqueRange(...)fit, if at all? Or do authors just use the constructor directly for custom-elements? - For
<input>/<textarea>specifically, which API would devs be expected to use:createValueRange()or the constructor?
There was a problem hiding this comment.
- Yes, that makes sense to me.
- Yes.
- We should not need anything on
ElementInternals. Web developers can use the constructor and expose APIs such ascreateValueRange()on their elements as they see fit. Including the more complicated ranges that expose more than just a (conceptual)Textnode. - They can't use the constructor because they don't have access to the conceptual
Textnode. Onlyinputandtextareahave access to that information and can thus create the range. As such web developers will have to go throughcreateValueRange().
There was a problem hiding this comment.
What does creating a new OpaqueRange(...) really mean? How would a custom element indicate what kind of value getClientRects as an example should return.
(Hmm, is OpaqueRange kind of a proxy object, and then there should be the other side of the thing which affects how it behaves. In case of input and textarea, that other side is native and with Custom Elements it would need to be JS based, maybe. Just mumbling aloud.)
There was a problem hiding this comment.
Yeah perhaps we need to go having an OpaqueRangeController (or OpaqueRangeInternals) directly if we offer a constructor. Basically an object that allows you to manipulate the internal values. Either using the revealing constructor pattern or doing something similar to AbortController. I was initially thinking that it might be sufficient if you can just indicate start and end and have the remainder fall out of it being live, but maybe not.
| interface OpaqueRange : AbstractRange { | ||
| DOMRectList getClientRects(); | ||
| DOMRect getBoundingClientRect(); | ||
| }; |
There was a problem hiding this comment.
I think we actually don't want to expose the offsets as per my other comment. I'm not sure if we want to expose collapsed. Perhaps it should just be separate from AbstractRange.
|
|
||
| <p>An {{OpaqueRange}} has an | ||
| <dfn export for=OpaqueRange>associated element</dfn> (an {{Element}} or null), initially null. It is | ||
| set by the specification that creates the {{OpaqueRange}}. |
There was a problem hiding this comment.
Why do we need this and disconnect()? Can't that be with the APIs that vend OpaqueRange objects? It's also a bit unclear what disconnecting means beyond that, maybe in part because this doesn't actually integrate with the tree mutation algorithms yet.
|
|
||
| <p>An {{Element}} | ||
| <dfn export id=supports-opaque-range>supports opaque ranges</dfn> if its specification defines that | ||
| it does. In HTML, this includes certain {{HTMLInputElement}} types and {{HTMLTextAreaElement}}. |
There was a problem hiding this comment.
I'm not sure why we need this concept.
|
|
||
| <p class=note>Other specifications can designate additional elements, including custom elements. | ||
|
|
||
| <p>An {{OpaqueRange}} is live, meaning its offsets are automatically updated when the underlying |
There was a problem hiding this comment.
I think we should define this very differently. We should define opaque ranges the same way we define live ranges. And then in HTML we should say that these elements have some kind of underlying Text node that this opaque range "exposes".
The reason for that is because we want opaque ranges to be a generic container. And the way you have specified them they are still very much restricted to specific elements.
|
It also seems this needs some rebasing. |
Implement disconnect() per WHATWG DOM spec PR #1404 [0]. Detaches the range from its element, stopping live offset updates and zeroing offsets. Calling it multiple times is safe and has no additional effect. Adds WPT tests covering disconnect behavior. Automatic disconnection when the element is removed from the tree or changes type will be handled in a follow-up CL. [0] whatwg/dom#1404 (comment) Low-Coverage-Reason: COVERAGE_UNDERREPORTED Bug: 421421332 Change-Id: I6f0bb55d57f1eb1876779faad018a19d23b1d665 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7595177 Reviewed-by: Dan Clark <daniec@microsoft.com> Commit-Queue: Stephanie Zhang <stephanie.zhang@microsoft.com> Reviewed-by: Ana Sollano Kim <ansollan@microsoft.com> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1588113}
OpaqueRange is a specialized, live AbstractRange subtype whose boundary points reference internal nodes within host-defined elements (e.g.,
<input>/<textarea>today, with a path to custom elements in the future). It enables range-based operations over encapsulated content while avoiding exposure of internal DOM nodes. This PR also updates AbstractRange sostartContainer/endContainerare nullable (Node?), which allows OpaqueRange to return null for those getters while Range/StaticRange continue returning nodes.Explainer: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/OpaqueRange/explainer.md
WHATWG Discussion: OpaqueRange Interface html#11478
Corresponding HTML Spec PR: Integrate OpaqueRange hooks into text controls (input/textarea) html#11741
Corresponding CSSOM PRs:
At least two implementers are interested (and none opposed):
Tests are written and can be reviewed and commented upon at:
Implementation bugs are filed:
MDN issue is filed: …
The top of this comment includes a clear commit message to use.
(See WHATWG Working Mode: Changes for more details.)
💥 Error: 422 Unprocessable Entity 💥
PR Preview failed to build. (Last tried on May 18, 2026, 4:25 PM UTC).
More
PR Preview relies on a number of web services to run. There seems to be an issue with the following one:
🚨 Spec Generator - Spec Generator is the web service used to build bikeshed/ReSpec specs
🔗 Related URL
Error output:
[ { "lineNum": null, "messageType": "warning", "text": "Multiple elements have the same id 'concept-range-is-opaque':\n <dfn> on line 8691:37, <dfn> on line 8707:37\nDeduping, but this ID may not be stable across revisions." }, { "lineNum": "8707:37", "messageType": "fatal", "text": "Multiple local 'dfn' <dfn>s for 'range' have the same linking text 'is opaque'." }, { "lineNum": null, "messageType": "failure", "text": "Did not generate, due to errors exceeding the allowed error level." } ]This seems to be an issue with the Spec Generator service. PR Preview doesn't manage this service and so has no control over it. If you've identified an issue with it, you can report the issue to the maintainers of Spec Generator directly. Please be courteous. Thank you!
If you don't have enough information above to solve the error by yourself or if the issue doesn't seem related to Spec Generator, you can file an issue with PR Preview.