aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom')
-rw-r--r--components/script/dom/document.rs18
-rw-r--r--components/script/dom/macros.rs2
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/range.rs54
-rw-r--r--components/script/dom/selection.rs515
-rw-r--r--components/script/dom/webidls/Document.webidl6
-rw-r--r--components/script/dom/webidls/EventHandler.webidl6
-rw-r--r--components/script/dom/webidls/Selection.webidl32
-rw-r--r--components/script/dom/webidls/Window.webidl6
-rw-r--r--components/script/dom/window.rs6
10 files changed, 646 insertions, 0 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 902463b7d10..24254ed1efe 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -79,6 +79,7 @@ use crate::dom::pagetransitionevent::PageTransitionEvent;
use crate::dom::processinginstruction::ProcessingInstruction;
use crate::dom::promise::Promise;
use crate::dom::range::Range;
+use crate::dom::selection::Selection;
use crate::dom::servoparser::ServoParser;
use crate::dom::shadowroot::ShadowRoot;
use crate::dom::storageevent::StorageEvent;
@@ -398,6 +399,8 @@ pub struct Document {
/// https://html.spec.whatwg.org/multipage/#concept-document-csp-list
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
csp_list: DomRefCell<Option<CspList>>,
+ /// https://w3c.github.io/slection-api/#dfn-selection
+ selection: MutNullableDom<Selection>,
}
#[derive(JSTraceable, MallocSizeOf)]
@@ -2816,6 +2819,7 @@ impl Document {
media_controls: DomRefCell::new(HashMap::new()),
dirty_webgl_contexts: DomRefCell::new(HashMap::new()),
csp_list: DomRefCell::new(None),
+ selection: MutNullableDom::new(None),
}
}
@@ -4481,6 +4485,11 @@ impl DocumentMethods for Document {
// TODO: https://github.com/servo/servo/issues/21936
Node::replace_all(None, self.upcast::<Node>());
+ // Specs and tests are in a state of flux about whether
+ // we want to clear the selection when we remove the contents;
+ // WPT selection/Document-open.html wants us to not clear it
+ // as of Feb 1 2020
+
// Step 12
if self.is_fully_active() {
let mut new_url = entry_responsible_document.url();
@@ -4653,6 +4662,15 @@ impl DocumentMethods for Document {
None => Err(Error::InvalidAccess),
}
}
+
+ // https://w3c.github.io/selection-api/#dom-document-getselection
+ fn GetSelection(&self) -> Option<DomRoot<Selection>> {
+ if self.has_browsing_context {
+ Some(self.selection.or_init(|| Selection::new(self)))
+ } else {
+ None
+ }
+ }
}
fn update_with_current_time_ms(marker: &Cell<u64>) {
diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs
index 21124fd37c4..1abf3056e95 100644
--- a/components/script/dom/macros.rs
+++ b/components/script/dom/macros.rs
@@ -488,6 +488,8 @@ macro_rules! global_event_handlers(
event_handler!(seeked, GetOnseeked, SetOnseeked);
event_handler!(seeking, GetOnseeking, SetOnseeking);
event_handler!(select, GetOnselect, SetOnselect);
+ event_handler!(selectionchange, GetOnselectionchange, SetOnselectionchange);
+ event_handler!(selectstart, GetOnselectstart, SetOnselectstart);
event_handler!(show, GetOnshow, SetOnshow);
event_handler!(stalled, GetOnstalled, SetOnstalled);
event_handler!(submit, GetOnsubmit, SetOnsubmit);
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index 8636ede66ac..2e2e8d9a35d 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -477,6 +477,7 @@ pub mod rtcpeerconnectioniceevent;
pub mod rtcsessiondescription;
pub mod rtctrackevent;
pub mod screen;
+pub mod selection;
pub mod serviceworker;
pub mod serviceworkercontainer;
pub mod serviceworkerglobalscope;
diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs
index e9e62a9cdc0..1aabc0fc540 100644
--- a/components/script/dom/range.rs
+++ b/components/script/dom/range.rs
@@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeConstants;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
@@ -24,6 +25,7 @@ use crate::dom::documentfragment::DocumentFragment;
use crate::dom::element::Element;
use crate::dom::htmlscriptelement::HTMLScriptElement;
use crate::dom::node::{Node, ShadowIncluding, UnbindContext};
+use crate::dom::selection::Selection;
use crate::dom::text::Text;
use crate::dom::window::Window;
use dom_struct::dom_struct;
@@ -37,6 +39,16 @@ pub struct Range {
reflector_: Reflector,
start: BoundaryPoint,
end: BoundaryPoint,
+ // A range that belongs to a Selection needs to know about it
+ // so selectionchange can fire when the range changes.
+ // A range shouldn't belong to more than one Selection at a time,
+ // but from the spec as of Feb 1 2020 I can't rule out a corner case like:
+ // * Select a range R in document A, from node X to Y
+ // * Insert everything from X to Y into document B
+ // * Set B's selection's range to R
+ // which leaves R technically, and observably, associated with A even though
+ // it will fail the same-root-node check on many of A's selection's methods.
+ associated_selections: DomRefCell<Vec<Dom<Selection>>>,
}
impl Range {
@@ -50,6 +62,7 @@ impl Range {
reflector_: Reflector::new(),
start: BoundaryPoint::new(start_container, start_offset),
end: BoundaryPoint::new(end_container, end_offset),
+ associated_selections: DomRefCell::new(vec![]),
}
}
@@ -163,6 +176,9 @@ impl Range {
// https://dom.spec.whatwg.org/#concept-range-bp-set
fn set_start(&self, node: &Node, offset: u32) {
+ if &self.start.node != node || self.start.offset.get() != offset {
+ self.report_change();
+ }
if &self.start.node != node {
if self.start.node == self.end.node {
node.ranges().push(WeakRef::new(&self));
@@ -178,6 +194,9 @@ impl Range {
// https://dom.spec.whatwg.org/#concept-range-bp-set
fn set_end(&self, node: &Node, offset: u32) {
+ if &self.end.node != node || self.end.offset.get() != offset {
+ self.report_change();
+ }
if &self.end.node != node {
if self.end.node == self.start.node {
node.ranges().push(WeakRef::new(&self));
@@ -228,6 +247,26 @@ impl Range {
// Step 6.
Ok(Ordering::Equal)
}
+
+ pub fn associate_selection(&self, selection: &Selection) {
+ let mut selections = self.associated_selections.borrow_mut();
+ if !selections.iter().any(|s| &**s == selection) {
+ selections.push(Dom::from_ref(selection));
+ }
+ }
+
+ pub fn disassociate_selection(&self, selection: &Selection) {
+ self.associated_selections
+ .borrow_mut()
+ .retain(|s| &**s != selection);
+ }
+
+ fn report_change(&self) {
+ self.associated_selections
+ .borrow()
+ .iter()
+ .for_each(|s| s.queue_selectionchange_task());
+ }
}
impl RangeMethods for Range {
@@ -821,6 +860,9 @@ impl RangeMethods for Range {
// Step 3.
if start_node == end_node {
if let Some(text) = start_node.downcast::<CharacterData>() {
+ if end_offset > start_offset {
+ self.report_change();
+ }
return text.ReplaceData(start_offset, end_offset - start_offset, DOMString::new());
}
}
@@ -1142,9 +1184,11 @@ impl WeakRangeVec {
entry.remove();
}
if &range.start.node == child {
+ range.report_change();
range.start.set(context.parent, offset);
}
if &range.end.node == child {
+ range.report_change();
range.end.set(context.parent, offset);
}
});
@@ -1169,9 +1213,11 @@ impl WeakRangeVec {
entry.remove();
}
if &range.start.node == node {
+ range.report_change();
range.start.set(sibling, range.StartOffset() + length);
}
if &range.end.node == node {
+ range.report_change();
range.end.set(sibling, range.EndOffset() + length);
}
});
@@ -1212,9 +1258,11 @@ impl WeakRangeVec {
}
if move_start {
+ range.report_change();
range.start.set(child, new_offset);
}
if move_end {
+ range.report_change();
range.end.set(child, new_offset);
}
});
@@ -1273,9 +1321,11 @@ impl WeakRangeVec {
}
if move_start {
+ range.report_change();
range.start.set(sibling, start_offset - offset);
}
if move_end {
+ range.report_change();
range.end.set(sibling, end_offset - offset);
}
});
@@ -1289,9 +1339,11 @@ impl WeakRangeVec {
(*self.cell.get()).update(|entry| {
let range = entry.root().unwrap();
if &range.start.node == node && offset == range.StartOffset() {
+ range.report_change();
range.start.set_offset(offset + 1);
}
if &range.end.node == node && offset == range.EndOffset() {
+ range.report_change();
range.end.set_offset(offset + 1);
}
});
@@ -1304,10 +1356,12 @@ impl WeakRangeVec {
let range = entry.root().unwrap();
let start_offset = range.StartOffset();
if &range.start.node == node && start_offset > offset {
+ range.report_change();
range.start.set_offset(f(start_offset));
}
let end_offset = range.EndOffset();
if &range.end.node == node && end_offset > offset {
+ range.report_change();
range.end.set_offset(f(end_offset));
}
});
diff --git a/components/script/dom/selection.rs b/components/script/dom/selection.rs
new file mode 100644
index 00000000000..e86c366d49c
--- /dev/null
+++ b/components/script/dom/selection.rs
@@ -0,0 +1,515 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
+use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
+use crate::dom::bindings::codegen::Bindings::SelectionBinding::{SelectionMethods, Wrap};
+use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
+use crate::dom::bindings::inheritance::Castable;
+use crate::dom::bindings::refcounted::Trusted;
+use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
+use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::document::Document;
+use crate::dom::eventtarget::EventTarget;
+use crate::dom::node::{window_from_node, Node};
+use crate::dom::range::Range;
+use crate::task_source::TaskSource;
+use dom_struct::dom_struct;
+use std::cell::Cell;
+
+#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
+enum Direction {
+ Forwards,
+ Backwards,
+ Directionless,
+}
+
+#[dom_struct]
+pub struct Selection {
+ reflector_: Reflector,
+ document: Dom<Document>,
+ range: MutNullableDom<Range>,
+ direction: Cell<Direction>,
+ task_queued: Cell<bool>,
+}
+
+impl Selection {
+ fn new_inherited(document: &Document) -> Selection {
+ Selection {
+ reflector_: Reflector::new(),
+ document: Dom::from_ref(document),
+ range: MutNullableDom::new(None),
+ direction: Cell::new(Direction::Directionless),
+ task_queued: Cell::new(false),
+ }
+ }
+
+ pub fn new(document: &Document) -> DomRoot<Selection> {
+ reflect_dom_object(
+ Box::new(Selection::new_inherited(document)),
+ &*document.global(),
+ Wrap,
+ )
+ }
+
+ fn set_range(&self, range: &Range) {
+ // If we are setting to literally the same Range object
+ // (not just the same positions), then there's nothing changing
+ // and no task to queue.
+ if let Some(existing) = self.range.get() {
+ if &*existing == range {
+ return;
+ }
+ }
+ self.range.set(Some(range));
+ range.associate_selection(self);
+ self.queue_selectionchange_task();
+ }
+
+ fn clear_range(&self) {
+ // If we already don't have a a Range object, then there's
+ // nothing changing and no task to queue.
+ if let Some(range) = self.range.get() {
+ range.disassociate_selection(self);
+ self.range.set(None);
+ self.queue_selectionchange_task();
+ }
+ }
+
+ pub fn queue_selectionchange_task(&self) {
+ if self.task_queued.get() {
+ // Spec doesn't specify not to queue multiple tasks,
+ // but it's much easier to code range operations if
+ // change notifications within a method are idempotent.
+ return;
+ }
+ let this = Trusted::new(self);
+ let window = window_from_node(&*self.document);
+ window
+ .task_manager()
+ .user_interaction_task_source() // w3c/selection-api#117
+ .queue(
+ task!(selectionchange_task_steps: move || {
+ let this = this.root();
+ this.task_queued.set(false);
+ this.document.upcast::<EventTarget>().fire_event(atom!("selectionchange"));
+ }),
+ window.upcast(),
+ )
+ .expect("Couldn't queue selectionchange task!");
+ self.task_queued.set(true);
+ }
+
+ fn is_same_root(&self, node: &Node) -> bool {
+ &*node.GetRootNode(&GetRootNodeOptions::empty()) == self.document.upcast::<Node>()
+ }
+}
+
+impl SelectionMethods for Selection {
+ // https://w3c.github.io/selection-api/#dom-selection-anchornode
+ fn GetAnchorNode(&self) -> Option<DomRoot<Node>> {
+ if let Some(range) = self.range.get() {
+ match self.direction.get() {
+ Direction::Forwards => Some(range.StartContainer()),
+ _ => Some(range.EndContainer()),
+ }
+ } else {
+ None
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-anchoroffset
+ fn AnchorOffset(&self) -> u32 {
+ if let Some(range) = self.range.get() {
+ match self.direction.get() {
+ Direction::Forwards => range.StartOffset(),
+ _ => range.EndOffset(),
+ }
+ } else {
+ 0
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-focusnode
+ fn GetFocusNode(&self) -> Option<DomRoot<Node>> {
+ if let Some(range) = self.range.get() {
+ match self.direction.get() {
+ Direction::Forwards => Some(range.EndContainer()),
+ _ => Some(range.StartContainer()),
+ }
+ } else {
+ None
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-focusoffset
+ fn FocusOffset(&self) -> u32 {
+ if let Some(range) = self.range.get() {
+ match self.direction.get() {
+ Direction::Forwards => range.EndOffset(),
+ _ => range.StartOffset(),
+ }
+ } else {
+ 0
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-iscollapsed
+ fn IsCollapsed(&self) -> bool {
+ if let Some(range) = self.range.get() {
+ range.Collapsed()
+ } else {
+ true
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-rangecount
+ fn RangeCount(&self) -> u32 {
+ if self.range.get().is_some() {
+ 1
+ } else {
+ 0
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-type
+ fn Type(&self) -> DOMString {
+ if let Some(range) = self.range.get() {
+ if range.Collapsed() {
+ DOMString::from("Caret")
+ } else {
+ DOMString::from("Range")
+ }
+ } else {
+ DOMString::from("None")
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-getrangeat
+ fn GetRangeAt(&self, index: u32) -> Fallible<DomRoot<Range>> {
+ if index != 0 {
+ Err(Error::IndexSize)
+ } else if let Some(range) = self.range.get() {
+ Ok(DomRoot::from_ref(&range))
+ } else {
+ Err(Error::IndexSize)
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-addrange
+ fn AddRange(&self, range: &Range) {
+ // Step 1
+ if !self.is_same_root(&*range.StartContainer()) {
+ return;
+ }
+
+ // Step 2
+ if self.RangeCount() != 0 {
+ return;
+ }
+
+ // Step 3
+ self.set_range(range);
+ // Are we supposed to set Direction here? w3c/selection-api#116
+ self.direction.set(Direction::Forwards);
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-removerange
+ fn RemoveRange(&self, range: &Range) -> ErrorResult {
+ if let Some(own_range) = self.range.get() {
+ if &*own_range == range {
+ self.clear_range();
+ return Ok(());
+ }
+ }
+ Err(Error::NotFound)
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-removeallranges
+ fn RemoveAllRanges(&self) {
+ self.clear_range();
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-empty
+ // TODO: When implementing actual selection UI, this may be the correct
+ // method to call as the abandon-selection action
+ fn Empty(&self) {
+ self.clear_range();
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-collapse
+ fn Collapse(&self, node: Option<&Node>, offset: u32) -> ErrorResult {
+ if let Some(node) = node {
+ if node.is_doctype() {
+ // w3c/selection-api#118
+ return Err(Error::InvalidNodeType);
+ }
+ if offset > node.len() {
+ // Step 2
+ return Err(Error::IndexSize);
+ }
+
+ if !self.is_same_root(node) {
+ // Step 3
+ return Ok(());
+ }
+
+ // Steps 4-5
+ let range = Range::new(&self.document, node, offset, node, offset);
+
+ // Step 6
+ self.set_range(&range);
+ // Are we supposed to set Direction here? w3c/selection-api#116
+ //
+ self.direction.set(Direction::Forwards);
+ } else {
+ // Step 1
+ self.clear_range();
+ }
+ Ok(())
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-setposition
+ // TODO: When implementing actual selection UI, this may be the correct
+ // method to call as the start-of-selection action, after a
+ // selectstart event has fired and not been cancelled.
+ fn SetPosition(&self, node: Option<&Node>, offset: u32) -> ErrorResult {
+ self.Collapse(node, offset)
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-collapsetostart
+ fn CollapseToStart(&self) -> ErrorResult {
+ if let Some(range) = self.range.get() {
+ self.Collapse(Some(&*range.StartContainer()), range.StartOffset())
+ } else {
+ Err(Error::InvalidState)
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-collapsetoend
+ fn CollapseToEnd(&self) -> ErrorResult {
+ if let Some(range) = self.range.get() {
+ self.Collapse(Some(&*range.EndContainer()), range.EndOffset())
+ } else {
+ Err(Error::InvalidState)
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-extend
+ // TODO: When implementing actual selection UI, this may be the correct
+ // method to call as the continue-selection action
+ fn Extend(&self, node: &Node, offset: u32) -> ErrorResult {
+ if !self.is_same_root(node) {
+ // Step 1
+ return Ok(());
+ }
+ if let Some(range) = self.range.get() {
+ if node.is_doctype() {
+ // w3c/selection-api#118
+ return Err(Error::InvalidNodeType);
+ }
+
+ if offset > node.len() {
+ // As with is_doctype, not explicit in selection spec steps here
+ // but implied by which exceptions are thrown in WPT tests
+ return Err(Error::IndexSize);
+ }
+
+ // Step 4
+ if !self.is_same_root(&*range.StartContainer()) {
+ // Step 5, and its following 8 and 9
+ self.set_range(&*Range::new(&self.document, node, offset, node, offset));
+ self.direction.set(Direction::Forwards);
+ } else {
+ let old_anchor_node = &*self.GetAnchorNode().unwrap(); // has range, therefore has anchor node
+ let old_anchor_offset = self.AnchorOffset();
+ let is_old_anchor_before_or_equal = {
+ if old_anchor_node == node {
+ old_anchor_offset <= offset
+ } else {
+ old_anchor_node.is_before(node)
+ }
+ };
+ if is_old_anchor_before_or_equal {
+ // Step 6, and its following 8 and 9
+ self.set_range(&*Range::new(
+ &self.document,
+ old_anchor_node,
+ old_anchor_offset,
+ node,
+ offset,
+ ));
+ self.direction.set(Direction::Forwards);
+ } else {
+ // Step 7, and its following 8 and 9
+ self.set_range(&*Range::new(
+ &self.document,
+ node,
+ offset,
+ old_anchor_node,
+ old_anchor_offset,
+ ));
+ self.direction.set(Direction::Backwards);
+ }
+ };
+ } else {
+ // Step 2
+ return Err(Error::InvalidState);
+ }
+ return Ok(());
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-setbaseandextent
+ fn SetBaseAndExtent(
+ &self,
+ anchor_node: &Node,
+ anchor_offset: u32,
+ focus_node: &Node,
+ focus_offset: u32,
+ ) -> ErrorResult {
+ // Step 1
+ if anchor_node.is_doctype() || focus_node.is_doctype() {
+ // w3c/selection-api#118
+ return Err(Error::InvalidNodeType);
+ }
+
+ if anchor_offset > anchor_node.len() || focus_offset > focus_node.len() {
+ return Err(Error::IndexSize);
+ }
+
+ // Step 2
+ if !self.is_same_root(anchor_node) || !self.is_same_root(focus_node) {
+ return Ok(());
+ }
+
+ // Steps 5-7
+ let is_focus_before_anchor = {
+ if anchor_node == focus_node {
+ focus_offset < anchor_offset
+ } else {
+ focus_node.is_before(anchor_node)
+ }
+ };
+ if is_focus_before_anchor {
+ self.set_range(&*Range::new(
+ &self.document,
+ focus_node,
+ focus_offset,
+ anchor_node,
+ anchor_offset,
+ ));
+ self.direction.set(Direction::Backwards);
+ } else {
+ self.set_range(&*Range::new(
+ &self.document,
+ anchor_node,
+ anchor_offset,
+ focus_node,
+ focus_offset,
+ ));
+ self.direction.set(Direction::Forwards);
+ }
+ Ok(())
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-selectallchildren
+ fn SelectAllChildren(&self, node: &Node) -> ErrorResult {
+ if node.is_doctype() {
+ // w3c/selection-api#118
+ return Err(Error::InvalidNodeType);
+ }
+ if !self.is_same_root(node) {
+ return Ok(());
+ }
+
+ // Spec wording just says node length here, but WPT specifically
+ // wants number of children (the main difference is that it's 0
+ // for cdata).
+ self.set_range(&*Range::new(
+ &self.document,
+ node,
+ 0,
+ node,
+ node.children_count(),
+ ));
+
+ self.direction.set(Direction::Forwards);
+ Ok(())
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-deletecontents
+ fn DeleteFromDocument(&self) -> ErrorResult {
+ if let Some(range) = self.range.get() {
+ // Since the range is changing, it should trigger a
+ // selectionchange event as it would if if mutated any other way
+ return range.DeleteContents();
+ }
+ return Ok(());
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-containsnode
+ fn ContainsNode(&self, node: &Node, allow_partial_containment: bool) -> bool {
+ // TODO: Spec requires a "visually equivalent to" check, which is
+ // probably up to a layout query. This is therefore not a full implementation.
+ if !self.is_same_root(node) {
+ return false;
+ }
+ if let Some(range) = self.range.get() {
+ let start_node = &*range.StartContainer();
+ if !self.is_same_root(start_node) {
+ // node can't be contained in a range with a different root
+ return false;
+ }
+ if allow_partial_containment {
+ // Spec seems to be incorrect here, w3c/selection-api#116
+ if node.is_before(start_node) {
+ return false;
+ }
+ let end_node = &*range.EndContainer();
+ if end_node.is_before(node) {
+ return false;
+ }
+ if node == start_node {
+ return range.StartOffset() < node.len();
+ }
+ if node == end_node {
+ return range.EndOffset() > 0;
+ }
+ return true;
+ } else {
+ if node.is_before(start_node) {
+ return false;
+ }
+ let end_node = &*range.EndContainer();
+ if end_node.is_before(node) {
+ return false;
+ }
+ if node == start_node {
+ return range.StartOffset() == 0;
+ }
+ if node == end_node {
+ return range.EndOffset() == node.len();
+ }
+ return true;
+ }
+ } else {
+ // No range
+ return false;
+ }
+ }
+
+ // https://w3c.github.io/selection-api/#dom-selection-stringifier
+ fn Stringifier(&self) -> DOMString {
+ // The spec as of Jan 31 2020 just says
+ // "See W3C bug 10583." for this method.
+ // Stringifying the range seems at least approximately right
+ // and passes the non-style-dependent case in the WPT tests.
+ if let Some(range) = self.range.get() {
+ range.Stringifier()
+ } else {
+ DOMString::from("")
+ }
+ }
+}
diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl
index 7d92ddbd137..3ca1905de90 100644
--- a/components/script/dom/webidls/Document.webidl
+++ b/components/script/dom/webidls/Document.webidl
@@ -214,6 +214,12 @@ partial interface Document {
Document includes DocumentOrShadowRoot;
+// https://w3c.github.io/selection-api/#dom-document
+partial interface Document {
+ Selection? getSelection();
+};
+
+
// Servo internal API.
partial interface Document {
[Throws]
diff --git a/components/script/dom/webidls/EventHandler.webidl b/components/script/dom/webidls/EventHandler.webidl
index 53df17fe7e0..842e1ebc7e8 100644
--- a/components/script/dom/webidls/EventHandler.webidl
+++ b/components/script/dom/webidls/EventHandler.webidl
@@ -95,6 +95,12 @@ partial interface mixin GlobalEventHandlers {
attribute EventHandler ontransitionend;
};
+// https://w3c.github.io/selection-api/#extensions-to-globaleventhandlers-interface
+partial interface mixin GlobalEventHandlers {
+ attribute EventHandler onselectstart;
+ attribute EventHandler onselectionchange;
+};
+
// https://html.spec.whatwg.org/multipage/#windoweventhandlers
[Exposed=Window]
interface mixin WindowEventHandlers {
diff --git a/components/script/dom/webidls/Selection.webidl b/components/script/dom/webidls/Selection.webidl
new file mode 100644
index 00000000000..38c874e3bf7
--- /dev/null
+++ b/components/script/dom/webidls/Selection.webidl
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://w3c.github.io/selection-api/#selection-interface
+[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;
+ [Throws] Range getRangeAt(unsigned long index);
+ void addRange(Range range);
+ [Throws] void removeRange(Range range);
+ void removeAllRanges();
+ void empty();
+ [Throws] void collapse(Node? node, optional unsigned long offset = 0);
+ [Throws] void setPosition(Node? node, optional unsigned long offset = 0);
+ [Throws] void collapseToStart();
+ [Throws] void collapseToEnd();
+ [Throws] void extend(Node node, optional unsigned long offset = 0);
+ [Throws]
+ void setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, unsigned long focusOffset);
+ [Throws] void selectAllChildren(Node node);
+ [CEReactions, Throws]
+ void deleteFromDocument();
+ boolean containsNode(Node node, optional boolean allowPartialContainment = false);
+ stringifier DOMString ();
+};
diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl
index eb8e4232886..0c0b5e80860 100644
--- a/components/script/dom/webidls/Window.webidl
+++ b/components/script/dom/webidls/Window.webidl
@@ -175,6 +175,12 @@ partial interface Window {
readonly attribute unsigned long runningAnimationCount;
};
+// https://w3c.github.io/selection-api/#dom-document
+partial interface Window {
+ Selection? getSelection();
+};
+
+
dictionary WindowPostMessageOptions : PostMessageOptions {
USVString targetOrigin = "/";
};
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index c1a6ba0c4c4..65a60176559 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -47,6 +47,7 @@ use crate::dom::node::{document_from_node, from_untrusted_node_address, Node, No
use crate::dom::performance::Performance;
use crate::dom::promise::Promise;
use crate::dom::screen::Screen;
+use crate::dom::selection::Selection;
use crate::dom::storage::Storage;
use crate::dom::testrunner::TestRunner;
use crate::dom::webglrenderingcontext::WebGLCommandSender;
@@ -1322,6 +1323,11 @@ impl WindowMethods for Window {
fn Origin(&self) -> USVString {
USVString(self.origin().immutable().ascii_serialization())
}
+
+ // https://w3c.github.io/selection-api/#dom-window-getselection
+ fn GetSelection(&self) -> Option<DomRoot<Selection>> {
+ self.document.get().and_then(|d| d.GetSelection())
+ }
}
impl Window {