CARVIEW |
Selection API
W3C Working Draft
- This version:
- https://www.w3.org/TR/2019/WD-selection-api-20190820/
- Latest published version:
- https://www.w3.org/TR/selection-api/
- Latest editor's draft:
- https://w3c.github.io/selection-api/
- Test suite:
- https://github.com/web-platform-tests/wpt/tree/master/selection
- Previous version:
- https://www.w3.org/TR/2019/WD-selection-api-20190607/
- Editor:
- Ryosuke Niwa (Apple Inc.)
- Participate:
- GitHub w3c/selection-api
- File a bug
- Commit history
- Pull requests
Copyright © 2019 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and permissive document license rules apply.
Abstract
This document is a preliminary draft of a specification for the Selection API and selection related functionality. It replaces a couple of old sections of the HTML specification, the selection part of the old DOM Range specification.
This document defines APIs for selection, which allows users and authors to select a portion of a document or specify a point of interest for copy, paste, and other editing operations.
Status of This Document
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
This is work in progress.
This document was published by the Web Applications Working Group as a Working Draft. This document is intended to become a W3C Recommendation.
GitHub Issues are preferred for discussion of this specification.
Publication as a Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.
This document is governed by the 1 March 2019 W3C Process Document.
1. Background
This section is non-normative.
IE9 and Firefox 6.0a2 allow arbitrary ranges in the selection, which follows what this spec originally said. However, this leads to unpleasant corner cases that authors, implementers, and spec writers all have to deal with, and they don't make any real sense. Chrome 14 dev and Opera 11.11 aggressively normalize selections, like not letting them lie inside empty elements and things like that, but this is also viewed as a bad idea, because it takes flexibility away from authors.
So I changed the spec to a made-up compromise that allows some simplification but doesn't constrain authors much. See discussion. Basically it would throw exceptions in some places to try to stop the selection from containing a range that had a boundary point other than an Element or Text node, or a boundary point that didn't descend from a Document.
But this meant getRangeAt() had to start returning a copy, not a reference. Also, it would be prone to things failing weirdly in corner cases. Perhaps most significantly, all sorts of problems might arise when DOM mutations transpire, like if a boundary point's node is removed from its parent and the mutation rules would place the new boundary point inside a non-Text/Element node. And finally, the previously-specified behavior had the advantage of matching two major implementations, while the new behavior matched no one. So I changed it back.
See bug 15470. IE9, Firefox 12.0a1, Chrome 17 dev, and Opera Next 12.00 alpha all make the range initially null.
2. Definition
Every document with a browsing context has a unique
selection
associated with it.
This is a requirement of the HTML spec. IE9 and Opera Next 12.00 alpha seem to follow it, while Firefox 12.0a1 and Chrome 17 dev seem not to. See Mozilla bug, WebKit bug.
This one selection
must be shared by all the content of the
document (though not by nested documents), including any
editing hosts in the document. Editing hosts that are not
inside a document cannot have a selection
Each selection
can be associated with a single range.
When there is no range associated with the selection
, the
selection is empty. The selection must be initially
empty.
A document's selection
is a singleton object associated
with that document, so it gets replaced with a new object when
Document.open()
is called. See bug 15470.
IE9 and Opera Next 12.00 alpha allow the user to reset the range to
null after the fact by clicking somewhere; Firefox 12.0a1 and Chrome 17
dev do not. We follow Gecko/WebKit, because it lessens the chance of
getRangeAt(0) throwing.
Once a selection
is associated with a given range, it
must continue to be associated with that same range until this
specification requires otherwise.
For instance, if the DOM changes in a way that changes the range's
boundary points, or a script modifies the boundary points of the range,
the same range object must continue to be associated with the
selection. However, if the user changes the selection or a script calls
, the selection must be associated with a new range
object, as required elsewhere in this specification.
addRange
()
If the selection
's range is not null and is
collapsed, then the caret position must be at that
range's boundary point. When the selection
is not
empty, this specification does not define the caret position; user
agents should follow platform conventions in deciding whether the caret
is at the start of the selection
, the end of the
selection
, or somewhere else.
This short-changes Mac users. See bug 13909.
Each selection
has a direction, forwards,
backwards, or directionless. If the user creates
a selection
by indicating first one boundary point of the
range and then the other (such as by clicking on one point and
dragging to another), and the first indicated boundary point is
after the second, then the corresponding
selection
must initially be backwards. If the first
indicated boundary point is before the
second, then the corresponding selection
must initially be
forwards. Otherwise, it must be directionless.
Each selection
s also have an anchor and a
focus. If the selection
's range is null, its
anchor and focus are both null. If the selection
's
range is not null and its direction is forwards,
its anchor is the range's start, and its
focus is the end. Otherwise, its focus is the
start and its anchor is the end.
3. Selection interface
Selection interface provides a way to interact with the
selection
associated with each document.
[Exposed=Window]
interface Selection {
readonly attribute Node? anchorNode
;
readonly attribute unsigned long anchorOffset
;
readonly attribute Node? focusNode
;
readonly attribute unsigned long focusOffset
;
readonly attribute boolean isCollapsed
;
readonly attribute unsigned long rangeCount
;
readonly attribute DOMString type
;
Range getRangeAt
(unsigned long index);
void addRange
(Range range);
void removeRange
(Range range);
void removeAllRanges
();
void empty
();
void collapse
(Node? node, optional unsigned long offset = 0);
void setPosition
(Node? node, optional unsigned long offset = 0);
void collapseToStart
();
void collapseToEnd
();
void extend
(Node node, optional unsigned long offset = 0);
void setBaseAndExtent
(Node anchorNode, unsigned long anchorOffset, Node focusNode, unsigned long focusOffset);
void selectAllChildren
(Node node);
[CEReactions]
void deleteFromDocument
();
boolean containsNode
(Node node, optional boolean allowPartialContainment = false);
stringifier DOMString ();
};
-
anchorNode
-
The attribute must return the anchor node of the context object, or
null
if the anchor is null. -
anchorOffset
-
The attribute must return the anchor offset of the context object, or
0
if the anchor is null. -
focusNode
-
The attribute must return the focus node of the context object, or
null
if the anchor is null. -
focusOffset
-
The attribute must return the focus offset of the context object, or
0
if the focus is null. -
isCollapsed
-
The attribute must return true if and only if the anchor and focus are the same (including if both are null). Otherwise it must return false.
-
rangeCount
-
The attribute must return
0
if the context object is empty, and must return1
otherwise. -
type
-
The attribute must return
"None"
if the context object is empty,"Caret"
if the context object's range is collapsed, and"Range"
otherwise. -
getRangeAt()
method -
The method must throw an
IndexSizeError
exception if index is not0
, or if the context object is empty. Otherwise, it must return a reference to (not a copy of) the context object's range.NoteThus subsequent calls of this method returns the same range object if nothing has removed the context object's range in the meantime. In particular,
getSelection().getRangeAt(0) === getSelection().getRangeAt(0)
evaluates totrue
if theselection
is not empty.NoteIE9 and Firefox 4.0 return the same object every time, as the spec says. Chrome 12 dev and Opera 11.10 return a different object every time.
-
addRange()
method -
The method must follow these steps:
- If the root of the range's boundary points are not the document associated with context object, abort these steps.
- If
rangeCount
is not0
, abort these steps. - Set the context object's range to range by a strong reference (not by making a copy).
NoteSince range is added by reference, subsequent calls to
getRangeAt(0)
returns the same object, and any changes that a script makes to range after it is added must be reflected in theselection
, until something else removes or replaces the context object's range. In particular, theselection
will contain b as opposed to a after running the following code:var r = document.createRange(); r.selectNode(a); getSelection().addRange(r); r.selectNode(b);
-
removeRange()
method -
The method must make the context object empty by disassociating its range if the context object's range is range. Otherwise, it must throw a
NotFoundError
. -
removeAllRanges()
method -
The method must make the context object empty by disassociating its range if the context object has an associated range.
-
empty
-
The method must be an alias, and behave identically, to
removeAllRanges()
. -
collapse()
method -
The method must follow these steps:
- If node is null, this method must behave identically
as
removeAllRanges()
and abort these steps. - The method must throw an
IndexSizeError
exception if offset is longer than node's length and abort these steps. - If node's root is not the document associated with the context object, abort these steps.
- Otherwise, let newRange be a new range.
- Set the start the start and the end of newRange to (node, offset).
- Set the context object's range to newRange.
- If node is null, this method must behave identically
as
-
setPosition()
method -
The method must be an alias, and behave identically, to
collapse()
. -
collapseToStart()
method -
The method must throw
InvalidStateError
exception if the context object is empty. Otherwise, it must create a new range, set the start both its start and end to the start of the context object's range, and then set the context object's range to the newly-created range.NoteFor collapseToStart/End, IE9 mutates the existing range, while Firefox 9.0a2 and Chrome 15 dev replace it with a new one. The spec follows the majority and replaces it with a new one, leaving the old Range object unchanged.
-
collapseToEnd()
method -
The method must throw
InvalidStateError
exception if the context object is empty. Otherwise, it must create a new range, set the start both its start and end to the end of the context object's range, and then set the context object's range to the newly-created range. -
extend()
method -
The method must follow these steps:
- If node's root is not the document associated with the context object, abort these steps.
- If the context object is empty, throw an
InvalidStateError
exception and abort these steps. - Let oldAnchor and oldFocus be the context object's anchor and focus, and let newFocus be the boundary point (node, offset).
- Let newRange be a new range.
- If node's root is not the same as the context object's range's root, set the start newRange's start and end to newFocus.
- Otherwise, if oldAnchor is before or equal to newFocus, set the start newRange's start to oldAnchor, then set its end to newFocus.
- Otherwise, set the start newRange's start to newFocus, then set its end to oldAnchor.
- Set the context object's range to newRange.
- If newFocus is before oldAnchor, set the context object's direction to backwards. Otherwise, set it to forwards.
NoteReverse-engineered circa January 2011. IE doesn't support it, so I'm relying on Firefox (implemented extend() sometime before 2000) and WebKit (implemented extend() in 2007). I'm mostly ignoring Opera, because gsnedders tells me its implementation isn't compatible. Firefox 12.0a1 seems to mutate the existing range. IE9 doesn't support extend(), and it's impossible to tell whether Chrome 17 dev or Opera Next 12.00 alpha mutate or replace, because getRangeAt() returns a copy anyway. Nevertheless, I go against Gecko here, to be consistent with collapse().
-
setBaseAndExtent()
method -
The method must follow these steps:
- If anchorOffset is longer than
anchorNode's length or if
focusOffset is longer than focusNode's
length, throw an
IndexSizeError
exception and abort these steps. - If the roots of anchorNode or focusNode are not the document associated with context object, abort these steps.
- Let anchor be the boundary point (anchorNode, anchorOffset) and let focus be the boundary point (focusNode, focusOffset).
- Let newRange be a new range.
- If anchor is before focus, set the start the newRange's start to anchor and its end to focus. Otherwise, set the start them to focus and anchor respectively.
- Set the context object's range to newRange.
- If focus is before anchor, set context object's direction to backwards. Otherwise, set it to forwards
- If anchorOffset is longer than
anchorNode's length or if
focusOffset is longer than focusNode's
length, throw an
-
selectAllChildren()
method -
The method must follow these steps:
- If node's root is not the document associated with the context object, abort these steps.
- Let newRange be a new range and nodeLength be the length of node.
- Set newRange's start to (node,
0
). - Set newRange's end to (node, nodeLength).
- Set the context object's range to newRange.
- Set the context object's direction to forwards.
NoteBased mostly on Firefox 9.0a2. It has a bug that I didn't reproduce, namely that if you pass a Document as the argument, the end offset becomes 1 instead of the number of children it has. It also throws a RangeException instead of DOMException, because its implementation predated their merging.
IE9 behaves similarly but with glitches. It throws "Unspecified error." if the node is detached or display:none, and apparently in some random other cases too. It throws "Invalid argument." for detached comments (only!). Finally, if you pass it a comment, it seems to select the whole comment, unlike with text nodes.
Chrome 16 dev behaves as you'd expect given its Selection implementation. It refuses to select anything that's not visible, so it's almost always wrong. Opera 11.50 just does nothing in all my tests, as usual.
The new range replaces any existing one, doesn't mutate it. This matches IE9 and Firefox 12.0a1. (Chrome 17 dev and Opera Next 12.00 alpha can't be tested, because getRangeAt() returns a copy anyway.)
-
deleteFromDocument()
method -
The method must invoke
deleteContents()
on the context object's range if the context object is not empty. Otherwise the method must do nothing.NoteThis is the one method that actually mutates the range instead of replacing it. This matches IE9 and Firefox 12.0a1. (Chrome 17 dev and Opera Next 12.00 alpha can't be tested, because getRangeAt() returns a copy anyway.)
-
containsNode()
method -
The method must return
false
if the context object is empty or if node's root is not the document associated with the context object.Otherwise, if allowPartialContainment is
false
, the method must returntrue
if and only if start of its range is before or visually equivalent to the first boundary point in the node and end of its range is after or visually equivalent to the last boundary point in the node.If allowPartialContainment is
true
, the method must returntrue
if and only if start of its range is before or visually equivalent to the first boundary point in the node or end of its range is after or visually equivalent to the last boundary point in the node. - stringifier
- See W3C bug 10583.
See also nsISelection.idl from Gecko. This spec doesn't have everything from there yet, in particular selectionLanguageChange() and containsNode() are missing. They are missing because I couldn't work out how to define them in terms of Ranges.
Originally, the Selection interface was a Netscape feature. The original implementation was carried on into Gecko (Firefox), and the feature was later implemented independently by other browser engines. The Netscape implementation always allowed multiple ranges in a single selection, for instance so the user could select a column of a table However, multi-range selections proved to be an unpleasant corner case that web developers didn't know about and even Gecko developers rarely handled correctly. Other browser engines never implemented the feature, and clamped selections to a single range in various incompatible fashions.
This specification follows non-Gecko engines in restricting
selections to at most one range, but the API was still originally
designed for selections with arbitrary numbers of ranges. This
explains oddities like the coexistence of removeRange()
and removeAllRanges()
, and a getRangeAt()
method that takes an integer argument that must always be zero.
All of the members of the Selection
interface are defined in terms
of operations on the range
object (if any)
represented by the object. These operations can raise exceptions, as
defined for the Range
interface; this can therefore result in the
members of the Selection interface raising exceptions as well,
in addition to any explicitly called out below.
4. Extensions to Other Interfaces
This specification extends several interfaces to provide entry points to the interfaces defined in this specification.
4.1
Extensions to Document
interface
The Document
interface is defined in [HTML].
partial interface Document
{
Selection? getSelection
();
};
-
getSelection()
method -
The method must return the
selection
associated with context object if the context object has an associated browsing context, and it must returnnull
otherwise.
If we create a Document object with no browsing context (say via
document.implementation.createHTMLDocument("")
and call
on it), IE9 seems to return a different Selection
object. Firefox 12.0a1 and Opera Next 12.00 alpha return the same
object as for the current window. Chrome 17 dev returns null. See
discussion. There's no meaningful selection associated with such
a document, so we follow WebKit and require returning getSelection
()null
.
4.2
Extensions to Window
interface
The Window
interface is
defined in [HTML].
partial interface Window
{
Selection? getSelection
();
};
-
getSelection()
method -
The method must invoke and return the result of
on the context object'sgetSelection
()
attribute.Window
.document
4.3
Extensions to GlobalEventHandlers
interface
The GlobalEventHandlers
interface is defined in [HTML].
partial interface mixin GlobalEventHandlers
{
attribute EventHandler onselectstart
;
attribute EventHandler onselectionchange
;
};
-
onselectstart
-
The attribute must be an event handler IDL attribute for the
selectstart
event supported by all HTML elements,
objects, andDocument
objects.Window
-
onselectionchange
-
The attribute must be an event handler IDL attribute for the
selectionchange
event supported by all HTML elements,
objects, andDocument
objects.Window
5. User Interactions
The user agent should allow the user to change the selection
associated with the active document. If the user makes any
modification to a selection
, the user agent must create a new
range with suitable start and end of the
range and associate the selection
with this new
range (not modify the existing range), and set update
selection
's direction to forwards if the
start is before or equal to the
end, backwards if if the end is before the start, or directionless if the
start and the end cannot be ordered due to the
platform convention.
The user agent must not make a selection
empty if it was
not already empty in response to any user actions (e.g. clicking
on a non-editable region).
See bug 15470. IE9 and Opera Next 12.00 alpha allow the user to reset the range to null after the fact by clicking somewhere; Firefox 12.0a1 and Chrome 17 dev do not. I follow Gecko/WebKit, because it lessens the chance of getRangeAt(0) throwing.
5.1
selectstart
event
When the user agent is about to associate a new range
newRange to the selection
in response to a user
initiated action, the user agent must fire an event with the
name selectstart
, which bubbles and is cancelable, at
the node associated with the boundary point
of newRange's start prior to changing the
selection if the selection
was previously empty or the
previously associated range was collapsed.
If the event is canceled, the user agent must not change the
selection
.
The user agent must not fire an event when the user agent sets
the selection
empty.
5.2
selectionchange
event
When the selection
is dissociated with its range,
associated with a new range or the associated range's
boundary point is mutated either by the user or the content
script, the user agent must queue a task to fire an
event with the name selectionchange
, which does not
bubble and is not cancelable, at the document associated with
the selection
.
6. Conformance
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
This specification defines conformance criteria that apply to a single product: the user agent that implements the interfaces that it contains.
A. Acknowledgements
Many thanks to
- Aryeh Gregor, who is the original author of this specification as well as HTML Editing API specification
- Contributors to the HTML Editing API specification - Ehsan Akhgari, Tab Atkins, Mathias Bynens, Tim Down, Markus Ernst, Daniel Glazman, Tali Gregor (née Fuss), Stig Halvorsen, Jeff Harris, Ian Hickson, Cameron Heavon-Jones, Anne van Kesteren, Alfonso Martínez de Lizarrondo, Glenn Maynard, Ms2ger, Robert O'Callahan, Julie Parent, Simon Pieters, Michael A. Puls II, Rich Schwerdtfeger, Jonas Sicking, Henri Sivonen, Smylers, Hallvord R. M. Steen, Roland Steiner, Annie Sullivan, timeless, Ojan Vafai, Brett Zamir, and Boris Zbarsky for their feedback, participation, or other helpful contributions
B. References
B.1 Normative references
- [dom]
- DOM Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://dom.spec.whatwg.org/
- [HTML]
- HTML Standard. Anne van Kesteren; Domenic Denicola; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/
- [webidl]
- Web IDL. Boris Zbarsky. W3C. 15 December 2016. W3C Editor's Draft. URL: https://heycam.github.io/webidl/