Skip to main content

script/dom/range/
range.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::LazyCell;
6use std::cmp::Ordering;
7use std::iter;
8use std::rc::Rc;
9
10use app_units::Au;
11use dom_struct::dom_struct;
12use euclid::Rect;
13use js::context::{JSContext, NoGC};
14use js::jsapi::JSTracer;
15use js::rust::HandleObject;
16use script_bindings::cell::DomRefCell;
17use script_bindings::dom::{MutDom, UnrootedDom};
18use script_bindings::reflector::reflect_weak_referenceable_dom_object_with_proto;
19use smallvec::SmallVec;
20use style_traits::CSSPixel;
21
22use crate::dom::abstractrange::{AbstractRange, BoundaryPoint, bp_position};
23use crate::dom::bindings::codegen::Bindings::AbstractRangeBinding::AbstractRangeMethods;
24use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
25use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
26use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
27use crate::dom::bindings::codegen::Bindings::RangeBinding::{RangeConstants, RangeMethods};
28use crate::dom::bindings::codegen::Bindings::TextBinding::TextMethods;
29use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
30use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
31use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
32use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId};
33use crate::dom::bindings::root::{Dom, DomRoot};
34use crate::dom::bindings::str::DOMString;
35use crate::dom::bindings::trace::JSTraceable;
36use crate::dom::bindings::weakref::{WeakRef, WeakRefVec};
37use crate::dom::characterdata::CharacterData;
38use crate::dom::document::Document;
39use crate::dom::documentfragment::DocumentFragment;
40use crate::dom::domrect::DOMRect;
41use crate::dom::domrectlist::DOMRectList;
42use crate::dom::element::Element;
43use crate::dom::html::htmlscriptelement::HTMLScriptElement;
44use crate::dom::iterators::ShadowIncluding;
45use crate::dom::node::{Node, NodeTraits};
46use crate::dom::selection::{Selection, SelectionLiveRangeNotification};
47use crate::dom::text::Text;
48use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
49use crate::dom::window::Window;
50
51#[dom_struct]
52pub(crate) struct Range {
53    abstract_range: AbstractRange,
54    /// The [`Document`] that this range belongs to i.e. the owner [`Document`]
55    /// of its two nodes. [`Range`] is written to ensure that both ends share
56    /// a root, so this should always be the same for both ends.
57    document: MutDom<Document>,
58    // A range that belongs to a Selection needs to know about it
59    // so selectionchange can fire when the range changes.
60    // A range shouldn't belong to more than one Selection at a time,
61    // but from the spec as of Feb 1 2020 I can't rule out a corner case like:
62    // * Select a range R in document A, from node X to Y
63    // * Insert everything from X to Y into document B
64    // * Set B's selection's range to R
65    // which leaves R technically, and observably, associated with A even though
66    // it will fail the same-root-node check on many of A's selection's methods.
67    associated_selections: DomRefCell<Vec<Dom<Selection>>>,
68}
69
70pub(crate) struct ContainedChildren {
71    pub(crate) first_partially_contained_child: Option<DomRoot<Node>>,
72    pub(crate) last_partially_contained_child: Option<DomRoot<Node>>,
73    pub(crate) contained_children: Vec<DomRoot<Node>>,
74}
75
76impl Range {
77    fn new_inherited(
78        start_container: &Node,
79        start_offset: u32,
80        end_container: &Node,
81        end_offset: u32,
82        start_container_document: &Document,
83    ) -> Self {
84        debug_assert!(start_offset <= start_container.len());
85        debug_assert!(end_offset <= end_container.len());
86        Self {
87            abstract_range: AbstractRange::new_inherited(
88                start_container,
89                start_offset,
90                end_container,
91                end_offset,
92            ),
93            document: MutDom::new(start_container_document),
94            associated_selections: DomRefCell::new(vec![]),
95        }
96    }
97
98    pub(crate) fn new_with_doc(
99        cx: &mut JSContext,
100        document: &Document,
101        proto: Option<HandleObject>,
102    ) -> DomRoot<Self> {
103        let root = document.upcast();
104        Self::new_with_proto(cx, document, proto, root, 0, root, 0)
105    }
106
107    pub(crate) fn new(
108        cx: &mut JSContext,
109        document: &Document,
110        start_container: &Node,
111        start_offset: u32,
112        end_container: &Node,
113        end_offset: u32,
114    ) -> DomRoot<Range> {
115        Self::new_with_proto(
116            cx,
117            document,
118            None,
119            start_container,
120            start_offset,
121            end_container,
122            end_offset,
123        )
124    }
125
126    fn new_with_proto(
127        cx: &mut JSContext,
128        document_to_reflect_into: &Document,
129        proto: Option<HandleObject>,
130        start_container: &Node,
131        start_offset: u32,
132        end_container: &Node,
133        end_offset: u32,
134    ) -> DomRoot<Range> {
135        let start_container_document = start_container.owner_document();
136        let range = reflect_weak_referenceable_dom_object_with_proto(
137            cx,
138            Rc::new(Self::new_inherited(
139                start_container,
140                start_offset,
141                end_container,
142                end_offset,
143                &start_container_document,
144            )),
145            document_to_reflect_into.window(),
146            proto,
147        );
148
149        start_container_document
150            .live_ranges()
151            .push(WeakRef::new(&range));
152
153        range
154    }
155
156    /// <https://dom.spec.whatwg.org/#concept-range-root>
157    ///
158    /// > The root of a live range is the root of its start node.
159    pub(crate) fn root(&self) -> DomRoot<Node> {
160        self.start_container().GetRootNode(&Default::default())
161    }
162
163    /// <https://dom.spec.whatwg.org/#contained>
164    pub(crate) fn contains(&self, no_gc: &NoGC, node: &Node) -> bool {
165        // > A node node is contained in a live range range if node’s root is range’s root,
166        // > and (node, 0) is after range’s start, and (node, node’s length) is before range’s end.
167        node.GetRootNode(&Default::default()) == self.root() &&
168            matches!(
169                (
170                    bp_position(no_gc, node, 0, &self.start_container(), self.start_offset()),
171                    bp_position(
172                        no_gc,
173                        node,
174                        node.len(),
175                        &self.end_container(),
176                        self.end_offset()
177                    ),
178                ),
179                (Ordering::Greater, Ordering::Less)
180            )
181    }
182
183    /// <https://dom.spec.whatwg.org/#partially-contained>
184    fn partially_contains(&self, node: &Node) -> bool {
185        // > A node is partially contained in a live range if it’s an inclusive ancestor
186        // > of the live range’s start node but not its end node, or vice versa.
187        self.start_container()
188            .inclusive_ancestors(ShadowIncluding::No)
189            .any(|n| &*n == node) !=
190            self.end_container()
191                .inclusive_ancestors(ShadowIncluding::No)
192                .any(|n| &*n == node)
193    }
194
195    /// <https://dom.spec.whatwg.org/#concept-range-clone>
196    pub(crate) fn contained_children(&self, no_gc: &NoGC) -> Fallible<ContainedChildren> {
197        let start_node = self.start_container();
198        let end_node = self.end_container();
199        // Steps 5-6.
200        let common_ancestor = self.CommonAncestorContainer();
201
202        let first_partially_contained_child = if start_node.is_inclusive_ancestor_of(&end_node) {
203            // Step 7.
204            None
205        } else {
206            // Step 8.
207            common_ancestor
208                .children()
209                .find(|node| Range::partially_contains(self, node))
210        };
211
212        let last_partially_contained_child = if end_node.is_inclusive_ancestor_of(&start_node) {
213            // Step 9.
214            None
215        } else {
216            // Step 10.
217            common_ancestor
218                .rev_children()
219                .find(|node| Range::partially_contains(self, node))
220        };
221
222        // Step 11.
223        let contained_children: Vec<DomRoot<Node>> = common_ancestor
224            .children()
225            .filter(|n| self.contains(no_gc, n))
226            .collect();
227
228        // Step 12.
229        if contained_children.iter().any(|n| n.is_doctype()) {
230            return Err(Error::HierarchyRequest(Some(
231                "Range contains a doctype node".into(),
232            )));
233        }
234
235        Ok(ContainedChildren {
236            first_partially_contained_child,
237            last_partially_contained_child,
238            contained_children,
239        })
240    }
241
242    /// <https://dom.spec.whatwg.org/#concept-range-bp-set>
243    pub(crate) fn set_start(&self, no_gc: &NoGC, node: &Node, offset: u32) {
244        if self.set_start_without_reporting(node, offset) {
245            self.report_change(no_gc, SelectionLiveRangeNotification::Start);
246        }
247    }
248
249    pub(crate) fn set_start_without_reporting(&self, node: &Node, offset: u32) -> bool {
250        if self.start().node() == node && self.start_offset() == offset {
251            return false;
252        }
253        self.start().set(node, offset);
254        true
255    }
256
257    /// <https://dom.spec.whatwg.org/#concept-range-bp-set>
258    pub(crate) fn set_end(&self, no_gc: &NoGC, node: &Node, offset: u32) {
259        if self.set_end_without_reporting(node, offset) {
260            self.report_change(no_gc, SelectionLiveRangeNotification::End);
261        }
262    }
263
264    pub(crate) fn set_end_without_reporting(&self, node: &Node, offset: u32) -> bool {
265        if self.end().node() == node && self.end_offset() == offset {
266            return false;
267        }
268        self.end().set(node, offset);
269        true
270    }
271
272    /// <https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset>
273    fn compare_point(&self, no_gc: &NoGC, node: &Node, offset: u32) -> Fallible<Ordering> {
274        // Step 1. If node’s root is not this’s root, then throw a "WrongDocumentError"
275        // DOMException.
276        if node.GetRootNode(&Default::default()) != self.root() {
277            return Err(Error::WrongDocument(Some(
278                "Node is not in the same tree as the range".into(),
279            )));
280        }
281        // Step 2. If node is a doctype, then throw an "InvalidNodeTypeError"
282        // DOMException.
283        if node.is_doctype() {
284            return Err(Error::InvalidNodeType(Some("Node is a doctype".into())));
285        }
286        // Step 3. If offset is greater than node’s length, then throw an "IndexSizeError"
287        // DOMException.
288        if offset > node.len() {
289            return Err(Error::IndexSize(Some(
290                "Offset is greater than the node's length".into(),
291            )));
292        }
293        // Step 4. If (node, offset) is before start, then return −1.
294        let start_node = self.start_container();
295        if let Ordering::Less = bp_position(no_gc, node, offset, &start_node, self.start_offset()) {
296            return Ok(Ordering::Less);
297        }
298        // Step 5. If (node, offset) is after end, then return 1.
299        if let Ordering::Greater = bp_position(
300            no_gc,
301            node,
302            offset,
303            &self.end_container(),
304            self.end_offset(),
305        ) {
306            return Ok(Ordering::Greater);
307        }
308        // Step 6. Return 0.
309        Ok(Ordering::Equal)
310    }
311
312    pub(crate) fn associate_selection(&self, selection: &Selection) {
313        let mut selections = self.associated_selections.borrow_mut();
314        if !selections.iter().any(|s| &**s == selection) {
315            selections.push(Dom::from_ref(selection));
316        }
317    }
318
319    pub(crate) fn disassociate_selection(&self, selection: &Selection) {
320        self.associated_selections
321            .borrow_mut()
322            .retain(|s| &**s != selection);
323    }
324
325    pub(crate) fn report_change(&self, no_gc: &NoGC, notification: SelectionLiveRangeNotification) {
326        if notification.is_empty() {
327            return;
328        }
329
330        // Clearing the selection might, in turn, call `Self::disassociate_selection`, so
331        // a `SmallVec` is necessary here to avoid a borrow hazard.
332        let selections: SmallVec<[DomRoot<Selection>; 1]> = self
333            .associated_selections
334            .borrow()
335            .iter()
336            .map(Dom::as_rooted)
337            .collect();
338        for selection in selections {
339            if !selection.clear_selection_if_live_range_document_changed(no_gc, self) {
340                selection.update_from_live_range(no_gc, self, notification);
341            }
342        }
343    }
344
345    fn abstract_range(&self) -> &AbstractRange {
346        &self.abstract_range
347    }
348
349    pub(crate) fn start(&self) -> &BoundaryPoint {
350        self.abstract_range().start()
351    }
352
353    pub(crate) fn end(&self) -> &BoundaryPoint {
354        self.abstract_range().end()
355    }
356
357    pub(crate) fn start_container(&self) -> DomRoot<Node> {
358        self.abstract_range().StartContainer()
359    }
360
361    pub(crate) fn start_offset(&self) -> u32 {
362        self.abstract_range().StartOffset()
363    }
364
365    pub(crate) fn end_container(&self) -> DomRoot<Node> {
366        self.abstract_range().EndContainer()
367    }
368
369    pub(crate) fn end_offset(&self) -> u32 {
370        self.abstract_range().EndOffset()
371    }
372
373    pub(crate) fn collapsed(&self) -> bool {
374        self.abstract_range().Collapsed()
375    }
376
377    /// <https://drafts.csswg.org/cssom-view/#dom-range-getclientrects>
378    fn client_rects(&self, cx: &mut JSContext) -> Vec<Rect<Au, CSSPixel>> {
379        // FIXME: For text nodes that are only partially selected, this should return the client
380        // rect of the selected part, not the whole text node.
381        let start = self.start_container();
382        let end = self.end_container();
383        // > The getClientRects() method, when invoked, must return an empty DOMRectList
384        // > object if the range is not in the document.
385        if !start.is_connected() || !end.is_connected() {
386            return vec![];
387        }
388
389        // Per the spec, only Text nodes contribute rects when the range is collapsed
390        // (including when the boundary points are identical).
391        if self.collapsed() {
392            if start.is::<CharacterData>() {
393                return start.border_boxes(cx);
394            } else {
395                return vec![];
396            }
397        }
398
399        let document = start.owner_doc();
400        let nodes: Vec<DomRoot<Node>> = {
401            let unrooted_end = end.as_unrooted(cx.no_gc());
402            start
403                .following_nodes_unrooted(
404                    cx.no_gc(),
405                    document.upcast::<Node>(),
406                    ShadowIncluding::No,
407                )
408                .take_while(|node| *node != *end)
409                .chain(iter::once(unrooted_end))
410                .map(|node| node.as_rooted())
411                .collect()
412        };
413
414        nodes
415            .iter()
416            .flat_map(move |node| node.border_boxes(cx))
417            .collect()
418    }
419
420    /// <https://dom.spec.whatwg.org/#concept-range-bp-set>
421    fn set_the_start_or_end(
422        &self,
423        no_gc: &NoGC,
424        node: &Node,
425        offset: u32,
426        start_or_end: StartOrEnd,
427    ) -> ErrorResult {
428        // Step 1. If node is a doctype, then throw an "InvalidNodeTypeError"
429        // DOMException.
430        if node.is_doctype() {
431            return Err(Error::InvalidNodeType(Some("Node is a doctype".into())));
432        }
433
434        // Step 2. If offset is greater than node’s length, then throw an "IndexSizeError"
435        // DOMException.
436        if offset > node.len() {
437            return Err(Error::IndexSize(Some(
438                "Offset is greater than the node's length".into(),
439            )));
440        }
441
442        // Step 3. Let bp be the boundary point (node, offset).
443        // NOTE: We don't need this part.
444        let mut notification = SelectionLiveRangeNotification::empty();
445        match start_or_end {
446            // If these steps were invoked as "set the start"
447            StartOrEnd::Start => {
448                // Step 4.1. If range’s root is not equal to node’s root, or if bp is after
449                // the range’s end, set range’s end to bp.
450                if self.root() != node.GetRootNode(&Default::default()) ||
451                    bp_position(
452                        no_gc,
453                        node,
454                        offset,
455                        &self.end_container(),
456                        self.end_offset(),
457                    ) == Ordering::Greater
458                {
459                    self.set_end_without_reporting(node, offset);
460                }
461
462                // Step 4.2. Set range’s start to bp.
463                notification.set(
464                    SelectionLiveRangeNotification::Start,
465                    self.set_start_without_reporting(node, offset),
466                );
467            },
468            // If these steps were invoked as "set the end"
469            StartOrEnd::End => {
470                // Step 4.1. If range’s root is not equal to node’s root, or if bp is
471                // before the range’s start, set range’s start to bp.
472                if self.root() != node.GetRootNode(&Default::default()) ||
473                    bp_position(
474                        no_gc,
475                        node,
476                        offset,
477                        &self.start_container(),
478                        self.start_offset(),
479                    ) == Ordering::Less
480                {
481                    self.set_start_without_reporting(node, offset);
482                }
483
484                // Step 4.2. Set range’s end to bp.
485                notification.set(
486                    SelectionLiveRangeNotification::End,
487                    self.set_end_without_reporting(node, offset),
488                );
489            },
490        }
491
492        self.maybe_update_document();
493        self.report_change(no_gc, notification);
494        Ok(())
495    }
496
497    /// When:
498    ///
499    /// - The start and end node have changed in a way in which they no longer have the same
500    ///   [`Document`]
501    /// - A node is adopted into a different [`Document`]
502    ///
503    /// this method ensures that the [`Document`] of this [`Range`] remains the same. If it doesn't
504    /// the [`Range`] is moved from the old [`Document`] to the new one.
505    pub(crate) fn maybe_update_document(&self) {
506        // Note: `debug_assert_eq!` requires the arguments to implement `Debug`.
507        debug_assert!(
508            self.start_container().owner_document() == self.end_container().owner_document(),
509        );
510
511        let current_document = self.document.get();
512        let new_document = self.start_container().owner_document();
513        if new_document != current_document {
514            current_document.live_ranges().remove(self);
515            new_document.live_ranges().push(WeakRef::new(self));
516            self.document.set(&new_document);
517        }
518    }
519
520    pub(crate) fn start_and_end_are_in_document_tree(&self) -> bool {
521        self.start_container().is_in_a_document_tree() &&
522            self.end_container().is_in_a_document_tree()
523    }
524}
525
526impl std::fmt::Debug for Range {
527    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
528        write!(
529            f,
530            "[({:?}, {}) -> ({:?}, {})]",
531            self.start_container(),
532            self.start_offset(),
533            self.end_container(),
534            self.end_offset()
535        )
536    }
537}
538
539#[derive(Copy, Clone)]
540pub(crate) enum StartOrEnd {
541    Start,
542    End,
543}
544
545impl RangeMethods<crate::DomTypeHolder> for Range {
546    /// <https://dom.spec.whatwg.org/#dom-range>
547    fn Constructor(
548        cx: &mut JSContext,
549        window: &Window,
550        proto: Option<HandleObject>,
551    ) -> Fallible<DomRoot<Range>> {
552        let document = window.Document();
553        Ok(Range::new_with_doc(cx, &document, proto))
554    }
555
556    /// <https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer>
557    fn CommonAncestorContainer(&self) -> DomRoot<Node> {
558        self.end_container()
559            .common_ancestor(&self.start_container(), ShadowIncluding::No)
560            .expect("Couldn't find common ancestor container")
561    }
562
563    /// <https://dom.spec.whatwg.org/#dom-range-setstart>
564    fn SetStart(&self, no_gc: &NoGC, node: &Node, offset: u32) -> ErrorResult {
565        self.set_the_start_or_end(no_gc, node, offset, StartOrEnd::Start)
566    }
567
568    /// <https://dom.spec.whatwg.org/#dom-range-setend>
569    fn SetEnd(&self, no_gc: &NoGC, node: &Node, offset: u32) -> ErrorResult {
570        self.set_the_start_or_end(no_gc, node, offset, StartOrEnd::End)
571    }
572
573    /// <https://dom.spec.whatwg.org/#dom-range-setstartbefore>
574    fn SetStartBefore(&self, no_gc: &NoGC, node: &Node) -> ErrorResult {
575        let parent = node
576            .GetParentNode()
577            .ok_or(Error::InvalidNodeType(Some("Node has no parent".into())))?;
578        self.SetStart(no_gc, &parent, node.index())
579    }
580
581    /// <https://dom.spec.whatwg.org/#dom-range-setstartafter>
582    fn SetStartAfter(&self, no_gc: &NoGC, node: &Node) -> ErrorResult {
583        let parent = node
584            .GetParentNode()
585            .ok_or(Error::InvalidNodeType(Some("Node has no parent".into())))?;
586        self.SetStart(no_gc, &parent, node.index() + 1)
587    }
588
589    /// <https://dom.spec.whatwg.org/#dom-range-setendbefore>
590    fn SetEndBefore(&self, no_gc: &NoGC, node: &Node) -> ErrorResult {
591        let parent = node
592            .GetParentNode()
593            .ok_or(Error::InvalidNodeType(Some("Node has no parent".into())))?;
594        self.SetEnd(no_gc, &parent, node.index())
595    }
596
597    /// <https://dom.spec.whatwg.org/#dom-range-setendafter>
598    fn SetEndAfter(&self, no_gc: &NoGC, node: &Node) -> ErrorResult {
599        let parent = node
600            .GetParentNode()
601            .ok_or(Error::InvalidNodeType(Some("Node has no parent".into())))?;
602        self.SetEnd(no_gc, &parent, node.index() + 1)
603    }
604
605    /// <https://dom.spec.whatwg.org/#dom-range-collapse>
606    fn Collapse(&self, no_gc: &NoGC, to_start: bool) {
607        if to_start {
608            self.set_end(no_gc, &self.start_container(), self.start_offset());
609        } else {
610            self.set_start(no_gc, &self.end_container(), self.end_offset());
611        }
612    }
613
614    /// <https://dom.spec.whatwg.org/#dom-range-selectnode>
615    fn SelectNode(&self, no_gc: &NoGC, node: &Node) -> ErrorResult {
616        // Steps 1, 2.
617        let parent = node
618            .GetParentNode()
619            .ok_or(Error::InvalidNodeType(Some("Node has no parent".into())))?;
620        // Step 3.
621        let index = node.index();
622        // Step 4.
623        self.set_start(no_gc, &parent, index);
624        // Step 5.
625        self.set_end(no_gc, &parent, index + 1);
626        Ok(())
627    }
628
629    /// <https://dom.spec.whatwg.org/#dom-range-selectnodecontents>
630    fn SelectNodeContents(&self, no_gc: &NoGC, node: &Node) -> ErrorResult {
631        if node.is_doctype() {
632            // Step 1.
633            return Err(Error::InvalidNodeType(Some("Node is a doctype".into())));
634        }
635        // Step 2.
636        let length = node.len();
637        // Step 3.
638        self.set_start(no_gc, node, 0);
639        // Step 4.
640        self.set_end(no_gc, node, length);
641        Ok(())
642    }
643
644    /// <https://dom.spec.whatwg.org/#dom-range-compareboundarypoints>
645    fn CompareBoundaryPoints(&self, no_gc: &NoGC, how: u16, source_range: &Range) -> Fallible<i16> {
646        // Step 1. If how is not one of
647        //    * START_TO_START,
648        //    * START_TO_END,
649        //    * END_TO_END, and
650        //    * END_TO_START,
651        // then throw a "NotSupportedError" DOMException.
652        if how > RangeConstants::END_TO_START {
653            return Err(Error::NotSupported(Some(
654                "Invalid comparison method".into(),
655            )));
656        }
657        // Step 2. If this’s root is not sourceRange’s root, then throw a
658        // "WrongDocumentError" DOMException.
659        if self.root() != source_range.root() {
660            return Err(Error::WrongDocument(Some(
661                "Ranges are not in the same tree".into(),
662            )));
663        }
664        // Step 3. Let thisPoint and sourcePoint be null.
665        // Step 4.  Switch on how:
666        //  ↪ START_TO_START:
667        //     Set thisPoint to this’s start and sourcePoint to sourceRange’s start.
668        //  ↪ START_TO_END:
669        //     Set thisPoint to this’s end and sourcePoint to sourceRange’s start.
670        //  ↪ END_TO_END:
671        //     Set thisPoint to this’s end and sourcePoint to sourceRange’s end.
672        //  ↪ END_TO_START:
673        //     Set thisPoint to this’s start and sourcePoint to sourceRange’s end.
674        let (this_point, source_point) = match how {
675            RangeConstants::START_TO_START => (self.start(), source_range.start()),
676            RangeConstants::START_TO_END => (self.end(), source_range.start()),
677            RangeConstants::END_TO_END => (self.end(), source_range.end()),
678            RangeConstants::END_TO_START => (self.start(), source_range.end()),
679            _ => unreachable!(),
680        };
681        // Step 5. Switch on the position of thisPoint relative to sourcePoint:
682        //  ↪ before
683        //      Return −1.
684        //  ↪ equal
685        //      Return 0.
686        //  ↪ after
687        //      Return 1.
688        match this_point.partial_cmp(no_gc, source_point).unwrap() {
689            Ordering::Less => Ok(-1),
690            Ordering::Equal => Ok(0),
691            Ordering::Greater => Ok(1),
692        }
693    }
694
695    /// <https://dom.spec.whatwg.org/#dom-range-clonerange>
696    fn CloneRange(&self, cx: &mut JSContext) -> DomRoot<Range> {
697        let start_node = self.start_container();
698        let owner_doc = start_node.owner_doc();
699        Range::new(
700            cx,
701            &owner_doc,
702            &start_node,
703            self.start_offset(),
704            &self.end_container(),
705            self.end_offset(),
706        )
707    }
708
709    /// <https://dom.spec.whatwg.org/#dom-range-ispointinrange>
710    fn IsPointInRange(&self, no_gc: &NoGC, node: &Node, offset: u32) -> Fallible<bool> {
711        match self.compare_point(no_gc, node, offset) {
712            Ok(Ordering::Less) => Ok(false),
713            Ok(Ordering::Equal) => Ok(true),
714            Ok(Ordering::Greater) => Ok(false),
715            Err(Error::WrongDocument(_)) => {
716                // Step 2.  If node’s root is not this’s root, then return false.
717                // Note: This is the only step that differs from `Self::compare_point`.
718                Ok(false)
719            },
720            Err(error) => Err(error),
721        }
722    }
723
724    /// <https://dom.spec.whatwg.org/#dom-range-comparepoint>
725    fn ComparePoint(&self, no_gc: &NoGC, node: &Node, offset: u32) -> Fallible<i16> {
726        self.compare_point(no_gc, node, offset)
727            .map(|order| match order {
728                Ordering::Less => -1,
729                Ordering::Equal => 0,
730                Ordering::Greater => 1,
731            })
732    }
733
734    /// <https://dom.spec.whatwg.org/#dom-range-intersectsnode>
735    fn IntersectsNode(&self, no_gc: &NoGC, node: &Node) -> bool {
736        // Step 1. If node’s root is not this’s root, then return false.
737        if self.root() != node.GetRootNode(&Default::default()) {
738            return false;
739        }
740        // Step 2. Let parent be node’s parent.
741        let Some(parent) = node.GetParentNode() else {
742            // Step 3. If parent is null, then return true.
743            return true;
744        };
745        // Step 4. Let offset be node’s index.
746        let offset = node.index();
747        // Step 5. If (parent, offset) is before end and (parent, offset + 1) is after
748        // start, then return true.
749        // Step 6. Return false.
750        let start_node = self.start_container();
751        Ordering::Greater ==
752            bp_position(no_gc, &parent, offset + 1, &start_node, self.start_offset()) &&
753            Ordering::Less ==
754                bp_position(
755                    no_gc,
756                    &parent,
757                    offset,
758                    &self.end_container(),
759                    self.end_offset(),
760                )
761    }
762
763    /// <https://dom.spec.whatwg.org/#dom-range-clonecontents>
764    /// <https://dom.spec.whatwg.org/#concept-range-clone>
765    fn CloneContents(&self, cx: &mut JSContext) -> Fallible<DomRoot<DocumentFragment>> {
766        // Step 3.
767        let start_node = self.start_container();
768        let start_offset = self.start_offset();
769        let end_node = self.end_container();
770        let end_offset = self.end_offset();
771
772        // Step 1.
773        let fragment = DocumentFragment::new(cx, &start_node.owner_doc());
774
775        // Step 2.
776        if self.start() == self.end() {
777            return Ok(fragment);
778        }
779
780        if end_node == start_node &&
781            let Some(cdata) = start_node.downcast::<CharacterData>()
782        {
783            // Steps 4.1-2.
784            let data = cdata
785                .SubstringData(start_offset, end_offset - start_offset)
786                .unwrap();
787            let clone = cdata.clone_with_data(cx, data, &start_node.owner_doc());
788            // Step 4.3.
789            fragment.upcast::<Node>().AppendChild(cx, &clone)?;
790            // Step 4.4
791            return Ok(fragment);
792        }
793
794        // Steps 5-12.
795        let ContainedChildren {
796            first_partially_contained_child,
797            last_partially_contained_child,
798            contained_children,
799        } = self.contained_children(cx.no_gc())?;
800
801        if let Some(child) = first_partially_contained_child {
802            // Step 13.
803            if let Some(cdata) = child.downcast::<CharacterData>() {
804                assert!(child == start_node);
805                // Steps 13.1-2.
806                let data = cdata
807                    .SubstringData(start_offset, start_node.len() - start_offset)
808                    .unwrap();
809                let clone = cdata.clone_with_data(cx, data, &start_node.owner_doc());
810                // Step 13.3.
811                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
812            } else {
813                // Step 14.1.
814                let clone = child.CloneNode(cx, /* deep */ false)?;
815                // Step 14.2.
816                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
817                // Step 14.3.
818                let subrange = Range::new(
819                    cx,
820                    &clone.owner_doc(),
821                    &start_node,
822                    start_offset,
823                    &child,
824                    child.len(),
825                );
826                // Step 14.4.
827                let subfragment = subrange.CloneContents(cx)?;
828                // Step 14.5.
829                clone.AppendChild(cx, subfragment.upcast())?;
830            }
831        }
832
833        // Step 15.
834        for child in contained_children {
835            // Step 15.1.
836            let clone = child.CloneNode(cx, /* deep */ true)?;
837            // Step 15.2.
838            fragment.upcast::<Node>().AppendChild(cx, &clone)?;
839        }
840
841        if let Some(child) = last_partially_contained_child {
842            // Step 16.
843            if let Some(cdata) = child.downcast::<CharacterData>() {
844                assert!(child == end_node);
845                // Steps 16.1-2.
846                let data = cdata.SubstringData(0, end_offset).unwrap();
847                let clone = cdata.clone_with_data(cx, data, &start_node.owner_doc());
848                // Step 16.3.
849                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
850            } else {
851                // Step 17.1.
852                let clone = child.CloneNode(cx, /* deep */ false)?;
853                // Step 17.2.
854                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
855                // Step 17.3.
856                let subrange = Range::new(cx, &clone.owner_doc(), &child, 0, &end_node, end_offset);
857                // Step 17.4.
858                let subfragment = subrange.CloneContents(cx)?;
859                // Step 17.5.
860                clone.AppendChild(cx, subfragment.upcast())?;
861            }
862        }
863
864        // Step 18.
865        Ok(fragment)
866    }
867
868    /// <https://dom.spec.whatwg.org/#dom-range-extractcontents>
869    /// <https://dom.spec.whatwg.org/#concept-range-extract>
870    fn ExtractContents(&self, cx: &mut JSContext) -> Fallible<DomRoot<DocumentFragment>> {
871        // Step 3.
872        let start_node = self.start_container();
873        let start_offset = self.start_offset();
874        let end_node = self.end_container();
875        let end_offset = self.end_offset();
876
877        // Step 1.
878        let fragment = DocumentFragment::new(cx, &start_node.owner_doc());
879
880        // Step 2.
881        if self.collapsed() {
882            return Ok(fragment);
883        }
884
885        if end_node == start_node &&
886            let Some(end_data) = end_node.downcast::<CharacterData>()
887        {
888            // Step 4.1.
889            let clone = end_node.CloneNode(cx, /* deep */ true)?;
890            // Step 4.2.
891            let text = end_data.SubstringData(start_offset, end_offset - start_offset);
892            clone
893                .downcast::<CharacterData>()
894                .unwrap()
895                .SetData(cx, text.unwrap());
896            // Step 4.3.
897            fragment.upcast::<Node>().AppendChild(cx, &clone)?;
898            // Step 4.4.
899            end_data.ReplaceData(
900                cx,
901                start_offset,
902                end_offset - start_offset,
903                DOMString::new(),
904            )?;
905            // Step 4.5.
906            return Ok(fragment);
907        }
908
909        // Steps 5-12.
910        let ContainedChildren {
911            first_partially_contained_child,
912            last_partially_contained_child,
913            contained_children,
914        } = self.contained_children(cx.no_gc())?;
915
916        let (new_node, new_offset) = if start_node.is_inclusive_ancestor_of(&end_node) {
917            // Step 13.
918            (DomRoot::from_ref(&*start_node), start_offset)
919        } else {
920            // Step 14.1-2.
921            let reference_node = start_node
922                .ancestors()
923                .take_while(|n| !n.is_inclusive_ancestor_of(&end_node))
924                .last()
925                .unwrap_or(DomRoot::from_ref(&start_node));
926            // Step 14.3.
927            (
928                reference_node.GetParentNode().unwrap(),
929                reference_node.index() + 1,
930            )
931        };
932
933        if let Some(child) = first_partially_contained_child {
934            if let Some(start_data) = child.downcast::<CharacterData>() {
935                assert!(child == start_node);
936                // Step 15.1.
937                let clone = start_node.CloneNode(cx, /* deep */ true)?;
938                // Step 15.2.
939                let text = start_data.SubstringData(start_offset, start_node.len() - start_offset);
940                clone
941                    .downcast::<CharacterData>()
942                    .unwrap()
943                    .SetData(cx, text.unwrap());
944                // Step 15.3.
945                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
946                // Step 15.4.
947                start_data.ReplaceData(
948                    cx,
949                    start_offset,
950                    start_node.len() - start_offset,
951                    DOMString::new(),
952                )?;
953            } else {
954                // Step 16.1.
955                let clone = child.CloneNode(cx, /* deep */ false)?;
956                // Step 16.2.
957                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
958                // Step 16.3.
959                let subrange = Range::new(
960                    cx,
961                    &clone.owner_doc(),
962                    &start_node,
963                    start_offset,
964                    &child,
965                    child.len(),
966                );
967                // Step 16.4.
968                let subfragment = subrange.ExtractContents(cx)?;
969                // Step 16.5.
970                clone.AppendChild(cx, subfragment.upcast())?;
971            }
972        }
973
974        // Step 17.
975        for child in contained_children {
976            fragment.upcast::<Node>().AppendChild(cx, &child)?;
977        }
978
979        if let Some(child) = last_partially_contained_child {
980            if let Some(end_data) = child.downcast::<CharacterData>() {
981                assert!(child == end_node);
982                // Step 18.1.
983                let clone = end_node.CloneNode(cx, /* deep */ true)?;
984                // Step 18.2.
985                let text = end_data.SubstringData(0, end_offset);
986                clone
987                    .downcast::<CharacterData>()
988                    .unwrap()
989                    .SetData(cx, text.unwrap());
990                // Step 18.3.
991                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
992                // Step 18.4.
993                end_data.ReplaceData(cx, 0, end_offset, DOMString::new())?;
994            } else {
995                // Step 19.1.
996                let clone = child.CloneNode(cx, /* deep */ false)?;
997                // Step 19.2.
998                fragment.upcast::<Node>().AppendChild(cx, &clone)?;
999                // Step 19.3.
1000                let subrange = Range::new(cx, &clone.owner_doc(), &child, 0, &end_node, end_offset);
1001                // Step 19.4.
1002                let subfragment = subrange.ExtractContents(cx)?;
1003                // Step 19.5.
1004                clone.AppendChild(cx, subfragment.upcast())?;
1005            }
1006        }
1007
1008        // Step 20.
1009        self.SetStart(cx.no_gc(), &new_node, new_offset)?;
1010        self.SetEnd(cx.no_gc(), &new_node, new_offset)?;
1011
1012        // Step 21.
1013        Ok(fragment)
1014    }
1015
1016    /// <https://dom.spec.whatwg.org/#dom-range-detach>
1017    /// > The detach() method steps are to do nothing. Its functionality (disabling a Range object)
1018    /// > was removed, but the method itself is preserved for compatibility.
1019    fn Detach(&self) {
1020        // This method intentionally left blank.
1021    }
1022
1023    /// <https://dom.spec.whatwg.org/#dom-range-insertnode>
1024    /// <https://dom.spec.whatwg.org/#concept-range-insert>
1025    fn InsertNode(&self, cx: &mut JSContext, node: &Node) -> ErrorResult {
1026        let start_node = self.start_container();
1027        let start_offset = self.start_offset();
1028
1029        // Step 1.
1030        if &*start_node == node {
1031            return Err(Error::HierarchyRequest(Some(
1032                "Node is the range's start container".into(),
1033            )));
1034        }
1035        match start_node.type_id() {
1036            // Handled under step 2.
1037            NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => (),
1038            NodeTypeId::CharacterData(_) => {
1039                return Err(Error::HierarchyRequest(Some(
1040                    "Cannot insert into a Comment or ProcessingInstruction node".into(),
1041                )));
1042            },
1043            _ => (),
1044        }
1045
1046        // Step 2.
1047        let (reference_node, parent) = match start_node.type_id() {
1048            NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => {
1049                // Step 3.
1050                let parent = match start_node.GetParentNode() {
1051                    Some(parent) => parent,
1052                    // Step 1.
1053                    None => {
1054                        return Err(Error::HierarchyRequest(Some(
1055                            "Range's start container has no parent".into(),
1056                        )));
1057                    },
1058                };
1059                // Step 5.
1060                (Some(DomRoot::from_ref(&*start_node)), parent)
1061            },
1062            _ => {
1063                // Steps 4-5.
1064                let child = start_node.ChildNodes(cx).Item(cx, start_offset);
1065                (child, DomRoot::from_ref(&*start_node))
1066            },
1067        };
1068
1069        // Step 6.
1070        Node::ensure_pre_insertion_validity(
1071            cx.no_gc(),
1072            node,
1073            &parent,
1074            reference_node.as_deref(),
1075            &[],
1076        )?;
1077
1078        // Step 7.
1079        let split_text;
1080        let reference_node = match start_node.downcast::<Text>() {
1081            Some(text) => {
1082                split_text = text.SplitText(cx, start_offset)?;
1083                let new_reference = DomRoot::upcast::<Node>(split_text);
1084                assert!(new_reference.GetParentNode().as_deref() == Some(&parent));
1085                Some(new_reference)
1086            },
1087            _ => reference_node,
1088        };
1089
1090        // Step 8.
1091        let reference_node = if Some(node) == reference_node.as_deref() {
1092            node.GetNextSibling()
1093        } else {
1094            reference_node
1095        };
1096
1097        // Step 9.
1098        node.remove_self(cx);
1099
1100        // Step 10.
1101        let new_offset = reference_node
1102            .as_ref()
1103            .map_or(parent.len(), |node| node.index());
1104
1105        // Step 11
1106        let new_offset = new_offset +
1107            if let NodeTypeId::DocumentFragment(_) = node.type_id() {
1108                node.len()
1109            } else {
1110                1
1111            };
1112
1113        // Step 12.
1114        Node::pre_insert(cx, node, &parent, reference_node.as_deref())?;
1115
1116        // Step 13.
1117        if self.collapsed() {
1118            self.set_end(cx.no_gc(), &parent, new_offset);
1119        }
1120
1121        Ok(())
1122    }
1123
1124    /// <https://dom.spec.whatwg.org/#dom-range-deletecontents>
1125    fn DeleteContents(&self, cx: &mut JSContext) -> ErrorResult {
1126        // Step 1. If this is collapsed, then return.
1127        if self.collapsed() {
1128            return Ok(());
1129        }
1130
1131        // Step 2. Let originalStartNode, originalStartOffset, originalEndNode,
1132        // and originalEndOffset be this’s start node, start offset, end node, and end offset, respectively.
1133        let start_node = self.start_container();
1134        let end_node = self.end_container();
1135        let start_offset = self.start_offset();
1136        let end_offset = self.end_offset();
1137
1138        // Step 3. If originalStartNode is originalEndNode and it is a CharacterData node:
1139        if start_node == end_node &&
1140            let Some(text) = start_node.downcast::<CharacterData>()
1141        {
1142            // Step 3.1. Replace data of originalStartNode with originalStartOffset,
1143            // originalEndOffset − originalStartOffset, and the empty string.
1144            // Step 3.2. Return.
1145            return text.ReplaceData(
1146                cx,
1147                start_offset,
1148                end_offset - start_offset,
1149                DOMString::new(),
1150            );
1151        }
1152
1153        // Step 4. Let nodesToRemove be a list of all the nodes that are contained in this,
1154        // in tree order, omitting any node whose parent is also contained in this.
1155        rooted_vec!(let mut contained_children);
1156        let ancestor = self.CommonAncestorContainer();
1157
1158        let mut iter = start_node.following_nodes(&ancestor, ShadowIncluding::No);
1159
1160        let mut next = iter.next();
1161        while let Some(child) = next {
1162            if self.contains(cx.no_gc(), &child) {
1163                contained_children.push(Dom::from_ref(&*child));
1164                next = iter.next_skipping_children();
1165            } else {
1166                next = iter.next();
1167            }
1168        }
1169
1170        // Step 5. Let newNode and newOffset be null.
1171        // Step 6. If originalStartNode is an inclusive ancestor of originalEndNode,
1172        // then set newNode to originalStartNode and newOffset to originalStartOffset.
1173        let (new_node, new_offset) = if start_node.is_inclusive_ancestor_of(&end_node) {
1174            (DomRoot::from_ref(&*start_node), start_offset)
1175        } else {
1176            // Step 7. Otherwise:
1177            fn compute_reference(start_node: &Node, end_node: &Node) -> (DomRoot<Node>, u32) {
1178                // Step 7.1. Let referenceNode be originalStartNode.
1179                let mut reference_node = DomRoot::from_ref(start_node);
1180                // Step 7.2. While referenceNode’s parent is non-null and
1181                // is not an inclusive ancestor of originalEndNode: set referenceNode to its parent.
1182                while let Some(parent) = reference_node.GetParentNode() {
1183                    if parent.is_inclusive_ancestor_of(end_node) {
1184                        // Step 7.3. Set newNode to referenceNode’s parent and newOffset to referenceNode’s index + 1.
1185                        return (parent, reference_node.index() + 1);
1186                    }
1187                    reference_node = parent;
1188                }
1189                unreachable!()
1190            }
1191
1192            compute_reference(&start_node, &end_node)
1193        };
1194
1195        // Step 8. Set this’s start and end to (newNode, newOffset).
1196        self.SetStart(cx.no_gc(), &new_node, new_offset).unwrap();
1197        self.SetEnd(cx.no_gc(), &new_node, new_offset).unwrap();
1198
1199        // Step 9. If originalStartNode is a CharacterData node,
1200        // then replace data of originalStartNode with originalStartOffset,
1201        // originalStartNode’s length − originalStartOffset, and the empty string.
1202        if let Some(text) = start_node.downcast::<CharacterData>() {
1203            text.ReplaceData(
1204                cx,
1205                start_offset,
1206                start_node.len() - start_offset,
1207                DOMString::new(),
1208            )
1209            .unwrap();
1210        }
1211
1212        // Step 10. For each node of nodesToRemove, in tree order: remove node.
1213        for child in &*contained_children {
1214            child.remove_self(cx);
1215        }
1216
1217        // Step 11. If originalEndNode is a CharacterData node,
1218        // then replace data of originalEndNode with 0, originalEndOffset, and the empty string.
1219        if let Some(text) = end_node.downcast::<CharacterData>() {
1220            text.ReplaceData(cx, 0, end_offset, DOMString::new())
1221                .unwrap();
1222        }
1223
1224        Ok(())
1225    }
1226
1227    /// <https://dom.spec.whatwg.org/#dom-range-surroundcontents>
1228    fn SurroundContents(&self, cx: &mut JSContext, new_parent: &Node) -> ErrorResult {
1229        // Step 1.
1230        let start = self.start_container();
1231        let end = self.end_container();
1232
1233        if start
1234            .inclusive_ancestors(ShadowIncluding::No)
1235            .any(|n| !n.is_inclusive_ancestor_of(&end) && !n.is::<Text>()) ||
1236            end.inclusive_ancestors(ShadowIncluding::No)
1237                .any(|n| !n.is_inclusive_ancestor_of(&start) && !n.is::<Text>())
1238        {
1239            return Err(Error::InvalidState(Some(
1240                "Range partially contains a non-Text node".into(),
1241            )));
1242        }
1243
1244        // Step 2.
1245        match new_parent.type_id() {
1246            NodeTypeId::Document(_) |
1247            NodeTypeId::DocumentType |
1248            NodeTypeId::DocumentFragment(_) => {
1249                return Err(Error::InvalidNodeType(Some(
1250                    "Node cannot be a Document, DocumentType or DocumentFragment".into(),
1251                )));
1252            },
1253            _ => (),
1254        }
1255
1256        // Step 3.
1257        let fragment = self.ExtractContents(cx)?;
1258
1259        // Step 4.
1260        Node::replace_all(cx, None, new_parent);
1261
1262        // Step 5.
1263        self.InsertNode(cx, new_parent)?;
1264
1265        // Step 6.
1266        new_parent.AppendChild(cx, fragment.upcast())?;
1267
1268        // Step 7.
1269        self.SelectNode(cx.no_gc(), new_parent)
1270    }
1271
1272    /// <https://dom.spec.whatwg.org/#dom-range-stringifier>
1273    fn Stringifier(&self, no_gc: &NoGC) -> DOMString {
1274        let start_node = self.start_container();
1275        let end_node = self.end_container();
1276
1277        // Step 1. Let string be the empty string.
1278        let mut s = DOMString::new();
1279
1280        if let Some(text_node) = start_node.downcast::<Text>() {
1281            let char_data = text_node.upcast::<CharacterData>();
1282
1283            // Step 2. If this’s start node is this’s end node and it is a Text node,
1284            // then return the substring of that Text node’s data beginning at
1285            // this’s start offset and ending at this’s end offset.
1286            if start_node == end_node {
1287                return char_data
1288                    .SubstringData(self.start_offset(), self.end_offset() - self.start_offset())
1289                    .unwrap();
1290            }
1291
1292            // Step 3. If this’s start node is a Text node, then append the substring of
1293            // that node’s data from this’s start offset until the end to string.
1294            s.push_str(
1295                &char_data
1296                    .SubstringData(
1297                        self.start_offset(),
1298                        char_data.Length() - self.start_offset(),
1299                    )
1300                    .unwrap()
1301                    .str(),
1302            );
1303        }
1304
1305        // Step 4. Append the concatenation of the data of all Text nodes that are contained in this,
1306        // in tree order, to string.
1307        let ancestor = self.CommonAncestorContainer();
1308        let iter = start_node
1309            .following_nodes_unrooted(no_gc, &ancestor, ShadowIncluding::No)
1310            .filter_map(UnrootedDom::downcast::<Text>);
1311
1312        for child in iter {
1313            if self.contains(no_gc, child.upcast()) {
1314                s.push_str(&child.upcast::<CharacterData>().Data().str());
1315            }
1316        }
1317
1318        // Step 5. If this’s end node is a Text node, then append the substring of
1319        // that node’s data from its start until this’s end offset to string.
1320        if let Some(text_node) = end_node.downcast::<Text>() {
1321            let char_data = text_node.upcast::<CharacterData>();
1322            s.push_str(&char_data.SubstringData(0, self.end_offset()).unwrap().str());
1323        }
1324
1325        // Step 6. Return string.
1326        s
1327    }
1328
1329    /// <https://html.spec.whatwg.org/multipage/#dom-range-createcontextualfragment>
1330    fn CreateContextualFragment(
1331        &self,
1332        cx: &mut JSContext,
1333        fragment: TrustedHTMLOrString,
1334    ) -> Fallible<DomRoot<DocumentFragment>> {
1335        // Step 2. Let node be this's start node.
1336        //
1337        // Required to obtain the global, so we do this first. Shouldn't be an
1338        // observable difference.
1339        let node = self.start_container();
1340
1341        // Step 1. Let compliantString be the result of invoking the
1342        // Get Trusted Type compliant string algorithm with TrustedHTML,
1343        // this's relevant global object, string, "Range createContextualFragment", and "script".
1344        let fragment = TrustedHTML::get_trusted_type_compliant_string(
1345            cx,
1346            node.owner_window().upcast(),
1347            fragment,
1348            "Range createContextualFragment",
1349        )?;
1350
1351        let owner_doc = node.owner_doc();
1352
1353        // Step 3. Let element be null.
1354        // Step 4. If node implements Element, set element to node.
1355        // Step 5. Otherwise, if node implements Text or Comment, set element to node's parent element.
1356        let element = match node.type_id() {
1357            NodeTypeId::Element(_) => Some(DomRoot::downcast::<Element>(node).unwrap()),
1358            NodeTypeId::CharacterData(CharacterDataTypeId::Comment) |
1359            NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => node.GetParentElement(),
1360            _ => None,
1361        };
1362
1363        // Step 6. If element is null or all of the following are true:
1364        let element = Element::fragment_parsing_context(cx, &owner_doc, element.as_deref());
1365
1366        // Step 7. Let fragment node be the result of invoking the fragment parsing algorithm steps with element and compliantString.
1367        let fragment_node = element.parse_fragment(fragment, cx)?;
1368
1369        // Step 8. For each script of fragment node's script element descendants:
1370        for node in fragment_node
1371            .upcast::<Node>()
1372            .traverse_preorder(ShadowIncluding::No)
1373        {
1374            if let Some(script) = node.downcast::<HTMLScriptElement>() {
1375                // Step 8.1. Set script's already started to false.
1376                script.set_already_started(false);
1377                // Step 8.2. Set script's parser document to null.
1378                script.set_parser_inserted(false);
1379            }
1380        }
1381
1382        // Step 9. Return fragment node.
1383        Ok(fragment_node)
1384    }
1385
1386    /// <https://drafts.csswg.org/cssom-view/#dom-range-getclientrects>
1387    fn GetClientRects(&self, cx: &mut JSContext) -> DomRoot<DOMRectList> {
1388        let start = self.start_container();
1389        let window = start.owner_window();
1390
1391        let client_rects = self.client_rects(cx);
1392        let client_rects = client_rects
1393            .iter()
1394            .map(|rect| {
1395                DOMRect::new(
1396                    cx,
1397                    window.upcast(),
1398                    rect.origin.x.to_f64_px(),
1399                    rect.origin.y.to_f64_px(),
1400                    rect.size.width.to_f64_px(),
1401                    rect.size.height.to_f64_px(),
1402                )
1403            })
1404            .collect();
1405
1406        DOMRectList::new(cx, &window, client_rects)
1407    }
1408
1409    /// <https://drafts.csswg.org/cssom-view/#dom-range-getboundingclientrect>
1410    fn GetBoundingClientRect(&self, cx: &mut JSContext) -> DomRoot<DOMRect> {
1411        let window = self.start_container().owner_window();
1412
1413        // Step 1. Let list be the result of invoking getClientRects() on the same range this method was invoked on.
1414        let list = self.client_rects(cx);
1415
1416        // Step 2. If list is empty return a DOMRect object whose x, y, width and height members are zero.
1417        // Step 3. If all rectangles in list have zero width or height, return the first rectangle in list.
1418        // Step 4. Otherwise, return a DOMRect object describing the smallest rectangle that includes all
1419        // of the rectangles in list of which the height or width is not zero.
1420        let bounding_rect = list
1421            .into_iter()
1422            .fold(euclid::Rect::zero(), |acc, rect| acc.union(&rect));
1423
1424        DOMRect::new(
1425            cx,
1426            window.upcast(),
1427            bounding_rect.origin.x.to_f64_px(),
1428            bounding_rect.origin.y.to_f64_px(),
1429            bounding_rect.size.width.to_f64_px(),
1430            bounding_rect.size.height.to_f64_px(),
1431        )
1432    }
1433}
1434
1435#[derive(MallocSizeOf)]
1436pub(crate) struct WeakRangeVec {
1437    cell: DomRefCell<WeakRefVec<Range>>,
1438}
1439
1440impl Default for WeakRangeVec {
1441    fn default() -> Self {
1442        WeakRangeVec {
1443            cell: DomRefCell::new(WeakRefVec::new()),
1444        }
1445    }
1446}
1447
1448impl WeakRangeVec {
1449    /// Get a rooted version of the contents of this [`WeakRangeVec`]
1450    pub(crate) fn as_vec(&self) -> SmallVec<[DomRoot<Range>; 4]> {
1451        self.cell
1452            .borrow()
1453            .iter()
1454            .filter_map(|range| range.root())
1455            .collect()
1456    }
1457
1458    pub(crate) fn for_each(&self, no_gc: &NoGC, mut callback: impl FnMut(&Range)) {
1459        for weak_range in self.cell.safe_borrow_mut(no_gc).iter() {
1460            if let Some(range) = weak_range.unrooted(no_gc) {
1461                callback(&range);
1462            }
1463        }
1464    }
1465
1466    pub(crate) fn is_empty(&self) -> bool {
1467        self.cell.borrow().is_empty()
1468    }
1469
1470    fn push(&self, weak_range: WeakRef<Range>) {
1471        self.cell.borrow_mut().push(weak_range);
1472    }
1473
1474    fn remove(&self, range_to_remove: &Range) {
1475        self.cell
1476            .borrow_mut()
1477            .retain(|range| range != range_to_remove);
1478    }
1479}
1480
1481#[expect(unsafe_code)]
1482unsafe impl JSTraceable for WeakRangeVec {
1483    unsafe fn trace(&self, _: *mut JSTracer) {
1484        self.cell.borrow_mut().retain_alive()
1485    }
1486}
1487
1488impl Document {
1489    /// <https://dom.spec.whatwg.org/#concept-node-insert> steps 5.1-5.2
1490    /// and
1491    /// <https://dom.spec.whatwg.org/#move> steps 17.1-17.2.
1492    pub(crate) fn live_range_insert_steps(
1493        &self,
1494        no_gc: &NoGC,
1495        parent: &Node,
1496        child: &Node,
1497        count: u32,
1498    ) {
1499        if self.live_ranges().is_empty() {
1500            return;
1501        }
1502
1503        let child_index = LazyCell::new(|| child.index());
1504        self.live_ranges().for_each(no_gc, |range| {
1505            // Step 5.1: For each live range whose start node is parent and start offset is
1506            // greater than child’s index: increase its start offset by count.
1507            if &*range.start_container() == parent && range.start_offset() > *child_index {
1508                range.set_start_without_reporting(parent, range.start_offset() + count);
1509            }
1510            // Step 5.2: For each live range whose end node is parent and end offset is
1511            // greater than child’s index: increase its end offset by count.
1512            if &*range.end_container() == parent && range.end_offset() > *child_index {
1513                range.set_end_without_reporting(parent, range.end_offset() + count);
1514            }
1515        });
1516    }
1517
1518    /// <https://dom.spec.whatwg.org/#live-range-pre-remove-steps> steps 4-7.
1519    pub(crate) fn live_range_pre_remove_steps(
1520        &self,
1521        no_gc: &NoGC,
1522        removed_node: &Node,           // "node" in the specification
1523        parent_of_removed_node: &Node, // "parent" in the specification
1524        index_of_removed_node: &mut dyn FnMut() -> u32, // "index" in the specification
1525    ) {
1526        if self.live_ranges().is_empty() {
1527            return;
1528        }
1529
1530        // Step 1. Let parent be node’s parent.
1531        // Note: This is `parent_of_removed_node`.
1532        // Step 2. Assert: parent is non-null.
1533        // Note: Cannot be null.
1534
1535        // Step 3. Let index be node’s index.
1536        // Note: This is `index_of_removed_node`.
1537
1538        self.live_ranges().for_each(no_gc, |range| {
1539            // Step 4: For each live range whose start node is an inclusive descendant of
1540            // node, set its start to (parent, index).
1541            let start_container = range.start_container();
1542            if removed_node.is_inclusive_ancestor_of(&start_container) {
1543                range.set_start_without_reporting(parent_of_removed_node, index_of_removed_node());
1544            }
1545            // Step 5: For each live range whose end node is an inclusive descendant of node,
1546            // set its end to (parent, index).
1547            if removed_node.is_inclusive_ancestor_of(&range.end_container()) {
1548                range.set_end_without_reporting(parent_of_removed_node, index_of_removed_node());
1549            }
1550
1551            // Step 6: For each live range whose start node is parent and start offset is
1552            // greater than index, decrease its start offset by 1.
1553            if &*range.start_container() == parent_of_removed_node &&
1554                range.start_offset() > index_of_removed_node()
1555            {
1556                range.set_start_without_reporting(parent_of_removed_node, range.start_offset() - 1);
1557            }
1558            // Step 7: For each live range whose end node is parent and end offset is greater than
1559            // index, decrease its end offset by 1.
1560            if &*range.end_container() == parent_of_removed_node &&
1561                range.end_offset() > index_of_removed_node()
1562            {
1563                range.set_end_without_reporting(parent_of_removed_node, range.end_offset() - 1);
1564            }
1565        });
1566    }
1567
1568    /// <https://dom.spec.whatwg.org/#dom-node-normalize> Steps 6.1-6.4.
1569    ///
1570    /// - `parent`: The parent of both other node arguments.
1571    /// - `node`: The node that text is being merged into.
1572    /// - `current_node`: The node which has text being merged into `node` and will be
1573    ///   removed from the DOM.
1574    /// - `current_node_index`: The index of `current_node` in `parent`.
1575    /// - `length`: The length of the text content in `node`, its original length plus
1576    ///   the length of all content that has already been merged from siblings before
1577    ///   `current_node`.
1578    pub(crate) fn live_range_normalization_steps(
1579        &self,
1580        no_gc: &NoGC,
1581        parent: &Node,
1582        node: &Node,
1583        current_node: &Node,
1584        current_node_index: &dyn Fn() -> u32,
1585        length: u32,
1586    ) {
1587        if self.live_ranges().is_empty() {
1588            return;
1589        }
1590
1591        self.live_ranges().for_each(no_gc, |range| {
1592            // Step 6.1: For each live range whose start node is currentNode: add length to
1593            // its start offset and set its start node to node.
1594            if &*range.start_container() == current_node {
1595                range.set_start_without_reporting(node, range.start_offset() + length);
1596            }
1597            // Step 6.2: For each live range whose end node is currentNode: add length to its
1598            // end offset and set its end node to node.
1599            if &*range.end_container() == current_node {
1600                range.set_end_without_reporting(node, range.end_offset() + length);
1601            }
1602
1603            // Step 6.3: For each live range whose start node is currentNode’s parent and
1604            // start offset is currentNode’s index: set its start node to node and its start
1605            // offset to length.
1606            if &*range.start_container() == parent && range.start_offset() == current_node_index() {
1607                range.set_start_without_reporting(node, length);
1608            }
1609            // Step 6.4: For each live range whose end node is currentNode’s parent and end
1610            // offset is currentNode’s index: set its end node to node and its end offset to
1611            // length.
1612            if &*range.end_container() == parent && range.end_offset() == current_node_index() {
1613                range.set_end_without_reporting(node, length);
1614            }
1615        });
1616    }
1617
1618    /// <https://dom.spec.whatwg.org/#concept-cd-replace> steps 8-11.
1619    pub(crate) fn live_range_replace_data_steps(
1620        &self,
1621        no_gc: &NoGC,
1622        node: &Node,
1623        offset: u32,
1624        removed_code_units: u32,
1625        added_code_units: &mut dyn FnMut() -> u32,
1626    ) {
1627        if self.live_ranges().is_empty() {
1628            return;
1629        }
1630
1631        self.live_ranges().for_each(no_gc, |range| {
1632            // Step 8: For each live range whose start node is node and start offset is
1633            // greater than offset but less than or equal to offset + count: set its start
1634            // offset to offset.
1635            let start_container = range.start_container();
1636            let start_offset = range.start_offset();
1637            if &*start_container == node &&
1638                start_offset > offset &&
1639                start_offset <= offset.saturating_add(removed_code_units)
1640            {
1641                range.set_start_without_reporting(node, offset);
1642            }
1643            // Step 9: For each live range whose end node is node and end offset is
1644            // greater than offset but less than or equal to offset + count: set its end
1645            // offset to offset.
1646            let end_container = range.end_container();
1647            let end_offset = range.end_offset();
1648            if &*end_container == node &&
1649                end_offset > offset &&
1650                end_offset <= offset.saturating_add(removed_code_units)
1651            {
1652                range.set_end_without_reporting(node, offset);
1653            }
1654            // Step 10: For each live range whose start node is node and start offset is
1655            // greater than offset + count: increase its start offset by data’s length and
1656            // decrease it by count.
1657            if &*start_container == node && start_offset > offset.saturating_add(removed_code_units)
1658            {
1659                range.set_start_without_reporting(
1660                    node,
1661                    start_offset + added_code_units() - removed_code_units,
1662                );
1663            }
1664            // Step 11: For each live range whose end node is node and end offset is
1665            // greater than offset + count: increase its end offset by data’s length and
1666            // decrease it by count.
1667            if &*end_container == node && end_offset > offset.saturating_add(removed_code_units) {
1668                range.set_end_without_reporting(
1669                    node,
1670                    end_offset + added_code_units() - removed_code_units,
1671                );
1672            }
1673        });
1674    }
1675
1676    /// <https://dom.spec.whatwg.org/#concept-text-split> steps 7.2-7.5.
1677    pub(crate) fn live_range_text_split_steps(
1678        &self,
1679        no_gc: &NoGC,
1680        parent: &Node,
1681        node: &Node,
1682        offset: u32,
1683        new_node: &Node,
1684    ) {
1685        if self.live_ranges().is_empty() {
1686            return;
1687        }
1688
1689        let node_index = LazyCell::new(|| node.index());
1690        self.live_ranges().for_each(no_gc, |range| {
1691            // Step 7.2. For each live range whose start node is node and start offset is
1692            // greater than offset, set its start node to newNode and decrease its start
1693            // offset by offset.
1694            if &*range.start_container() == node && range.start_offset() > offset {
1695                range.set_start_without_reporting(new_node, range.start_offset() - offset);
1696            }
1697            // Step 7.3. For each live range whose end node is node and end offset is
1698            // greater than offset, set its end node to newNode and decrease its end
1699            // offset by offset.
1700            if &*range.end_container() == node && range.end_offset() > offset {
1701                range.set_end_without_reporting(new_node, range.end_offset() - offset);
1702            }
1703
1704            // Step 7.4. For each live range whose start node is parent and start offset
1705            // is equal to the index of node plus 1, increase its start offset by 1.
1706            if &*range.start_container() == parent && range.start_offset() == *node_index + 1 {
1707                range.set_start_without_reporting(parent, range.start_offset() + 1);
1708            }
1709
1710            // Step 7.5. For each live range whose end node is parent and end offset is
1711            // equal to the index of node plus 1, increase its end offset by 1.
1712            if &*range.end_container() == parent && range.end_offset() == *node_index + 1 {
1713                range.set_end_without_reporting(parent, range.end_offset() + 1);
1714            }
1715        });
1716    }
1717}