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