diff options
Diffstat (limited to 'components/script/dom/range.rs')
-rw-r--r-- | components/script/dom/range.rs | 488 |
1 files changed, 478 insertions, 10 deletions
diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs index f79e6685a28..c9124f1eb3d 100644 --- a/components/script/dom/range.rs +++ b/components/script/dom/range.rs @@ -2,44 +2,512 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use dom::bindings::codegen::Bindings::RangeBinding; +use dom::bindings::codegen::Bindings::NodeBinding::NodeConstants; +use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use dom::bindings::codegen::Bindings::RangeBinding::{self, RangeConstants}; use dom::bindings::codegen::Bindings::RangeBinding::RangeMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::error::Fallible; +use dom::bindings::codegen::InheritTypes::NodeCast; +use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::global::GlobalRef; -use dom::bindings::js::{JSRef, Rootable, Temporary}; +use dom::bindings::js::{JS, JSRef, MutHeap, Rootable, Temporary}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::document::{Document, DocumentHelpers}; +use dom::node::{Node, NodeHelpers}; + +use std::cell::RefCell; +use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; +use std::rc::Rc; #[dom_struct] pub struct Range { - reflector_: Reflector + reflector_: Reflector, + inner: Rc<RefCell<RangeInner>>, } impl Range { - fn new_inherited() -> Range { + fn new_inherited(start_container: JSRef<Node>, start_offset: u32, + end_container: JSRef<Node>, end_offset: u32) -> Range { Range { - reflector_: Reflector::new() + reflector_: Reflector::new(), + inner: Rc::new(RefCell::new(RangeInner::new( + BoundaryPoint::new(start_container, start_offset), + BoundaryPoint::new(end_container, end_offset)))), } } - pub fn new(document: JSRef<Document>) -> Temporary<Range> { + pub fn new_with_doc(document: JSRef<Document>) -> Temporary<Range> { + let root = NodeCast::from_ref(document); + Range::new(document, root, 0, root, 0) + } + + pub fn new(document: JSRef<Document>, + start_container: JSRef<Node>, start_offset: u32, + end_container: JSRef<Node>, end_offset: u32) + -> Temporary<Range> { let window = document.window().root(); - reflect_dom_object(box Range::new_inherited(), + reflect_dom_object(box Range::new_inherited(start_container, start_offset, + end_container, end_offset), GlobalRef::Window(window.r()), RangeBinding::Wrap) } + // https://dom.spec.whatwg.org/#dom-range pub fn Constructor(global: GlobalRef) -> Fallible<Temporary<Range>> { let document = global.as_window().Document().root(); - Ok(Range::new(document.r())) + Ok(Range::new_with_doc(document.r())) + } +} + +pub trait RangeHelpers<'a> { + fn inner(self) -> &'a Rc<RefCell<RangeInner>>; +} + +impl<'a> RangeHelpers<'a> for JSRef<'a, Range> { + fn inner(self) -> &'a Rc<RefCell<RangeInner>> { + &self.extended_deref().inner } } impl<'a> RangeMethods for JSRef<'a, Range> { - /// https://dom.spec.whatwg.org/#dom-range-detach + // http://dom.spec.whatwg.org/#dom-range-startcontainer + fn StartContainer(self) -> Temporary<Node> { + self.inner().borrow().start.node() + } + + /// http://dom.spec.whatwg.org/#dom-range-startoffset + fn StartOffset(self) -> u32 { + self.inner().borrow().start.offset + } + + /// http://dom.spec.whatwg.org/#dom-range-endcontainer + fn EndContainer(self) -> Temporary<Node> { + self.inner().borrow().end.node() + } + + /// http://dom.spec.whatwg.org/#dom-range-endoffset + fn EndOffset(self) -> u32 { + self.inner().borrow().end.offset + } + + // https://dom.spec.whatwg.org/#dom-range-collapsed + fn Collapsed(self) -> bool { + let inner = self.inner().borrow(); + inner.start == inner.end + } + + // https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer + fn CommonAncestorContainer(self) -> Temporary<Node> { + self.inner().borrow().common_ancestor_container() + } + + // https://dom.spec.whatwg.org/#dom-range-setstartnode-offset + fn SetStart(self, node: JSRef<Node>, offset: u32) -> ErrorResult { + if node.is_doctype() { + // Step 1. + Err(Error::InvalidNodeType) + } else if offset > node.len() { + // Step 2. + Err(Error::IndexSize) + } else { + // Step 3-4. + self.inner().borrow_mut().set_start(node, offset); + Ok(()) + } + } + + // https://dom.spec.whatwg.org/#dom-range-setendnode-offset + fn SetEnd(self, node: JSRef<Node>, offset: u32) -> ErrorResult { + if node.is_doctype() { + // Step 1. + Err(Error::InvalidNodeType) + } else if offset > node.len() { + // Step 2. + Err(Error::IndexSize) + } else { + // Step 3-4. + self.inner().borrow_mut().set_end(node, offset); + Ok(()) + } + } + + // https://dom.spec.whatwg.org/#dom-range-setstartbeforenode + fn SetStartBefore(self, node: JSRef<Node>) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetStart(parent.r(), node.index()) + } + + // https://dom.spec.whatwg.org/#dom-range-setstartafternode + fn SetStartAfter(self, node: JSRef<Node>) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetStart(parent.r(), node.index() + 1) + } + + // https://dom.spec.whatwg.org/#dom-range-setendbeforenode + fn SetEndBefore(self, node: JSRef<Node>) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetEnd(parent.r(), node.index()) + } + + // https://dom.spec.whatwg.org/#dom-range-setendafternode + fn SetEndAfter(self, node: JSRef<Node>) -> ErrorResult { + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + self.SetEnd(parent.r(), node.index() + 1) + } + + // https://dom.spec.whatwg.org/#dom-range-collapsetostart + fn Collapse(self, to_start: bool) { + self.inner().borrow_mut().collapse(to_start); + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodenode + fn SelectNode(self, node: JSRef<Node>) -> ErrorResult { + self.inner().borrow_mut().select_node(node) + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodecontentsnode + fn SelectNodeContents(self, node: JSRef<Node>) -> ErrorResult { + self.inner().borrow_mut().select_node_contents(node) + } + + // https://dom.spec.whatwg.org/#dom-range-compareboundarypointshow-sourcerange + fn CompareBoundaryPoints(self, how: u16, source_range: JSRef<Range>) + -> Fallible<i16> { + if how > RangeConstants::END_TO_START { + // Step 1. + return Err(Error::NotSupported); + } + let this_inner = self.inner().borrow(); + let other_inner = source_range.inner().borrow(); + let this_start_node = this_inner.start.node().root(); + let other_start_node = other_inner.start.node().root(); + let this_root = this_start_node.r().inclusive_ancestors().last().unwrap(); + let other_root = other_start_node.r().inclusive_ancestors().last().unwrap(); + if this_root != other_root { + // Step 2. + return Err(Error::WrongDocument); + } + // Step 3. + let (this_point, other_point) = match how { + RangeConstants::START_TO_START => { + (&this_inner.start, &other_inner.start) + }, + RangeConstants::START_TO_END => { + (&this_inner.end, &other_inner.start) + }, + RangeConstants::END_TO_END => { + (&this_inner.end, &other_inner.end) + }, + RangeConstants::END_TO_START => { + (&this_inner.start, &other_inner.end) + }, + _ => unreachable!(), + }; + // step 4. + match this_point.partial_cmp(other_point).unwrap() { + Ordering::Less => Ok(-1), + Ordering::Equal => Ok(0), + Ordering::Greater => Ok(1), + } + } + + // https://dom.spec.whatwg.org/#dom-range-clonerange + fn CloneRange(self) -> Temporary<Range> { + let inner = self.inner().borrow(); + let start = &inner.start; + let end = &inner.end; + let start_node = start.node().root(); + let owner_doc = NodeCast::from_ref(start_node.r()).owner_doc().root(); + Range::new(owner_doc.r(), start_node.r(), start.offset, + end.node().root().r(), end.offset) + } + + // https://dom.spec.whatwg.org/#dom-range-ispointinrangenode-offset + fn IsPointInRange(self, node: JSRef<Node>, offset: u32) -> Fallible<bool> { + match self.inner().borrow().compare_point(node, offset) { + Ok(Ordering::Less) => Ok(false), + Ok(Ordering::Equal) => Ok(true), + Ok(Ordering::Greater) => Ok(false), + Err(Error::WrongDocument) => { + // Step 2. + Ok(false) + } + Err(error) => Err(error), + } + } + + // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset + fn ComparePoint(self, node: JSRef<Node>, offset: u32) -> Fallible<i16> { + self.inner().borrow().compare_point(node, offset).map(|order| { + match order { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + } + }) + } + + // https://dom.spec.whatwg.org/#dom-range-intersectsnodenode + fn IntersectsNode(self, node: JSRef<Node>) -> bool { + let inner = self.inner().borrow(); + let start = &inner.start; + let start_node = start.node().root(); + let start_offset = start.offset; + let start_node_root = start_node.r().inclusive_ancestors().last().unwrap().root(); + let node_root = node.inclusive_ancestors().last().unwrap().root(); + if start_node_root.r() != node_root.r() { + // Step 1. + return false; + } + let parent = match node.parent_node() { + Some(parent) => parent, + None => { + // Step 3. + return true; + }, + }.root(); + // Step 4. + let offset = node.index(); + let end = &inner.end; + let end_node = end.node().root(); + let end_offset = end.offset; + match (bp_position(parent.r(), offset + 1, start_node.r(), start_offset).unwrap(), + bp_position(parent.r(), offset, end_node.r(), end_offset).unwrap()) { + (Ordering::Greater, Ordering::Less) => { + // Step 5. + true + }, + _ => { + // Step 6. + false + } + } + } + + // http://dom.spec.whatwg.org/#dom-range-detach fn Detach(self) { // This method intentionally left blank. } } +#[jstraceable] +#[must_root] +#[privatize] +pub struct RangeInner { + start: BoundaryPoint, + end: BoundaryPoint, +} + +impl RangeInner { + fn new(start: BoundaryPoint, end: BoundaryPoint) -> RangeInner { + RangeInner { start: start, end: end } + } + + // https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer + fn common_ancestor_container(&self) -> Temporary<Node> { + let start_container = self.start.node().root(); + let end_container = self.end.node().root(); + // Step 1. + for container in start_container.r().inclusive_ancestors() { + // Step 2. + if container.root().r().is_inclusive_ancestor_of(end_container.r()) { + // Step 3. + return container; + } + } + unreachable!(); + } + + // https://dom.spec.whatwg.org/#concept-range-bp-set + pub fn set_start(&mut self, bp_node: JSRef<Node>, bp_offset: u32) { + // Steps 1-3 handled in Range caller. + let end_node = self.end.node().root(); + let end_offset = self.end.offset; + match bp_position(bp_node, bp_offset, end_node.r(), end_offset) { + None | Some(Ordering::Greater) => { + // Step 4-1. + self.end.set(bp_node, bp_offset); + }, + _ => {}, + }; + // Step 4-2. + self.start.set(bp_node, bp_offset); + } + + // https://dom.spec.whatwg.org/#concept-range-bp-set + pub fn set_end(&mut self, bp_node: JSRef<Node>, bp_offset: u32) { + // Steps 1-3 handled in Range caller. + let start_node = self.start.node().root(); + let start_offset = self.start.offset; + match bp_position(bp_node, bp_offset, start_node.r(), start_offset) { + None | Some(Ordering::Less) => { + // Step 4-1. + self.start.set(bp_node, bp_offset); + }, + _ => {}, + }; + // Step 4-2. + self.end.set(bp_node, bp_offset); + } + + // https://dom.spec.whatwg.org/#dom-range-collapsetostart + fn collapse(&mut self, to_start: bool) { + if to_start { + let start_node = self.start.node().root(); + self.end.set(start_node.r(), self.start.offset); + } else { + let end_node = self.end.node().root(); + self.start.set(end_node.r(), self.end.offset); + } + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodenode + fn select_node(&mut self, node: JSRef<Node>) -> ErrorResult { + // Steps 1, 2. + let parent = try!(node.parent_node().ok_or(Error::InvalidNodeType)).root(); + // Step 3. + let index = node.index(); + // Step 4. + self.start.set(parent.r(), index); + // Step 5. + self.end.set(parent.r(), index + 1); + Ok(()) + } + + // https://dom.spec.whatwg.org/#dom-range-selectnodecontentsnode + fn select_node_contents(&mut self, node: JSRef<Node>) -> ErrorResult { + if node.is_doctype() { + // Step 1. + return Err(Error::InvalidNodeType); + } + // Step 2. + let length = node.len(); + // Step 3. + self.start.set(node, 0); + // Step 4. + self.end.set(node, length); + Ok(()) + } + + // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset + fn compare_point(&self, node: JSRef<Node>, offset: u32) -> Fallible<Ordering> { + let start = &self.start; + let start_node = start.node().root(); + let start_offset = start.offset; + let start_node_root = start_node.r().inclusive_ancestors().last().unwrap().root(); + let node_root = node.inclusive_ancestors().last().unwrap().root(); + if start_node_root.r() != node_root.r() { + // Step 1. + return Err(Error::WrongDocument); + } + if node.is_doctype() { + // Step 2. + return Err(Error::InvalidNodeType); + } + if offset > node.len() { + // Step 3. + return Err(Error::IndexSize); + } + if let Ordering::Less = bp_position(node, offset, start_node.r(), start_offset).unwrap() { + // Step 4. + return Ok(Ordering::Less); + } + let end = &self.end; + let end_node = end.node().root(); + let end_offset = end.offset; + if let Ordering::Greater = bp_position(node, offset, end_node.r(), end_offset).unwrap() { + // Step 5. + return Ok(Ordering::Greater); + } + // Step 6. + Ok(Ordering::Equal) + } +} + +#[jstraceable] +#[must_root] +#[privatize] +pub struct BoundaryPoint { + node: MutHeap<JS<Node>>, + offset: u32, +} + +impl BoundaryPoint { + fn new(node: JSRef<Node>, offset: u32) -> BoundaryPoint { + debug_assert!(!node.is_doctype()); + debug_assert!(offset <= node.len()); + BoundaryPoint { + node: MutHeap::new(JS::from_rooted(node)), + offset: offset, + } + } + + pub fn node(&self) -> Temporary<Node> { + Temporary::from_rooted(self.node.get()) + } + + pub fn offset(&self) -> u32 { + self.offset + } + + fn set(&mut self, node: JSRef<Node>, offset: u32) { + debug_assert!(!node.is_doctype()); + debug_assert!(offset <= node.len()); + self.node.set(JS::from_rooted(node)); + self.offset = offset; + } +} + +#[allow(unrooted_must_root)] +impl PartialOrd for BoundaryPoint { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + bp_position(self.node().root().r(), self.offset, + other.node().root().r(), other.offset) + } +} + +#[allow(unrooted_must_root)] +impl PartialEq for BoundaryPoint { + fn eq(&self, other: &Self) -> bool { + self.node().root().r() == other.node().root().r() && + self.offset == other.offset + } +} + +// https://dom.spec.whatwg.org/#concept-range-bp-position +fn bp_position(a_node: JSRef<Node>, a_offset: u32, + b_node: JSRef<Node>, b_offset: u32) + -> Option<Ordering> { + if a_node == b_node { + // Step 1. + return Some(a_offset.cmp(&b_offset)); + } + let position = b_node.CompareDocumentPosition(a_node); + if position & NodeConstants::DOCUMENT_POSITION_DISCONNECTED != 0 { + // No order is defined for nodes not in the same tree. + None + } else if position & NodeConstants::DOCUMENT_POSITION_FOLLOWING != 0 { + // Step 2. + match bp_position(b_node, b_offset, a_node, a_offset).unwrap() { + Ordering::Less => Some(Ordering::Greater), + Ordering::Greater => Some(Ordering::Less), + Ordering::Equal => unreachable!(), + } + } else if position & NodeConstants::DOCUMENT_POSITION_CONTAINS != 0 { + // Step 3-1, 3-2. + let b_ancestors = b_node.inclusive_ancestors(); + let ref child = b_ancestors.map(|child| child.root()).find(|child| { + child.r().parent_node().unwrap().root().r() == a_node + }).unwrap(); + // Step 3-3. + if child.r().index() < a_offset { + Some(Ordering::Greater) + } else { + // Step 4. + Some(Ordering::Less) + } + } else { + // Step 4. + Some(Ordering::Less) + } +} |