Copyright © 2025 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本文件是关于 Selection API 及与选择相关功能规范的初步草案。它取代了 HTML specification 的几节旧章节,以及旧 DOM Range specification 中关于选择的部分。
本文件定义了与选择相关的 API,允许用户和作者选择文档的一部分或指定用于复制、粘贴及其他编辑操作的关注点。
本节描述了在本文件发布时的状态。当前的 W3C 出版物列表以及本技术报告的最新修订可在 W3C technical reports index 的 https://www.w3.org/TR/ 找到。
该工作仍在进行中。
本文件由 Web Editing Working Group 作为工作草案发布,使用 Recommendation track。
作为工作草案的发布并不意味着 W3C 及其成员的认可。
本文件为草案,可能随时被其它文件更新、替换或废弃。不应将本文件作为除正在进行中的工作以外的正式引用来源。
本文件由在 W3C Patent Policy 下运作的小组编写。 W3C 保持一份 与该小组交付物相关的任何专利披露的公开清单; 该页面还包含披露专利的说明。若个人实际知悉其认为包含 必要权利要求 的专利,则必须按照 W3C 专利政策第6节 的规定披露相关信息。
本文件受 2023年11月03日 W3C 流程文档 的约束。
本节为非规范性内容。
IE9 和 Firefox 6.0a2 允许在选择中存在任意范围,这与本规范最初的描述一致。但这会导致作者、实现者和规范作者都必须处理一些棘手的边界情况,而且这些情况其实并没有实际意义。Chrome 14 dev 和 Opera 11.11 强制规范化选择,例如不允许其位于空元素内部等,但这也被认为不是好主意,因为这限制了作者的灵活性。
所以我将规范改为一个折中方案,既允许一定简化,又不怎么限制作者。参见 讨论。基本上会在某些地方抛出异常,以防止选择包含一个不是 Element 或 Text 节点的 边界点,或选择的边界点不属于某个文档。
但这意味着 getRangeAt() 必须开始返回副本而不是引用。同时,某些边缘情况可能会导致奇怪的失败。或许最重要的是,当 DOM 发生变化时可能会出现各种问题,比如如果边界点的节点从其父节点被移除,并且变更规则会将新的边界点放在非 Text/Element 节点内部。最后,先前规定的行为具有与两个主流实现一致的优点,而新的行为却没有任何实现一致。因此,我又改回去了。
参见 bug 15470。 IE9、Firefox 12.0a1、Chrome 17 dev 和 Opera Next 12.00 alpha 都使 range 初始为 null。
这是 HTML 规范的要求。IE9 和 Opera Next 12.00 alpha 好像遵循了这一点,Firefox 12.0a1 和 Chrome 17 dev 则没有。 参见 Mozilla bug、 WebKit bug。
这个选择必须被文档的所有内容共享(但不包括嵌套的文档),包括文档内的任意编辑宿主。
每个选择可与单个范围关联。当范围没有与选择关联时, 选择为空。选择最初必须为 空。
文档的选择是与该文档关联的单例对象,因此在调用
Document.open() 时会被新对象替换。参见 bug 15470。
IE9 和 Opera Next 12.00 alpha 允许用户通过点击操作后将 range 重置为 null;Firefox 12.0a1 和 Chrome 17 dev 则不允许。我们遵循
Gecko/WebKit 的行为,因为这样 getRangeAt(0) 抛异常的概率更低。
一旦选择与指定范围关联,除非本规范要求,否则必须持续与该 范围关联。
例如,如果 DOM 发生变化导致范围的边界点发生改变,或者脚本修改了范围的边界点,则仍必须使用同一个范围对象与选择关联。但如果用户更改选择或者脚本调用addRange(),则该选择必须与新范围对象关联,其他地方有要求。
如果选择的范围不为 null 且已折叠,则插入符号的位置必须在该边界点。当选择未折叠时,本规范未定义插入符号位置;用户代理应遵循平台惯例决定插入符号是在选择的起始、末尾或其它位置。
每个选择有一个方向:正向、 反向或无方向。当用户首先按下一个边界点,再按下另一个(如,点击某一点再拖至另一点)时,如果第一个边界点在第二个之后,则该选择初始为反向。如果第一个边界点在第二个之前,则初始为正向。 否则应为无方向。
当选择的范围被脚本修改(例如调用selectNode(node)),
方向必须被保留。
每个选择还具有锚点与 焦点。如果选择的范围为 null,其锚点和焦点都为 null。 若范围非 null 且方向为正向, 锚点为范围的起点, 焦点为终点。否则 焦点为起点, 锚点为终点。
每个文档、input 元素 和 textarea 元素 有一个布尔值属性has scheduled selectionchange event,其初始为 false。
Selection 接口提供了与每个文档关联的选择交互的方式。
WebIDL[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;
readonly attribute DOMString direction;
Range getRangeAt(unsigned long index);
undefined addRange(Range range);
undefined removeRange(Range range);
undefined removeAllRanges();
undefined empty();
sequence<StaticRange> getComposedRanges(optional GetComposedRangesOptions options = {});
undefined collapse(Node? node, optional unsigned long offset = 0);
undefined setPosition(Node? node, optional unsigned long offset = 0);
undefined collapseToStart();
undefined collapseToEnd();
undefined extend(Node node, optional unsigned long offset = 0);
undefined setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, unsigned long focusOffset);
undefined selectAllChildren(Node node);
undefined modify(optional DOMString alter, optional DOMString direction, optional DOMString granularity);
[CEReactions] undefined deleteFromDocument();
boolean containsNode(Node node, optional boolean allowPartialContainment = false);
stringifier;
};
dictionary GetComposedRangesOptions {
sequence<ShadowRoot> shadowRoots = [];
};
anchorNode
anchorOffset
focusNode
focusOffset
isCollapsed
只有当 anchor 和 focus 相同时(包括两者均为 null 的情况)该属性才应返回 true。否则应返回 false。
rangeCount
type
如果 此对象 为 空,或 focus 或
anchor
任一不在 文档树
中,则该属性必须返回 "None";如果此对象的 range 为 折叠,则返回 "Caret";否则返回
"Range"。
direction
如果 此对象 为 空 或者该选择为
无方向,则该属性必须返回 "none"。如果该选择的方向为 正向,返回 "forward";如果方向为 反向,返回
"backward"。
getRangeAt() method
如果 index 不是 0,或者 此对象 为 空,或 focus 或 anchor 任何一个不在 文档树
中,则该方法必须抛出 IndexSizeError
异常。否则,必须返回对此对象的 范围
的引用(不是副本)。
因此,如果在此期间没有移除此对象的范围,随后对该方法的调用将返回相同的 范围 对象。特别是,当选择非空时,getSelection().getRangeAt(0) ===
getSelection().getRangeAt(0) 的求值应为 true。
addRange() method
该方法必须遵循以下步骤:
removeRange() method
该方法必须通过解除关联其 范围 来使 此对象 变为 空,前提是此对象的范围正是
range。否则,必须抛出 NotFoundError。
removeAllRanges() method
empty() method
该方法必须作为 removeAllRanges() 的别名并且其行为与之完全相同。
getComposedRanges() method
shadowRoots]
的影子包含性祖先时,重复以下步骤:
shadowRoots]
的影子包含性祖先时,重复以下步骤:
StaticRange 组成的数组,
这些 StaticRange 的 起始节点 为
startNode,
起始偏移量
为 startOffset,
结束节点 为
endNode,
以及 结束偏移量 为
endOffset。
collapse() method
该方法必须遵循以下步骤:
removeAllRanges() 完全相同,并中止这些步骤。
DocumentType,则抛出 InvalidNodeTypeError
异常并中止这些步骤。
IndexSizeError
异常并中止这些步骤。
setPosition() method
该方法必须作为 collapse() 的别名并且其行为与之完全相同。
collapseToStart() method
如果 InvalidStateError 异常如果
此对象 为 空,则应抛出该异常。否则,必须创建一个新的 范围,将其 起始 和 结束
都设置为此对象的范围的起始位置,然后将此对象的范围设置为新创建的范围。
对于 collapseToStart/End,IE9 会修改现有范围,而 Firefox 9.0a2 和 Chrome 15 dev 会替换为新的范围。规范遵循多数实现,使用新的范围替换并保持旧的 Range 对象不变。
collapseToEnd() method
如果 InvalidStateError 异常如果
此对象 为 空,则应抛出该异常。否则,必须创建一个新的 范围,将其 起始 和 结束
都设置为此对象的范围的结束位置,然后将此对象的范围设置为新创建的范围。
extend() method
该方法必须遵循以下步骤:
InvalidStateError
并中止这些步骤。
约在 2011 年 1 月进行的逆向工程。IE 不支持该方法,因此我依赖 Firefox(在 2000 年之前实现了 extend())和 WebKit(2007 年实现 extend())。我基本上忽略 Opera,因为其实现据说不兼容。Firefox 12.0a1 似乎会修改现有范围。IE9 不支持 extend(),而 Chrome 17 dev 或 Opera Next 12.00 alpha 是否修改或替换无法判断,因为 getRangeAt() 无论如何都会返回副本。尽管如此,为与 collapse() 保持一致,我在这里不采用 Gecko 的行为。
setBaseAndExtent() method
该方法必须遵循以下步骤:
IndexSizeError
异常并中止这些步骤。
selectAllChildren() method
该方法必须遵循以下步骤:
DocumentType,则抛出 InvalidNodeTypeError
并中止这些步骤。
0)。
主要基于 Firefox 9.0a2。它有一个我没有复现的 bug:如果传入的是 Document,结束偏移量会变为 1 而不是它的子节点数。它还抛出 RangeException 而不是 DOMException,因为其实现早于两者合并。
IE9 的行为类似但有瑕疵。若节点被分离或 display:none 时会抛出“Unspecified error.”,在某些随机情况下也会如此。对于分离的注释节点它会抛出“Invalid argument.”(仅此一种情况)。最后,如果传入注释节点,它似乎会选择整个注释,而不像对文本节点那样只选择部分内容。
Chrome 16 dev 的行为符合其 Selection 实现的预期。它拒绝选择任何不可见的内容,因此在很多情况下表现不正确。Opera 11.50 在我的所有测试中什么都不做,一如既往。
新范围会替换任何现有范围,而不是修改它。这与 IE9 和 Firefox 12.0a1 相匹配。(Chrome 17 dev 和 Opera Next 12.00 alpha 无法测试,因为 getRangeAt() 无论如何返回副本。)
modify() method
该方法必须遵循以下步骤:
我们需要更精确定义按每种粒度扩展或移动选择意味着什么。
deleteFromDocument() method
如果此对象非 空
且 focus
与 anchor 均在 文档树 中,则该方法必须在此对象的 范围 上调用 deleteContents()。否则该方法不得执行任何操作。
这是唯一一个实际修改范围而不是替换它的方法。这与 IE9 和 Firefox 12.0a1 一致。(Chrome 17 dev 和 Opera Next 12.00 alpha 无法测试,因为 getRangeAt() 无论如何返回副本。)
containsNode() method
如果 此对象 为 空,或
node 的 根 不是与 此对象 关联的文档,则该方法必须返回 false。
否则,如果 allowPartialContainment 为 false,当且仅当其 范围 的 起始 在 node
中的第一个边界点之前或视觉等价且其范围的 结束 在 node
中的最后一个边界点之后或视觉等价时,该方法返回 true。
如果 allowPartialContainment 为 true,当且仅当其范围的 起始 在 node
中的最后一个边界点之前或视觉等价且其范围的 结束 在 node
中的第一个边界点之后或视觉等价时,该方法返回 true。
stringifier
字符串化必须返回一个字符串,该字符串是与此对象关联的 范围 的呈现文本的连接结果。
另见来自 Gecko 的 nsISelection.idl。本规范尚未包含那里的所有内容,特别是 selectionLanguageChange() 和 containsNode() 尚缺失。它们缺失的原因是我无法弄清如何用 Range 来定义它们。
最初,Selection 接口是一个 Netscape 的特性。其原始实现被带入到 Gecko(Firefox),随后该特性被其他浏览器引擎独立实现。Netscape 的实现始终允许在单个选择中存在多个范围,例如用户可以选择表格的一列。然而,多范围选择证明是一个让 Web 开发者不易察觉并且即使是 Gecko 的开发者也很少正确处理的不愉快的边界情况。其他浏览器引擎从未实现该特性,并以各种不兼容的方式将选择限制为单个范围。
本规范遵循非 Gecko 引擎,将选择限制为最多一个范围,但该 API 最初仍为支持任意数量范围的选择而设计。这解释了像 removeRange() 与
removeAllRanges() 并存以及 getRangeAt() 方法接受一个必须始终为零的整数参数等奇怪之处。
所有 Selection 接口的成员都是以该对象所表示的
range
对象(如果有的话)上的操作来定义的。这些操作可能会引发异常,如在 Range 接口的定义中所述;因此,这也可能导致 Selection 接口的成员抛出异常,除上文明确指出的情况外。
本规范扩展了若干接口以提供到本规范中定义的接口的入口点。
WebIDLpartial interface Document {
Selection? getSelection();
};
WebIDLpartial interface Window {
Selection? getSelection();
};
getSelection() method
该方法必须调用并返回 getSelection() 在 此对象 的 Window
的 document
属性上的结果。
GlobalEventHandlers
接口在 [HTML] 中定义。
WebIDLpartial interface mixin GlobalEventHandlers {
attribute EventHandler onselectstart;
attribute EventHandler onselectionchange;
};
onselectstart
该属性必须是一个针对 selectstart 事件的 事件处理器
IDL 属性,该事件由所有 HTML
元素、Document 对象和 Window
对象支持。
onselectionchange
该属性必须是一个针对 selectionchange 事件的 事件处理器
IDL 属性,该事件由所有 HTML
元素、Document 对象和 Window
对象支持。
当用户代理在 CharacterData 上替换数据或截取子数据时,用户代理必须像操作活动范围一样,更新该CharacterData的节点文档的选择关联的范围。
当用户代理要拆分 Text 节点时,用户代理必须像操作活动范围一样,更新该Text的节点文档的选择关联的范围。
当用户代理执行 normalize() 方法步骤时,用户代理必须像操作活动范围一样,更新 此的节点文档的选择关联的范围。
用户代理应允许用户更改与选择 关联的活动文档。 如果用户对选择进行了任何修改,用户代理必须创建一个具有合适起始和结束的新的范围,并将选择与这个新范围关联(而不是修改现有的范围),并根据以下规则设置和更新选择的方向:如果起始小于或等于结束则设为正向;如果结束小于起始则设为反向;如果由于平台惯例无法判断起始和结束的顺序则设为无方向。
对于任何用户操作(如点击非可编辑区域),如果选择原本非空,用户代理不得将其设为空。
见bug 15470。IE9 和 Opera Next 12.00 alpha 允许用户点击后将范围重置为 null;Firefox 12.0a1 和 Chrome 17 dev 则不会。我遵循 Gecko/WebKit,这样 getRangeAt(0) 抛出异常的机会更小。
当用户代理准备在响应用户操作时将新的 newRange 关联到 选择上时,如果之前的 选择是空或原关联范围是折叠状态,则必须在更改选择前,于与
newRange 的边界点相关的节点上,触发一个冒泡且可取消的名为selectstart的事件。
如果该事件被取消,则用户代理不得更改选择。
当选择与其范围解除关联、关联新的范围,或关联的范围的边界点被用户或内容脚本修改时,用户代理必须在文档上调度 selectionchange 事件。
当
input
或
textarea
元素提供文本选择,且其选择(范围或方向)发生变化时,用户代理必须在该元素上调度
selectionchange 事件。
在 节点 target 上调度 selectionchange 事件,按以下步骤操作:
在节点 target 上触发 selectionchange 事件,按以下步骤操作:
selectionchange、可冒泡但不可取消的事件。
selectionchange、不可冒泡且不可取消的事件。
本规范中标记为非规范性(non-normative)的部分,以及所有作者指南、图示、示例和说明都是非规范性的。本规范中其余内容均为规范性内容。
本规范定义了适用于单一产品的符合性标准:实现本规范所含接口的用户代理。
目前该标准未发现已知的安全性考量。
为减轻暴露用户辅助技术使用情况的潜在隐私风险,例如,用户代理可选择在用户修改文档选择时,模拟通常与selectstart或selectionchange事件有关的鼠标和键盘事件。
特别感谢
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: