diff options
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/document.rs | 18 | ||||
-rw-r--r-- | components/script/dom/macros.rs | 2 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/range.rs | 54 | ||||
-rw-r--r-- | components/script/dom/selection.rs | 515 | ||||
-rw-r--r-- | components/script/dom/webidls/Document.webidl | 6 | ||||
-rw-r--r-- | components/script/dom/webidls/EventHandler.webidl | 6 | ||||
-rw-r--r-- | components/script/dom/webidls/Selection.webidl | 32 | ||||
-rw-r--r-- | components/script/dom/webidls/Window.webidl | 6 | ||||
-rw-r--r-- | components/script/dom/window.rs | 6 |
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 { |