Skip to content

Add OpaqueRange interface#1404

Open
stephanieyzhang wants to merge 17 commits into
whatwg:mainfrom
stephanieyzhang:fcr-initial
Open

Add OpaqueRange interface#1404
stephanieyzhang wants to merge 17 commits into
whatwg:mainfrom
stephanieyzhang:fcr-initial

Conversation

@stephanieyzhang
Copy link
Copy Markdown

@stephanieyzhang stephanieyzhang commented Sep 12, 2025

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 so startContainer/endContainer are nullable (Node?), which allows OpaqueRange to return null for those getters while Range/StaticRange continue returning nodes.

(See WHATWG Working Mode: Changes for more details.)


Preview | Diff

@stephanieyzhang stephanieyzhang changed the title initial changes Add FormControlRange interface Sep 23, 2025
Copy link
Copy Markdown
Contributor

@dandclark dandclark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to see a spec for this coming together!

Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs
@rniwa
Copy link
Copy Markdown

rniwa commented Sep 29, 2025

FWIW, I'd rather extend Range so that it can work with shadow host in general instead of creating a new interface specifically for form controls.

@stephanieyzhang
Copy link
Copy Markdown
Author

FWIW, I'd rather extend Range so that it can work with shadow host in general instead of creating a new interface specifically for form controls.

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.

@rniwa
Copy link
Copy Markdown

rniwa commented Sep 30, 2025

FWIW, I'd rather extend Range so that it can work with shadow host in general instead of creating a new interface specifically for form controls.

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 setStart/setEnd wouldn't be an only way to solve this problem. I'd instead add a new argument or a new function which specifies "offset" within element's shadow tree.

Copy link
Copy Markdown
Contributor

@dandclark dandclark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
<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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this, too, will likely need to be something that we'll need to put in the HTML spec)

Comment thread dom.bs Outdated
@dandclark
Copy link
Copy Markdown
Contributor

Changing the behavior of setStart/setEnd wouldn't be an only way to solve this problem. I'd instead add a new argument or a new function which specifies "offset" within element's shadow tree.

@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 <textarea>/<input> and another boundary point outside, which would be a clumsier invariant to express with the Range API surface. Adding this to Range would also add confusion about what things like Range.selectNodeContents should do; should it move the range to select an <input>'s value string, or not? We could add a parameter to all the setter functions to switch the behavior, but again it seems cleaner to relegate the behavior to a different interface vs adding parameters to a bunch of Range functions.

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.

@stephanieyzhang stephanieyzhang changed the title Add FormControlRange interface Add PlainTextRange interface Dec 4, 2025
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
Comment thread dom.bs Outdated
@stephanieyzhang stephanieyzhang changed the title Add PlainTextRange interface Add OpaqueRange interface Jan 14, 2026
Comment thread dom.bs
interface OpaqueRange : AbstractRange {
DOMRectList getClientRects();
DOMRect getBoundingClientRect();
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why OpaqueRange offsets are readonly? Or there isn't any method to update them?

Copy link
Copy Markdown
Author

@stephanieyzhang stephanieyzhang Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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----

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dom.bs
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Feb 20, 2026
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}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Feb 20, 2026
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}
lando-worker Bot pushed a commit to mozilla-firefox/firefox that referenced this pull request Feb 26, 2026
…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
@stephanieyzhang stephanieyzhang marked this pull request as ready for review April 23, 2026 16:07
stephanieyzhang and others added 2 commits April 27, 2026 16:57
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Comment thread dom.bs
<dfn export id=concept-range-end-offset for=range>end offset</dfn> is its <a for=range>end</a>'s
<a for="boundary point">offset</a>.

<p>A <a>range</a> has an associated <dfn export for=range id=concept-range-is-opaque>is opaque</dfn>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

associated boolean*

We don't want flags anymore.

Comment thread dom.bs
Comment on lines +8698 to +8700
flag, initially false. When true, the {{AbstractRange/startContainer}} and
{{AbstractRange/endContainer}} getters return null. Only {{OpaqueRange}} objects have this flag set
to true.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This second bit should probably be in a note.

Comment thread dom.bs
interface AbstractRange {
readonly attribute Node startContainer;
readonly attribute Node? startContainer;
readonly attribute unsigned long startOffset;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dom.bs
</pre>

<p>Objects implementing the {{OpaqueRange}} interface are known as {{OpaqueRange}} objects.
{{OpaqueRange}} objects cannot be constructed directly; they are created by specifications defining
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we would have a constructor so people can create these for custom elements?

Comment thread dom.bs
interface OpaqueRange : AbstractRange {
DOMRectList getClientRects();
DOMRect getBoundingClientRect();
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dom.bs

<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}}.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dom.bs

<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}}.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why we need this concept.

Comment thread dom.bs

<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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@annevk
Copy link
Copy Markdown
Member

annevk commented May 8, 2026

It also seems this needs some rebasing.

Sheikhharis50 pushed a commit to Sheikhharis50/neurobrowser that referenced this pull request May 16, 2026
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}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants