Skip to main content

script/dom/
selection.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, LazyCell};
6use std::cmp::Ordering;
7
8use bitflags::bitflags;
9use dom_struct::dom_struct;
10use js::context::{JSContext, NoGC};
11use rustc_hash::FxHashSet;
12use script_bindings::cell::DomRefCell;
13use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
14use script_bindings::dom::UnrootedDom;
15use script_bindings::reflector::{Reflector, reflect_dom_object};
16use servo_base::text::{RangeAny, Utf16CodeUnits, Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
17
18use crate::dom::abstractrange::bp_position;
19use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
20use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
21use crate::dom::bindings::codegen::Bindings::SelectionBinding::SelectionMethods;
22use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::refcounted::Trusted;
25use crate::dom::bindings::reflector::DomGlobal;
26use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
27use crate::dom::bindings::str::DOMString;
28use crate::dom::document::Document;
29use crate::dom::eventtarget::EventTarget;
30use crate::dom::iterators::PrePostIteration;
31use crate::dom::node::{Node, NodeTraits};
32use crate::dom::range::Range;
33use crate::dom::selection_range::{SelectionBoundary, SelectionRange};
34use crate::dom::types::ShadowRoot;
35use crate::dom::{CharacterData, FlatTreeParent, NodeDamage, NodeFlags};
36
37#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
38enum Direction {
39    Forwards,
40    Backwards,
41    Directionless,
42}
43
44/// <https://w3c.github.io/selection-api/#dfn-selection>
45#[dom_struct]
46pub(crate) struct Selection {
47    reflector_: Reflector,
48    document: Dom<Document>,
49    /// A range that holds the start and end of this selection, which may potentially
50    /// cross shadow roots.
51    range: DomRefCell<Option<SelectionRange>>,
52    /// The live range version of this selection, which will never cross shadow roots.
53    live_range: MutNullableDom<Range>,
54    /// The [`Direction`] of this [`Selection`] which determines which endpoint of
55    /// [`Self::range`] is the anchor and which is the focus.
56    direction: Cell<Direction>,
57    /// <https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event>
58    has_scheduled_selectionchange_event: Cell<bool>,
59    /// Whether or not this [`Selection`] needs to remark DOM nodes with selection flags
60    /// after a change to its underlying [`Range`].
61    visible_selection_dirty: Cell<bool>,
62}
63
64impl Selection {
65    fn new_inherited(document: &Document) -> Selection {
66        Selection {
67            reflector_: Reflector::new(),
68            document: Dom::from_ref(document),
69            range: Default::default(),
70            live_range: MutNullableDom::new(None),
71            direction: Cell::new(Direction::Directionless),
72            has_scheduled_selectionchange_event: Cell::new(false),
73            visible_selection_dirty: Cell::new(false),
74        }
75    }
76
77    pub(crate) fn new(cx: &mut JSContext, document: &Document) -> DomRoot<Selection> {
78        reflect_dom_object(
79            cx,
80            Box::new(Selection::new_inherited(document)),
81            &*document.global(),
82        )
83    }
84
85    pub(crate) fn visible_selection_dirty(&self) -> bool {
86        self.visible_selection_dirty.get()
87    }
88
89    fn clear_cached_live_range(&self) {
90        if let Some(old_range) = self.live_range.take() {
91            old_range.disassociate_selection(self);
92        }
93    }
94
95    pub(crate) fn update_from_live_range(
96        &self,
97        live_range: &Range,
98        notification: SelectionLiveRangeNotification,
99    ) {
100        let start_changed;
101        let end_changed;
102        {
103            let mut range = self.range.borrow_mut();
104            let range = range
105                .as_mut()
106                .expect("A live range implies a selection range");
107
108            start_changed = notification.contains(SelectionLiveRangeNotification::Start) &&
109                range.start != *live_range.start();
110            if start_changed {
111                range.start =
112                    SelectionBoundary::new(&live_range.start_container(), live_range.start_offset())
113            }
114
115            end_changed = notification.contains(SelectionLiveRangeNotification::End) &&
116                range.end != *live_range.end();
117            if end_changed {
118                range.end =
119                    SelectionBoundary::new(&live_range.end_container(), live_range.end_offset())
120            }
121        }
122
123        if start_changed || end_changed {
124            self.selection_boundaries_changed();
125        }
126    }
127
128    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
129    fn set_range(&self, new_range: Option<SelectionRange>) -> bool {
130        let changed;
131        {
132            let mut range = self.range.borrow_mut();
133            changed = *range != new_range;
134            *range = new_range;
135        }
136
137        // Any changes must unconditionally install a new live range.
138        self.clear_cached_live_range();
139
140        if changed {
141            self.selection_boundaries_changed();
142            self.assert_valid_selection();
143        }
144
145        changed
146    }
147
148    pub(crate) fn set_live_range(&self, new_range: Option<&Range>) {
149        if new_range == self.live_range.get().as_deref() {
150            return;
151        }
152
153        let boundaries_changed = self.set_range(new_range.map(|new_range| new_range.into()));
154
155        // It's possible that `set_range` was a no-op, but still in that case we need to
156        // replace the live range per-specification.
157        if let Some(old_range) = self.live_range.take() {
158            old_range.disassociate_selection(self);
159        }
160        if let Some(new_range) = new_range {
161            self.live_range.set(Some(new_range));
162            new_range.associate_selection(self);
163        }
164
165        // From <https://w3c.github.io/selection-api/#selectionchange-event>:
166        // > When the selection is dissociated with its range, associated with a new
167        // > range, or the associated range's boundary point is mutated either by the user
168        // > or the content script, the user agent must schedule a selectionchange event on
169        // > document.
170        //
171        // This means we should fire the event even if the boundaries themselves did not change. A
172        // change to the range object is enough. Normally, this happens in `set_range`, but
173        // only when the boundaries changed. In this case the call to `set_range` above did
174        // not queue the task.
175        if !boundaries_changed {
176            self.queue_selectionchange_task();
177        }
178    }
179
180    fn selection_boundaries_changed(&self) {
181        self.set_visible_selection_dirty();
182        self.queue_selectionchange_task();
183
184        // See:
185        //  - <https://w3c.github.io/editing/docs/execCommand/#state-override> and
186        //  - <https://w3c.github.io/editing/docs/execCommand/#value-override>
187        //
188        // > Whenever the number of ranges in the selection changes to something
189        // > different, and whenever a boundary point of the range at a given index in the
190        // > selection changes to something different, the state override and value
191        // > override must be unset for every command.
192        self.document.clear_command_overrides();
193    }
194
195    fn iter_nodes_with_overlaps_document_selection_flag<'no_gc>(
196        &self,
197        no_gc: &'no_gc NoGC,
198    ) -> impl Iterator<Item = UnrootedDom<'no_gc, Node>> {
199        let mut traversal = self
200            .document
201            .upcast::<Node>()
202            .following_flat_tree_nodes_unrooted(no_gc);
203        let mut next = traversal.next();
204        std::iter::from_fn(move || {
205            while let Some(node) = next.take() {
206                match node {
207                    PrePostIteration::Enter(node) => {
208                        if node.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION) {
209                            next = traversal.next();
210                            return Some(node);
211                        } else {
212                            // This relies on flags being set consistently: this node
213                            // with the flag unset claims that no part of it overlaps selection,
214                            // which implies that none of its descendant either have any part
215                            // of them overlapping selection, meaning none of them have the flag
216                            next = traversal.next_skipping_subtree();
217                        }
218                    },
219                    PrePostIteration::Leave(_) => next = traversal.next(),
220                }
221            }
222            None
223        })
224    }
225
226    pub(crate) fn update_overlaps_document_selection_flags<'no_gc>(&self, no_gc: &'no_gc NoGC) {
227        if !self.visible_selection_dirty.take() {
228            return;
229        }
230
231        let previously_flagged_nodes = self.iter_nodes_with_overlaps_document_selection_flag(no_gc);
232
233        let needs_new_display_list = Cell::new(false);
234        let set_text_run_selection =
235            |character_data: &CharacterData, range: Option<RangeAny<Utf32CodeUnits>>| {
236                if character_data.set_text_run_selection(range) {
237                    needs_new_display_list.set(true)
238                } else {
239                    character_data
240                        .upcast::<Node>()
241                        .dirty(no_gc, NodeDamage::ContentOrHeritage);
242                }
243            };
244        let remove_selection = |node: &Node| {
245            node.set_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION, false);
246            // Currently only `CharacterData` nodes show visible selection.
247            if let Some(character_data) = node.downcast::<CharacterData>() {
248                set_text_run_selection(character_data, None)
249            }
250        };
251
252        let range = self.range.borrow();
253        let Some(range) = range.as_ref() else {
254            for node in previously_flagged_nodes {
255                remove_selection(&node)
256            }
257            if needs_new_display_list.get() {
258                self.document.window().layout().set_needs_new_display_list();
259            }
260            return;
261        };
262
263        // Hash keys are pointer addresses which are not directly controlled by web content
264        // so we don’t need HashDoS resistance and can use a faster hasher than `std`’s default
265        let mut previously_flagged_nodes: FxHashSet<_> = previously_flagged_nodes.collect();
266
267        let start_offset = range.start.offset as usize;
268        let end_offset = range.end.offset as usize;
269        let start_container = range.start.container.as_rooted();
270        let end_container = range.end.container.as_rooted();
271        let start_position =
272            position_in_flat_tree_for_selection(no_gc, start_container.clone(), start_offset);
273        let end_position =
274            position_in_flat_tree_for_selection(no_gc, end_container.clone(), end_offset);
275
276        let start_node = start_position.node();
277        let end_node = end_position.node();
278
279        // In case the range hasn't changed, but the offsets within the start/end end node have
280        // changed, always update the selection on the start and end nodes, if they paint selection.
281
282        // TODO(mrobinson): We should handle changes only to the offsets within a single
283        // boundary node explicitly and not traversing the whole range.
284        // But that requires keeping track of the previous range, to compare.
285        if let Some(character_data) = start_container.downcast::<CharacterData>() {
286            let text = character_data.data();
287            let range = RangeAny {
288                start: Some(Utf16CodeUnits(start_offset).to_utf32_code_units_in(&text)),
289                end: (start_node == end_node)
290                    .then_some(Utf16CodeUnits(end_offset).to_utf32_code_units_in(&text)),
291            };
292            set_text_run_selection(character_data, Some(range))
293        }
294        if end_container != start_container &&
295            let Some(character_data) = end_container.downcast::<CharacterData>()
296        {
297            let text = character_data.data();
298            let range = RangeAny {
299                start: None,
300                end: Some(Utf16CodeUnits(end_offset).to_utf32_code_units_in(&text)),
301            };
302            set_text_run_selection(character_data, Some(range))
303        }
304
305        let mut set_selection_flag = |node: &UnrootedDom<'no_gc, Node>| {
306            if !node.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION) {
307                node.set_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION, true);
308                debug_assert!(!previously_flagged_nodes.contains(node));
309            } else {
310                previously_flagged_nodes.remove(node);
311            }
312        };
313
314        // We mark the ancestors of the start node as containing a selection. Two notes:
315        // - The traversal itself will take care of marking ancestors of all other nodes,
316        //   as the in-order tree walk will be guaranteed to walk them.
317        // - We do not need to mark these nodes as dirty as they are guaranteed to not be
318        //   leaves (the only nodes that show visible selection).
319        let mut maybe_parent = start_node.parent_in_flat_tree(no_gc);
320        while let FlatTreeParent::Parent(parent) = maybe_parent {
321            set_selection_flag(&parent);
322            maybe_parent = parent.parent_in_flat_tree(no_gc);
323        }
324
325        let mut traversal = start_node.following_flat_tree_nodes_unrooted(no_gc);
326
327        // If the selection starts after the first node, skip that node and all descendants
328        // before setting flags in the selection range.
329        if matches!(start_position, FlatTreeNodePosition::After(_)) {
330            let leaving_start = traversal.next_skipping_subtree();
331            debug_assert!(
332                matches!(leaving_start, Some(PrePostIteration::Leave(node)) if node == *start_node)
333            );
334        }
335
336        for iteration in traversal {
337            match &iteration {
338                PrePostIteration::Enter(node) => {
339                    if node == end_node && matches!(end_position, FlatTreeNodePosition::Before(_)) {
340                        break;
341                    }
342                    if node == start_node {
343                        continue;
344                    }
345                    set_selection_flag(node);
346                },
347                PrePostIteration::Leave(node) => {
348                    set_selection_flag(node);
349                    if node == end_node {
350                        break;
351                    }
352                    if let Some(character_data) = node.downcast::<CharacterData>() {
353                        set_text_run_selection(character_data, Some(RangeAny::full()))
354                    }
355                },
356            }
357        }
358
359        // Nodes that haven’t been removed from the `HashSet` by `add_selection_flag`
360        // should no longer have the flag:
361        for node in &previously_flagged_nodes {
362            remove_selection(node)
363        }
364        if needs_new_display_list.get() {
365            self.document.window().layout().set_needs_new_display_list();
366        }
367    }
368
369    /// <https://w3c.github.io/selection-api/#dfn-schedule-a-selectionchange-event>
370    pub(crate) fn queue_selectionchange_task(&self) {
371        // Step 1. If target's has scheduled selectionchange event is true, abort these steps.
372        if self.has_scheduled_selectionchange_event.get() {
373            return;
374        }
375        // Step 2. Set target's has scheduled selectionchange event to true.
376        self.has_scheduled_selectionchange_event.set(true);
377        // Step 3. Queue a task on the user interaction task source to fire a
378        // selectionchange event on target.
379        let this = Trusted::new(self);
380        self.document
381            .owner_global()
382            .task_manager()
383            .user_interaction_task_source() // w3c/selection-api#117
384            .queue(
385                // https://w3c.github.io/selection-api/#firing-selectionchange-event
386                task!(selectionchange_task_steps: move |cx| {
387                    let this = this.root();
388                    // Step 1. Set target's has scheduled selectionchange event to false.
389                    this.has_scheduled_selectionchange_event.set(false);
390                    // Step 2. If target is an element, fire an event named
391                    // selectionchange, which bubbles and not cancelable, at target.
392                    //
393                    // n/a
394
395                    // Step 3. Otherwise, if target is a document, fire an event named
396                    // selectionchange, which does not bubble and not cancelable, at
397                    // target.
398                    this.document.upcast::<EventTarget>().fire_event(cx, atom!("selectionchange"));
399                }),
400            );
401    }
402
403    fn is_in_document_of_range(&self, node: &Node) -> bool {
404        // TODO(mrobinson): This should eventually allow nodes in the same composed tree (and
405        // not just the same tree), but this requires more work to allow `Selection` to cross
406        // shadow tree boundaries.
407        &*node.GetRootNode(&GetRootNodeOptions { composed: false }) ==
408            self.document.upcast::<Node>()
409    }
410
411    pub(crate) fn start_boundary(&self, cx: &mut JSContext) -> (DomRoot<Node>, u32) {
412        let range = self.expect_active_range(cx);
413        (range.start_container(), range.start_offset())
414    }
415
416    pub(crate) fn end_boundary(&self, cx: &mut JSContext) -> (DomRoot<Node>, u32) {
417        let range = self.expect_active_range(cx);
418        (range.end_container(), range.end_offset())
419    }
420
421    fn assert_valid_selection(&self) {
422        #[cfg(not(debug_assertions))]
423        return;
424
425        let range_borrow = self.range.borrow();
426        let Some(range) = range_borrow.as_ref() else {
427            return;
428        };
429        debug_assert_eq!(
430            range.start.container.GetRootNode(&Default::default()),
431            range.end.container.GetRootNode(&Default::default())
432        );
433        debug_assert!(
434            bp_position(
435                &range.start.container,
436                range.start.offset,
437                &range.end.container,
438                range.end.offset
439            ) != Ordering::Greater
440        );
441    }
442
443    fn assert_valid_selection_and_live_range(&self) {
444        #[cfg(not(debug_assertions))]
445        return;
446
447        self.assert_valid_selection();
448
449        let Some(active_range) = self.live_range.get() else {
450            return;
451        };
452
453        // TODO: For now the live range is equal to the selection range, but one selections
454        // can span shadow root boundaries they will be different.
455        let range = self.range.borrow();
456        let range = range
457            .as_ref()
458            .expect("Should always have a range if we have an live range");
459        debug_assert!(*range.start.container == *active_range.start_container());
460        debug_assert_eq!(range.start.offset, active_range.start_offset());
461        debug_assert!(*range.end.container == *active_range.end_container());
462        debug_assert_eq!(range.end.offset, active_range.end_offset());
463        debug_assert!(
464            bp_position(
465                &active_range.start_container(),
466                active_range.start_offset(),
467                &active_range.end_container(),
468                active_range.end_offset()
469            ) != Ordering::Greater
470        );
471    }
472
473    /// <https://w3c.github.io/editing/docs/execCommand/#active-range>
474    ///
475    /// > The active range is the range of the selection given by calling
476    /// > getSelection() on the context object. (Thus the active range may be null.)
477    pub(crate) fn active_range(&self, cx: &mut JSContext) -> Option<DomRoot<Range>> {
478        self.assert_valid_selection_and_live_range();
479
480        if let Some(active_range) = self.live_range.get() {
481            return Some(active_range);
482        }
483
484        // TODO: This should eventually be the projection of the composed range stored in
485        // `self.range` into the boundaries of a single DOM tree.
486        let active_range = {
487            let range = self.range.borrow();
488            let range = range.as_ref()?;
489            Range::new(
490                cx,
491                &self.document,
492                &range.start.container,
493                range.start.offset,
494                &range.end.container,
495                range.end.offset,
496            )
497        };
498
499        self.live_range.set(Some(&active_range));
500        active_range.associate_selection(self);
501        Some(active_range)
502    }
503
504    pub(crate) fn expect_active_range(&self, cx: &mut JSContext) -> DomRoot<Range> {
505        self.active_range(cx)
506            .expect("Should always have an active range")
507    }
508
509    pub(crate) fn set_visible_selection_dirty(&self) {
510        self.visible_selection_dirty.set(true);
511    }
512
513    fn composed_anchor_position(&self) -> Option<(DomRoot<Node>, u32)> {
514        let range = self.range.borrow();
515        let range = range.as_ref()?;
516        Some(match self.direction.get() {
517            Direction::Forwards => (range.start.container.as_rooted(), range.start.offset),
518            _ => (range.end.container.as_rooted(), range.end.offset),
519        })
520    }
521
522    /// <https://w3c.github.io/selection-api/#dfn-anchor>
523    fn live_anchor_node(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
524        self.active_range(cx)
525            .map(|range| match self.direction.get() {
526                Direction::Forwards => range.start_container(),
527                _ => range.end_container(),
528            })
529    }
530
531    /// <https://w3c.github.io/selection-api/#dfn-anchor>
532    fn live_anchor_offset(&self, cx: &mut JSContext) -> u32 {
533        self.active_range(cx)
534            .map_or(0, |range| match self.direction.get() {
535                Direction::Forwards => range.start_offset(),
536                _ => range.end_offset(),
537            })
538    }
539
540    /// <https://w3c.github.io/selection-api/#dfn-focus>
541    fn live_focus_node(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
542        self.active_range(cx)
543            .map(|range| match self.direction.get() {
544                Direction::Forwards => range.end_container(),
545                _ => range.start_container(),
546            })
547    }
548
549    /// <https://w3c.github.io/selection-api/#dfn-focus>
550    fn live_focus_offset(&self, cx: &mut JSContext) -> u32 {
551        self.active_range(cx)
552            .map_or(0, |range| match self.direction.get() {
553                Direction::Forwards => range.end_offset(),
554                _ => range.start_offset(),
555            })
556    }
557
558    /// <https://dom.spec.whatwg.org/#concept-node-insert> steps 5.1-5.2
559    /// and
560    /// <https://dom.spec.whatwg.org/#move> steps 17.1-17.2
561    /// adapted for selections.
562    pub(crate) fn insert_steps(&self, parent: &Node, child: &Node, count: u32) {
563        let mut range_borrow = self.range.borrow_mut();
564        let Some(range) = &mut *range_borrow else {
565            return;
566        };
567        let child_index = LazyCell::new(|| child.index());
568        // Step 5.1: For each live range whose start node is parent and start offset is
569        // greater than child’s index: increase its start offset by count.
570        if range.start.container == parent && range.start.offset > *child_index {
571            range.start.offset += count;
572            self.selection_boundaries_changed();
573        }
574        // Step 5.2: For each live range whose end node is parent and end offset is
575        // greater than child’s index: increase its end offset by count.
576        if range.end.container == parent && range.end.offset > *child_index {
577            range.end.offset += count;
578            self.selection_boundaries_changed();
579        }
580    }
581
582    /// <https://dom.spec.whatwg.org/#live-range-pre-remove-steps> steps 4 and 5
583    /// adapted for selections.
584    ///
585    /// These steps are run on the inclusive descendants of a removed node, but to avoid
586    /// having to iterate through those nodes twice, they are run when the inclusive
587    /// descendants themselves are unbound from the tree.
588    pub(crate) fn remove_steps_for_removed_subtree(
589        &self,
590        inclusive_descendant_of_removed_node: &Node, // "node" in the specification
591        parent_of_removed_node: &Node,               // "parent" in the specification
592        index_of_removed_node: &mut dyn FnMut() -> u32, // "index" in the specification
593    ) {
594        // The steps are only supposed to run on DOM tree inclusive descendants of the removal
595        // root and elements in shadow trees are not, so they shouldn't run for them.
596        //
597        // TODO: This won't be true once selections can span shadow tree roots.
598        if inclusive_descendant_of_removed_node.is_in_a_shadow_tree() {
599            return;
600        }
601
602        let mut range_borrow = self.range.borrow_mut();
603        let Some(range) = &mut *range_borrow else {
604            return;
605        };
606        // Step 4: For each live range whose start node is an inclusive descendant of
607        // node, set its start to (parent, index).
608        if range.start.container == inclusive_descendant_of_removed_node {
609            range.start = SelectionBoundary::new(parent_of_removed_node, index_of_removed_node());
610            self.selection_boundaries_changed();
611        }
612        // Step 5: For each live range whose end node is an inclusive descendant of node,
613        // set its end to (parent, index).
614        if range.end.container == inclusive_descendant_of_removed_node {
615            range.end = SelectionBoundary::new(parent_of_removed_node, index_of_removed_node());
616            self.selection_boundaries_changed();
617        }
618    }
619
620    /// <https://dom.spec.whatwg.org/#live-range-pre-remove-steps> steps 6 and 7
621    /// adapted for selections.
622    pub(crate) fn remove_steps_for_parent(
623        &self,
624        parent: &Node,
625        node_index: &mut dyn FnMut() -> u32,
626    ) {
627        let mut range_borrow = self.range.borrow_mut();
628        let Some(range) = &mut *range_borrow else {
629            return;
630        };
631
632        // Step 6: For each live range whose start node is parent and start offset is
633        // greater than index, decrease its start offset by 1.
634        if range.start.container == parent && range.start.offset > node_index() {
635            range.start.offset -= 1;
636            self.selection_boundaries_changed();
637        }
638        // Step 7: For each live range whose end node is parent and end offset is greater than
639        // index, decrease its end offset by 1.
640        if range.end.container == parent && range.end.offset > node_index() {
641            range.end.offset -= 1;
642            self.selection_boundaries_changed();
643        }
644    }
645
646    /// <https://dom.spec.whatwg.org/#dom-node-normalize> Steps 6.1-6.4 adapted for selections.
647    ///
648    /// - `parent`: The parent of both other node arguments.
649    /// - `node`: The node that text is being merged into.
650    /// - `current_node`: The node which has text being merged into `node` and will be
651    ///   removed from the DOM.
652    /// - `length`: The length of the text content that was merged into `node` from
653    ///   siblings before `current_node`.
654    pub(crate) fn normalization_steps(
655        &self,
656        parent: &Node,
657        node: &Node,
658        current_node: &Node,
659        current_node_index: &dyn Fn() -> u32,
660        length: u32,
661    ) {
662        let mut range_borrow = self.range.borrow_mut();
663        let Some(range) = &mut *range_borrow else {
664            return;
665        };
666        // Step 6.1: For each live range whose start node is currentNode: add length to its start
667        // offset and set its start node to node.
668        if range.start.container == current_node {
669            range.start.offset += length;
670            range.start.container = Dom::from_ref(node);
671            self.selection_boundaries_changed();
672        }
673        // Step 6.2 For each live range whose end node is currentNode: add length to its end offset
674        // and set its end node to node.
675        if range.end.container == current_node {
676            range.end.offset += length;
677            range.end.container = Dom::from_ref(node);
678            self.selection_boundaries_changed();
679        }
680        // Step 6.3: For each live range whose start node is currentNode’s parent and start
681        // offset is currentNode’s index: set its start node to node and its start offset
682        // to length.
683        if range.start.container == parent && range.start.offset == current_node_index() {
684            range.start.container = Dom::from_ref(node);
685            range.start.offset = length;
686            self.selection_boundaries_changed();
687        }
688        // Step 6.4: For each live range whose end node is currentNode’s parent and end offset is
689        // currentNode’s index: set its end node to node and its end offset to length.
690        if range.end.container == parent && range.end.offset == current_node_index() {
691            range.end.container = Dom::from_ref(node);
692            range.end.offset = length;
693            self.selection_boundaries_changed();
694        }
695    }
696
697    /// <https://dom.spec.whatwg.org/#concept-cd-replace> steps 8-11
698    /// adapted for selections.
699    pub(crate) fn replace_data_steps(
700        &self,
701        node: &Node,
702        offset: u32,
703        removed_code_units: u32,
704        added_code_units: &mut dyn FnMut() -> u32,
705    ) {
706        let mut range_borrow = self.range.borrow_mut();
707        let Some(range) = &mut *range_borrow else {
708            return;
709        };
710        // Step 8: For each live range whose start node is node and start offset is
711        // greater than offset but less than or equal to offset + count: set its start
712        // offset to offset.
713        let start_container = &range.start.container;
714        let start_offset = range.start.offset;
715        if &**start_container == node &&
716            start_offset > offset &&
717            start_offset <= offset + removed_code_units
718        {
719            range.start.offset = offset;
720            self.selection_boundaries_changed();
721        }
722        // Step 9: For each live range whose end node is node and end offset is
723        // greater than offset but less than or equal to offset + count: set its end
724        // offset to offset.
725        let end_container = &range.end.container;
726        let end_offset = range.end.offset;
727        if &**end_container == node &&
728            end_offset > offset &&
729            end_offset <= offset + removed_code_units
730        {
731            range.end.offset = offset;
732            self.selection_boundaries_changed();
733        }
734        // Step 10: For each live range whose start node is node and start offset is
735        // greater than offset + count: increase its start offset by data’s length and
736        // decrease it by count.
737        if &**start_container == node && start_offset > offset + removed_code_units {
738            range.start.offset = start_offset + added_code_units() - removed_code_units;
739            self.selection_boundaries_changed();
740        }
741        // Step 11: For each live range whose end node is node and end offset is
742        // greater than offset + count: increase its end offset by data’s length and
743        // decrease it by count.
744        if &**end_container == node && end_offset > offset + removed_code_units {
745            range.end.offset = end_offset + added_code_units() - removed_code_units;
746            self.selection_boundaries_changed();
747        }
748    }
749
750    /// <https://dom.spec.whatwg.org/#concept-text-split> steps 7.2-7.3
751    /// adapted for selections.
752    pub(crate) fn text_split_steps(
753        &self,
754        node: &Node,
755        offset: u32,
756        parent_node: &Node,
757        new_node: &Node,
758    ) {
759        let mut range_borrow = self.range.borrow_mut();
760        let Some(range) = &mut *range_borrow else {
761            return;
762        };
763        // Step 7.2: For each live range whose start node is node and start offset is
764        // greater than offset, set its start node to newNode and decrease its start
765        // offset by offset.
766        if range.start.container == node && range.start.offset > offset {
767            range.start.container = Dom::from_ref(new_node);
768            range.start.offset -= offset;
769            self.selection_boundaries_changed();
770        }
771        // Step 7.3: For each live range whose end node is node and end offset is greater
772        // than offset, set its end node to newNode and decrease its end offset by offset.
773        if range.end.container == node && range.end.offset > offset {
774            range.end.container = Dom::from_ref(new_node);
775            range.end.offset -= offset;
776            self.selection_boundaries_changed();
777        }
778        // Step 7.4: For each live range whose start node is parent and start offset is
779        // equal to the index of node plus 1, increase its start offset by 1.
780        let node_index = LazyCell::new(|| node.index());
781        if range.start.container == parent_node && range.start.offset == *node_index + 1 {
782            range.start.offset += 1;
783            self.selection_boundaries_changed();
784        }
785        // Step 7.5: For each live range whose end node is parent and end offset is equal
786        // to the index of node plus 1, increase its end offset by 1.
787        if range.end.container == parent_node && range.end.offset == *node_index + 1 {
788            range.end.offset += 1;
789            self.selection_boundaries_changed();
790        }
791    }
792
793    pub(crate) fn collapse_to_dom_position(
794        &self,
795        cx: &mut JSContext,
796        container: &Node,
797        offset: Utf32CodeUnitsOrNodeOffset,
798    ) {
799        let _ = self.Collapse(
800            cx,
801            Some(container),
802            container.to_sibling_or_utf16_offset(offset),
803        );
804    }
805
806    pub(crate) fn collapse_or_extend_to_dom_position(
807        &self,
808        cx: &mut JSContext,
809        container: &Node,
810        offset: Utf32CodeUnitsOrNodeOffset,
811    ) {
812        let offset = container.to_sibling_or_utf16_offset(offset);
813        let is_anchor =
814            self.composed_anchor_position()
815                .is_some_and(|(anchor_node, anchor_offset)| {
816                    &*anchor_node == container && anchor_offset == offset
817                });
818
819        if self.range.borrow().is_none() || is_anchor {
820            let _ = self.Collapse(cx, Some(container), offset);
821        } else {
822            let _ = self.Extend(cx, container, offset);
823        }
824    }
825}
826
827impl SelectionMethods<crate::DomTypeHolder> for Selection {
828    /// <https://w3c.github.io/selection-api/#dom-selection-anchornode>
829    fn GetAnchorNode(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
830        // > The attribute must return the anchor node of this, or null if the anchor is
831        // > null or anchor is not in the document tree.
832        let anchor_node = self.live_anchor_node(cx)?;
833        if !anchor_node.is_in_a_document_tree() {
834            return None;
835        }
836        Some(anchor_node)
837    }
838
839    /// <https://w3c.github.io/selection-api/#dom-selection-anchoroffset>
840    fn AnchorOffset(&self, cx: &mut JSContext) -> u32 {
841        // > The attribute must return the anchor offset of this, or 0 if the anchor is null
842        // > or anchor is not in the document tree.
843        if self
844            .live_anchor_node(cx)
845            .is_none_or(|anchor_node| !anchor_node.is_in_a_document_tree())
846        {
847            return 0;
848        }
849        self.live_anchor_offset(cx)
850    }
851
852    /// <https://w3c.github.io/selection-api/#dom-selection-focusnode>
853    fn GetFocusNode(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
854        // > The attribute must return the focus node of this, or null if the focus is
855        // > null or focus is not in the document tree.
856        let focus_node = self.live_focus_node(cx)?;
857        if !focus_node.is_in_a_document_tree() {
858            return None;
859        }
860        Some(focus_node)
861    }
862
863    /// <https://w3c.github.io/selection-api/#dom-selection-focusoffset>
864    fn FocusOffset(&self, cx: &mut JSContext) -> u32 {
865        // > The attribute must return the focus offset of this, or 0 if the focus is null
866        // > or focus is not in the document tree.
867        if self
868            .live_focus_node(cx)
869            .is_none_or(|focus_node| !focus_node.is_in_a_document_tree())
870        {
871            return 0;
872        }
873        self.live_focus_offset(cx)
874    }
875
876    /// <https://w3c.github.io/selection-api/#dom-selection-iscollapsed>
877    fn IsCollapsed(&self, cx: &mut JSContext) -> bool {
878        // > The attribute must return true if and only if the anchor and focus are the
879        // > same (including if both are null). Otherwise it must return false.
880        self.active_range(cx).is_none_or(|range| range.collapsed())
881    }
882
883    /// <https://w3c.github.io/selection-api/#dom-selection-rangecount>
884    fn RangeCount(&self) -> u32 {
885        // > The attribute must return 0 if this is empty or either focus or anchor is not
886        // > in the document tree, and must return 1 otherwise.
887        let range = self.range.borrow();
888        let Some(range) = range.as_ref() else {
889            return 0;
890        };
891        if !range.start_and_end_are_in_document_tree() {
892            return 0;
893        }
894        1
895    }
896
897    /// <https://w3c.github.io/selection-api/#dom-selection-type>
898    fn Type(&self) -> DOMString {
899        // > The attribute must return "None" if this is empty or either focus or anchor
900        // > is not in the document tree, "Caret" if this's range is collapsed, and "Range"
901        // > otherwise.
902        let range = self.range.borrow();
903        let Some(range) = range.as_ref() else {
904            return DOMString::from_static("None");
905        };
906        if !range.start_and_end_are_in_document_tree() {
907            return DOMString::from_static("None");
908        }
909
910        if range.collapsed() {
911            DOMString::from_static("Caret")
912        } else {
913            DOMString::from_static("Range")
914        }
915    }
916
917    /// <https://w3c.github.io/selection-api/#dom-selection-getrangeat>
918    fn GetRangeAt(&self, cx: &mut JSContext, index: u32) -> Fallible<DomRoot<Range>> {
919        // > The method must throw an IndexSizeError exception if index is not 0, or if this
920        // > is empty or either focus or anchor is not in the document tree. Otherwise, it
921        // > must return a reference to (not a copy of) this's range.
922        if index != 0 {
923            return Err(Error::IndexSize(Some("Index must be zero".into())));
924        }
925
926        let range = self.range.borrow();
927        let Some(range) = range.as_ref() else {
928            return Err(Error::IndexSize(Some("Selection is empty".into())));
929        };
930        if !range.start_and_end_are_in_document_tree() {
931            return Err(Error::IndexSize(Some(
932                "Start and end are not in document tree".into(),
933            )));
934        }
935        self.active_range(cx).ok_or(Error::IndexSize(Some(
936            "Could not create live range for selection".into(),
937        )))
938    }
939
940    /// <https://w3c.github.io/selection-api/#dom-selection-addrange>
941    fn AddRange(&self, range: &Range) {
942        // Step 1. If the root of the range's boundary points are not the document
943        // associated with this, abort these steps.
944        if !self.is_in_document_of_range(&range.start_container()) {
945            return;
946        }
947
948        // Step 2. If rangeCount is not 0, abort these steps.
949        if self.RangeCount() != 0 {
950            return;
951        }
952
953        // Step 3. Set this's range to range by a strong reference (not by making a copy).
954        self.set_live_range(Some(range));
955
956        // Are we supposed to set Direction here? w3c/selection-api#116
957        self.direction.set(Direction::Forwards);
958    }
959
960    /// <https://w3c.github.io/selection-api/#dom-selection-removerange>
961    fn RemoveRange(&self, range: &Range) -> ErrorResult {
962        // > The method must make this empty by disassociating its range if this's range
963        // > is range. Otherwise, it must throw a NotFoundError.
964        if let Some(own_range) = self.live_range.get() &&
965            &*own_range == range
966        {
967            self.set_range(None);
968            return Ok(());
969        }
970        Err(Error::NotFound(None))
971    }
972
973    /// <https://w3c.github.io/selection-api/#dom-selection-removeallranges>
974    fn RemoveAllRanges(&self) {
975        // > The method must make this empty by disassociating its range if this has an
976        // > associated range.
977        self.set_range(None);
978    }
979
980    /// <https://w3c.github.io/selection-api/#dom-selection-empty>
981    fn Empty(&self) {
982        // > The method must be an alias, and behave identically, to removeAllRanges().
983        self.RemoveAllRanges();
984    }
985
986    /// <https://w3c.github.io/selection-api/#dom-selection-collapse>
987    fn Collapse(&self, _cx: &mut JSContext, node: Option<&Node>, offset: u32) -> ErrorResult {
988        // Step 1. If node is null, this method must behave identically as
989        // removeAllRanges() and abort these steps.
990        let Some(node) = node else {
991            self.set_range(None);
992            return Ok(());
993        };
994
995        // Step 2. If node is a DocumentType, throw an InvalidNodeTypeError exception and
996        // abort these steps.
997        if node.is_doctype() {
998            return Err(Error::InvalidNodeType(None));
999        }
1000
1001        // Step 3. The method must throw an IndexSizeError exception if offset is longer
1002        // than node's length and abort these steps.
1003        if offset > node.len() {
1004            return Err(Error::IndexSize(None));
1005        }
1006
1007        // Step 4. If document associated with this is not a shadow-including inclusive
1008        // ancestor of node, abort these steps.
1009        //
1010        // TODO(mrobinson): This should eventually allow nodes in the same composed tree (and
1011        // not just the same tree), but this requires more work to allow `Selection` to cross
1012        // shadow tree boundaries.
1013        if &*node.GetRootNode(&GetRootNodeOptions { composed: false }) !=
1014            self.document.upcast::<Node>()
1015        {
1016            return Ok(());
1017        }
1018
1019        // Step 5. Otherwise, let newRange be a new range.
1020        // Step 6. Set the start the start and the end of newRange to (node, offset).
1021        // Step 7. Set this's range to newRange.
1022        self.set_range(Some(SelectionRange::collapsed_at(SelectionBoundary::new(
1023            node, offset,
1024        ))));
1025
1026        // Are we supposed to set Direction here? w3c/selection-api#116
1027        self.direction.set(Direction::Forwards);
1028
1029        Ok(())
1030    }
1031
1032    /// <https://w3c.github.io/selection-api/#dom-selection-setposition>
1033    fn SetPosition(&self, cx: &mut JSContext, node: Option<&Node>, offset: u32) -> ErrorResult {
1034        // > The method must be an alias, and behave identically, to collapse().
1035        self.Collapse(cx, node, offset)
1036    }
1037
1038    /// <https://w3c.github.io/selection-api/#dom-selection-collapsetostart>
1039    fn CollapseToStart(&self, cx: &mut JSContext) -> ErrorResult {
1040        // > The method must throw InvalidStateError exception if the this is empty.
1041        // > Otherwise, it must create a new range, set the start both its start and end to
1042        // > the start of this's range, and then set this's range to the newly-created
1043        // > range.
1044        let Some((start_container, start_offset)) = self
1045            .range
1046            .borrow()
1047            .as_ref()
1048            .map(|range| (range.start.container.as_rooted(), range.start.offset))
1049        else {
1050            return Err(Error::InvalidState(None));
1051        };
1052        self.Collapse(cx, Some(&*start_container), start_offset)
1053    }
1054
1055    /// <https://w3c.github.io/selection-api/#dom-selection-collapsetoend>
1056    fn CollapseToEnd(&self, cx: &mut JSContext) -> ErrorResult {
1057        // > The method must throw InvalidStateError exception if the this is empty.
1058        // > Otherwise, it must create a new range, set the start both its start and end to
1059        // > the end of this's range, and then set this's range to the newly-created range.
1060        let Some((end_container, end_offset)) = self
1061            .range
1062            .borrow()
1063            .as_ref()
1064            .map(|range| (range.end.container.as_rooted(), range.end.offset))
1065        else {
1066            return Err(Error::InvalidState(None));
1067        };
1068        self.Collapse(cx, Some(&*end_container), end_offset)
1069    }
1070
1071    /// <https://w3c.github.io/selection-api/#dom-selection-extend>
1072    fn Extend(&self, _cx: &mut JSContext, node: &Node, offset: u32) -> ErrorResult {
1073        // Step 1. If the document associated with this is not a shadow-including
1074        // inclusive ancestor of node, abort these steps.
1075        //
1076        // TODO(mrobinson): This should eventually allow nodes in the same composed tree (and
1077        // not just the same tree), but this requires more work to allow `Selection` to cross
1078        // shadow tree boundaries.
1079        if &*node.GetRootNode(&GetRootNodeOptions { composed: false }) !=
1080            self.document.upcast::<Node>()
1081        {
1082            return Ok(());
1083        }
1084
1085        // Step 2. If this is empty, throw an InvalidStateError exception and abort these steps.
1086        let range_borrow = self.range.borrow();
1087        let Some(range) = range_borrow.as_ref() else {
1088            return Err(Error::InvalidState(None));
1089        };
1090
1091        // This isn't specified, but it appears to be implementation behavior of other
1092        // browsers. See w3c/selection-api#118.
1093        if node.is_doctype() {
1094            return Err(Error::InvalidNodeType(None));
1095        }
1096
1097        // As with is_doctype, this is not explicit in the selection specification steps
1098        // here but implied by which exceptions are thrown in WPT tests.
1099        if offset > node.len() {
1100            return Err(Error::IndexSize(None));
1101        }
1102
1103        // Step 3. Let oldAnchor and oldFocus be the this's anchor and focus, and let
1104        // newFocus be the boundary point (node, offset).
1105        //
1106        // Note: oldFocus is unused, so we do not set it here.
1107        let (old_anchor_node, old_anchor_offset) = self
1108            .composed_anchor_position()
1109            .expect("has range, therefore has anchor node");
1110
1111        // Step 4. Let newRange be a new range.
1112        // Note: Set directly to satisfy crown.
1113        let direction;
1114
1115        // Step 5. If node's root is not the same as the this's range's root, set the
1116        // start newRange's start and end to newFocus.
1117        let is_in_document_of_range = self.is_in_document_of_range(&range.start.container);
1118        drop(range_borrow);
1119
1120        if !is_in_document_of_range {
1121            self.set_range(Some(SelectionRange::collapsed_at(SelectionBoundary::new(
1122                node, offset,
1123            ))));
1124            direction = Direction::Forwards;
1125        } else {
1126            let is_old_anchor_before_or_equal = matches!(
1127                bp_position(&old_anchor_node, old_anchor_offset, node, offset),
1128                Ordering::Less | Ordering::Equal
1129            );
1130            if is_old_anchor_before_or_equal {
1131                // Step 6. Otherwise, if oldAnchor is before or equal to newFocus, set the start
1132                // newRange's start to oldAnchor, then set its end to newFocus.
1133                self.set_range(Some(SelectionRange::new(
1134                    SelectionBoundary::new(&old_anchor_node, old_anchor_offset),
1135                    SelectionBoundary::new(node, offset),
1136                )));
1137                direction = Direction::Forwards;
1138            } else {
1139                // Step 7. Otherwise, set the start newRange's start to newFocus, then set
1140                // its end to oldAnchor.
1141                self.set_range(Some(SelectionRange::new(
1142                    SelectionBoundary::new(node, offset),
1143                    SelectionBoundary::new(&old_anchor_node, old_anchor_offset),
1144                )));
1145                direction = Direction::Backwards;
1146            }
1147        }
1148
1149        // Step 8. Set this's range to newRange.
1150        // Note: Done above to satisfy crown.
1151
1152        // Step 9. If newFocus is before oldAnchor, set this's direction to backwards.
1153        // Otherwise, set it to forwards.
1154        self.direction.set(direction);
1155
1156        Ok(())
1157    }
1158
1159    /// <https://w3c.github.io/selection-api/#dom-selection-setbaseandextent>
1160    fn SetBaseAndExtent(
1161        &self,
1162        _cx: &mut JSContext,
1163        anchor_node: &Node,
1164        anchor_offset: u32,
1165        focus_node: &Node,
1166        focus_offset: u32,
1167    ) -> ErrorResult {
1168        // This isn't specified, but it appears to be implementation behavior of other
1169        // browsers. See w3c/selection-api#118.
1170        if anchor_node.is_doctype() || focus_node.is_doctype() {
1171            return Err(Error::InvalidNodeType(None));
1172        }
1173
1174        // Step 1. If anchorOffset is longer than anchorNode's length or if focusOffset is
1175        // longer than focusNode's length, throw an IndexSizeError exception and abort
1176        // these steps.
1177        if anchor_offset > anchor_node.len() || focus_offset > focus_node.len() {
1178            return Err(Error::IndexSize(None));
1179        }
1180
1181        // Step 2. If document associated with this is not a shadow-including inclusive
1182        // ancestor of anchorNode or focusNode, abort these steps.
1183        //
1184        // TODO(mrobinson): This should eventually allow nodes in the same composed tree (and
1185        // not just the same tree), but this requires more work to allow `Selection` to cross
1186        // shadow tree boundaries.
1187        if &*anchor_node.GetRootNode(&GetRootNodeOptions { composed: false }) !=
1188            self.document.upcast::<Node>()
1189        {
1190            return Ok(());
1191        }
1192        if &*focus_node.GetRootNode(&GetRootNodeOptions { composed: false }) !=
1193            self.document.upcast::<Node>()
1194        {
1195            return Ok(());
1196        }
1197
1198        // Step 3. Let anchor be the boundary point (anchorNode, anchorOffset) and let
1199        // focus be the boundary point (focusNode, focusOffset).
1200        //
1201        // Note: We do not model the boundary point in this way.
1202
1203        // Step 4. Let newRange be a new range.
1204        // Note: We set the range directly to satisfy crown.
1205
1206        // Step 5. If anchor is before focus, set the start the newRange's start to anchor
1207        // and its end to focus. Otherwise, set the start them to focus and anchor
1208        // respectively.
1209        let is_anchor_before_focus =
1210            bp_position(anchor_node, anchor_offset, focus_node, focus_offset) == Ordering::Less;
1211        let direction = if is_anchor_before_focus {
1212            self.set_range(Some(SelectionRange::new(
1213                SelectionBoundary::new(anchor_node, anchor_offset),
1214                SelectionBoundary::new(focus_node, focus_offset),
1215            )));
1216            Direction::Forwards
1217        } else {
1218            self.set_range(Some(SelectionRange::new(
1219                SelectionBoundary::new(focus_node, focus_offset),
1220                SelectionBoundary::new(anchor_node, anchor_offset),
1221            )));
1222            Direction::Backwards
1223        };
1224
1225        // Step 6. Set this's range to newRange.
1226        // Note: Done above to satisfy crown.
1227
1228        // Step 7. If focus is before anchor, set this's direction to backwards.
1229        // Otherwise, set it to forwards
1230        self.direction.set(direction);
1231
1232        Ok(())
1233    }
1234
1235    /// <https://w3c.github.io/selection-api/#dom-selection-selectallchildren>
1236    fn SelectAllChildren(&self, _cx: &mut JSContext, node: &Node) -> ErrorResult {
1237        // Step 1. If node is a DocumentType, throw an InvalidNodeTypeError exception and
1238        // abort these steps.
1239        if node.is_doctype() {
1240            return Err(Error::InvalidNodeType(None));
1241        }
1242
1243        // Step 2. If node's root is not the document associated with this, abort these
1244        // steps.
1245        if !self.is_in_document_of_range(node) {
1246            return Ok(());
1247        }
1248
1249        // Let newRange be a new range and childCount be the number of children of node.
1250        let child_count = node.children_count();
1251
1252        // Step 4. Set newRange's start to (node, 0).
1253        // Step 5. Set newRange's end to (node, childCount).
1254        // Step 6. Set this's range to newRange.
1255        self.set_range(Some(SelectionRange::new(
1256            SelectionBoundary::new(node, 0),
1257            SelectionBoundary::new(node, child_count),
1258        )));
1259
1260        // Step 7. Set this's direction to forwards.
1261        self.direction.set(Direction::Forwards);
1262
1263        Ok(())
1264    }
1265
1266    /// <https://w3c.github.io/selection-api/#dom-selection-deletecontents>
1267    fn DeleteFromDocument(&self, cx: &mut JSContext) -> ErrorResult {
1268        // > The method must invoke deleteContents() on this's range if this is not empty
1269        // > and both focus and anchor are in the document tree. Otherwise the method must
1270        // > do nothing.
1271        if self
1272            .range
1273            .borrow()
1274            .as_ref()
1275            .is_none_or(|range| !range.start_and_end_are_in_document_tree())
1276        {
1277            return Ok(());
1278        }
1279
1280        self.active_range(cx)
1281            .map_or(Ok(()), |active_range| active_range.DeleteContents(cx))
1282    }
1283
1284    /// <https://w3c.github.io/selection-api/#dom-selection-containsnode>
1285    fn ContainsNode(&self, node: &Node, allow_partial_containment: bool) -> bool {
1286        // > The method must return false if this is empty or if node's root is not the document
1287        // > associated with this.
1288        // >
1289        // > Otherwise, if allowPartialContainment is false, the method must return true if and only
1290        // > if start of its range is before or visually equivalent to the first boundary point in
1291        // > the node *and* end of its range is after or visually equivalent to the last boundary
1292        // > point in the node.
1293        // >
1294        // > If allowPartialContainment is true, the method must return true if and only if start of
1295        // > its range is before or visually equivalent to the last boundary point in the node *and*
1296        // > end of its range is after or visually equivalent to the first boundary point in the
1297        // > node.
1298        if !self.is_in_document_of_range(node) {
1299            return false;
1300        }
1301        let range = self.range.borrow();
1302        let Some(range) = range.as_ref() else {
1303            return false;
1304        };
1305        let start_node = &*range.start.container;
1306        if !self.is_in_document_of_range(start_node) {
1307            return false;
1308        }
1309        let end_node = &*range.end.container;
1310
1311        let first_offset = 0;
1312        let last_offset = node.len();
1313        let (compare_start_to, compare_end_to) = if allow_partial_containment {
1314            (last_offset, first_offset)
1315        } else {
1316            (first_offset, last_offset)
1317        };
1318
1319        // TODO: find out what "visually equivalent" means for boundary points and implement it.
1320        // https://github.com/w3c/selection-api/issues/6
1321        // For now it is simplified to "position is equal".
1322        matches!(
1323            bp_position(start_node, range.start.offset, node, compare_start_to),
1324            Ordering::Less | Ordering::Equal
1325        ) && matches!(
1326            bp_position(end_node, range.end.offset, node, compare_end_to),
1327            Ordering::Greater | Ordering::Equal
1328        )
1329    }
1330
1331    /// <https://w3c.github.io/selection-api/#dom-selection-stringifier>
1332    fn Stringifier(&self, cx: &mut JSContext) -> DOMString {
1333        // > The stringification must return the string, which is the concatenation of the
1334        // > rendered text if there is a range associated with this.
1335        // >
1336        // > If the selection is within a textarea or input element, it must return the
1337        // > selected substring in its value.
1338        //
1339        // TODO: This implementation should be examined in depth. Does rendered text take
1340        // into account `display: none`. The case for textarea and input elements is
1341        // completely unhandled here.
1342        self.GetRangeAt(cx, 0)
1343            .map(|range| range.Stringifier(cx.no_gc()))
1344            .unwrap_or_default()
1345    }
1346}
1347
1348impl<'dom> LayoutDom<'dom, Selection> {
1349    #[expect(unsafe_code)]
1350    pub(crate) fn range_for_layout(&self) -> &Option<SelectionRange> {
1351        unsafe { self.unsafe_get().range.borrow_for_layout() }
1352    }
1353}
1354
1355enum FlatTreeNodePosition {
1356    Before(DomRoot<Node>),
1357    Inside(DomRoot<Node>),
1358    After(DomRoot<Node>),
1359}
1360
1361impl FlatTreeNodePosition {
1362    fn node(&self) -> &Node {
1363        match self {
1364            FlatTreeNodePosition::Before(node) => node,
1365            FlatTreeNodePosition::Inside(node) => node,
1366            FlatTreeNodePosition::After(node) => node,
1367        }
1368    }
1369}
1370
1371/// Find the position of a node and offset in the flat tree for the purposes of selection
1372/// boundaries. This projects the given position onto the flat tree, accounting for origin
1373/// nodes that may not actually be in the flat tree at all.
1374fn position_in_flat_tree_for_selection(
1375    no_gc: &NoGC,
1376    container: DomRoot<Node>,
1377    offset: usize,
1378) -> FlatTreeNodePosition {
1379    if container.is::<CharacterData>() {
1380        return FlatTreeNodePosition::Inside(container);
1381    }
1382
1383    let shadow_host_or_node = |node: &Node| {
1384        container
1385            .downcast::<ShadowRoot>()
1386            .map(|shadow_root| DomRoot::upcast(shadow_root.Host()))
1387            .unwrap_or(DomRoot::from_ref(node))
1388    };
1389
1390    if let Some(child) = container.children().nth(offset) {
1391        if let FlatTreeParent::Parent(_) = child.parent_in_flat_tree(no_gc) {
1392            return FlatTreeNodePosition::Before(child);
1393        }
1394    } else if let Some(last_child) = container.GetLastChild() &&
1395        let FlatTreeParent::Parent(_) = last_child.parent_in_flat_tree(no_gc)
1396    {
1397        return FlatTreeNodePosition::After(shadow_host_or_node(&container));
1398    }
1399
1400    // The container has no child in the flat tree or the child indicated by the index
1401    // isn't in the flat tree, so just return a position inside that container.
1402    FlatTreeNodePosition::Inside(shadow_host_or_node(&container))
1403}
1404
1405impl Node {
1406    /// Get the `Utf16CodeUnits` offset for the given offset if `self` is a
1407    /// `CharacterData` or else return the offset in the child list.
1408    fn to_sibling_or_utf16_offset(&self, offset: Utf32CodeUnitsOrNodeOffset) -> u32 {
1409        if let Some(character_data) = self.downcast::<CharacterData>() {
1410            offset.to_utf16_code_units_in(&character_data.data()).0 as u32
1411        } else {
1412            offset.0 as u32
1413        }
1414    }
1415}
1416
1417bitflags! {
1418    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1419    pub(crate) struct SelectionLiveRangeNotification: u8 {
1420        const Start = 1 << 0;
1421        const End = 1 << 1;
1422    }
1423}