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