Skip to main content

script/dom/range/
abstractrange.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
7
8use deny_public_fields::DenyPublicFields;
9use dom_struct::dom_struct;
10use script_bindings::reflector::Reflector;
11use servo_base::text::Utf16CodeUnits;
12
13use crate::dom::bindings::codegen::Bindings::AbstractRangeBinding::AbstractRangeMethods;
14use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
15use crate::dom::bindings::root::{DomRoot, MutDom};
16use crate::dom::iterators::ShadowIncluding;
17use crate::dom::node::Node;
18
19#[dom_struct]
20pub(crate) struct AbstractRange {
21    reflector_: Reflector,
22    start: BoundaryPoint,
23    end: BoundaryPoint,
24}
25
26impl AbstractRange {
27    pub(crate) fn new_inherited(
28        start_container: &Node,
29        start_offset: u32,
30        end_container: &Node,
31        end_offset: u32,
32    ) -> AbstractRange {
33        AbstractRange {
34            reflector_: Reflector::new(),
35            start: BoundaryPoint::new(start_container, start_offset),
36            end: BoundaryPoint::new(end_container, end_offset),
37        }
38    }
39
40    pub(crate) fn start(&self) -> &BoundaryPoint {
41        &self.start
42    }
43
44    pub(crate) fn end(&self) -> &BoundaryPoint {
45        &self.end
46    }
47}
48
49impl AbstractRangeMethods<crate::DomTypeHolder> for AbstractRange {
50    /// <https://dom.spec.whatwg.org/#dom-range-startcontainer>
51    fn StartContainer(&self) -> DomRoot<Node> {
52        self.start.node.get()
53    }
54
55    /// <https://dom.spec.whatwg.org/#dom-range-startoffset>
56    fn StartOffset(&self) -> u32 {
57        self.start.offset.get()
58    }
59
60    /// <https://dom.spec.whatwg.org/#dom-range-endcontainer>
61    fn EndContainer(&self) -> DomRoot<Node> {
62        self.end.node.get()
63    }
64
65    /// <https://dom.spec.whatwg.org/#dom-range-endoffset>
66    fn EndOffset(&self) -> u32 {
67        self.end.offset.get()
68    }
69
70    /// <https://dom.spec.whatwg.org/#dom-range-collapsed>
71    fn Collapsed(&self) -> bool {
72        // > The collapsed getter steps are to return true if this is collapsed; otherwise false.
73        self.start == self.end
74    }
75}
76
77/// <https://dom.spec.whatwg.org/#concept-range-bp>
78#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
79#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
80pub(crate) struct BoundaryPoint {
81    /// <https://dom.spec.whatwg.org/#boundary-point-node>
82    node: MutDom<Node>,
83    /// <https://dom.spec.whatwg.org/#concept-range-bp-offset>
84    offset: Cell<u32>,
85}
86
87impl BoundaryPoint {
88    pub(crate) fn new(node: &Node, offset: u32) -> BoundaryPoint {
89        debug_assert!(!node.is_doctype());
90        BoundaryPoint {
91            node: MutDom::new(node),
92            offset: Cell::new(offset),
93        }
94    }
95
96    pub(crate) fn set(&self, node: &Node, offset: u32) {
97        self.node.set(node);
98        self.set_offset(offset);
99    }
100
101    pub(crate) fn offset(&self) -> Utf16CodeUnits {
102        Utf16CodeUnits::from(self.offset.get())
103    }
104
105    pub(crate) fn set_offset(&self, offset: u32) {
106        self.offset.set(offset);
107    }
108
109    pub(crate) fn node(&self) -> &MutDom<Node> {
110        &self.node
111    }
112}
113
114impl PartialOrd for BoundaryPoint {
115    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
116        Some(bp_position(
117            &self.node.get(),
118            self.offset.get(),
119            &other.node.get(),
120            other.offset.get(),
121        ))
122    }
123}
124
125/// <https://dom.spec.whatwg.org/#range-collapsed>
126impl PartialEq for BoundaryPoint {
127    fn eq(&self, other: &Self) -> bool {
128        // > A range is collapsed if its start node is its end node and its start offset is its end offset.
129        self.node.get() == other.node.get() && self.offset.get() == other.offset.get()
130    }
131}
132
133/// <https://dom.spec.whatwg.org/#concept-range-bp-position>
134pub(crate) fn bp_position(a_node: &Node, a_offset: u32, b_node: &Node, b_offset: u32) -> Ordering {
135    // Step 1: Assert: nodeA and nodeB have the same root.
136    debug_assert!(
137        a_node.GetRootNode(&Default::default()) == b_node.GetRootNode(&Default::default())
138    );
139
140    // Step 2: If nodeA is nodeB, then return equal if offsetA is offsetB, before if
141    // offsetA is less than offsetB, and after if offsetA is greater than offsetB.
142    if a_node == b_node {
143        return a_offset.cmp(&b_offset);
144    }
145
146    let position = b_node.CompareDocumentPosition(a_node);
147    assert!(
148        position & NodeConstants::DOCUMENT_POSITION_DISCONNECTED == 0,
149        "Nodes should be in the same tree"
150    );
151    if position & NodeConstants::DOCUMENT_POSITION_FOLLOWING != 0 {
152        // Step 3: If nodeA is following nodeB, then if the position of (nodeB, offsetB)
153        // relative to (nodeA, offsetA) is before, return after, and if it is after,
154        // return before.
155        return match bp_position(b_node, b_offset, a_node, a_offset) {
156            Ordering::Less => Ordering::Greater,
157            Ordering::Greater => Ordering::Less,
158            Ordering::Equal => unreachable!("Should be impossible due to Step 2."),
159        };
160    } else if position & NodeConstants::DOCUMENT_POSITION_CONTAINS != 0 {
161        // Step 4: If nodeA is an ancestor of nodeB:
162        // Step 4.1: Let child be nodeB.
163        // Step 4.2: While child is not a child of nodeA, set child to its parent.
164        let mut b_ancestors = b_node.inclusive_ancestors(ShadowIncluding::No);
165        let child = b_ancestors
166            .find(|child| &*child.GetParentNode().unwrap() == a_node)
167            .unwrap();
168
169        // Step 4.3: If child’s index is less than offsetA, then return after.
170        if child.index() < a_offset {
171            return Ordering::Greater;
172        }
173    }
174
175    // Step 5: Return before.
176    Ordering::Less
177}