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 layout_api::QueryMsg;
12use rustc_hash::{FxHashMap, FxHashSet};
13use script_bindings::cell::DomRefCell;
14use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
15use script_bindings::dom::UnrootedDom;
16use script_bindings::reflector::{Reflector, reflect_dom_object};
17use servo_base::text::{
18    AssumeUnder4GB, RangeAny, Utf16CodeUnits, Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset,
19};
20
21use crate::dom::abstractrange::bp_position;
22use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
23use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
24use crate::dom::bindings::codegen::Bindings::SelectionBinding::{
25    GetComposedRangesOptions, SelectionMethods,
26};
27use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::refcounted::Trusted;
30use crate::dom::bindings::reflector::DomGlobal;
31use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
32use crate::dom::bindings::str::DOMString;
33use crate::dom::comparator::compare_dom_positions;
34use crate::dom::document::Document;
35use crate::dom::eventtarget::EventTarget;
36use crate::dom::iterators::{PrePostIteration, UnrootedFollowingFlatTreeNodesTraversal};
37use crate::dom::node::{Node, NodeTraits};
38use crate::dom::range::Range;
39use crate::dom::selection_range::{SelectionBoundary, SelectionRange};
40use crate::dom::staticrange::StaticRange;
41use crate::dom::traversal::FlatTreeForSelectionNoGcTraversal;
42use crate::dom::types::ShadowRoot;
43use crate::dom::{CharacterData, FlatTreeParent, NodeDamage, NodeFlags, StartOrEnd};
44
45/// Used value of [`user-select`](https://drafts.csswg.org/css-ui-4/#propdef-user-select):
46/// like the computed value but excludes `auto`.
47#[derive(Copy, Clone, PartialEq)]
48pub(crate) enum UsedUserSelect {
49    Text,
50    None,
51    Contain,
52    All,
53}
54
55#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
56pub(crate) enum Direction {
57    Forwards,
58    Backwards,
59    Directionless,
60}
61
62/// <https://w3c.github.io/selection-api/#dfn-selection>
63#[dom_struct]
64pub(crate) struct Selection {
65    reflector_: Reflector,
66    document: Dom<Document>,
67    /// A range that holds the start and end of this selection, which may potentially
68    /// cross shadow roots.
69    range: DomRefCell<Option<SelectionRange>>,
70    /// The live range version of this selection, which will never cross shadow roots.
71    live_range: MutNullableDom<Range>,
72    /// This is like [`Range`], but in the flat tree, which means that:
73    ///
74    /// * It is `None` if the selection is unrenderable.
75    /// * Boundaries are in flat tree order.
76    visible_range: DomRefCell<Option<SelectionRange>>,
77    /// The [`Direction`] of this [`Selection`] which determines which endpoint of
78    /// [`Self::range`] is the anchor and which is the focus.
79    direction: Cell<Direction>,
80    /// <https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event>
81    has_scheduled_selectionchange_event: Cell<bool>,
82    /// Whether or not this [`Selection`] needs to remark DOM nodes with selection flags
83    /// after a change to its underlying [`Range`].
84    visible_selection_dirty: Cell<bool>,
85}
86
87impl Selection {
88    fn new_inherited(document: &Document) -> Selection {
89        Selection {
90            reflector_: Reflector::new(),
91            document: Dom::from_ref(document),
92            range: Default::default(),
93            live_range: MutNullableDom::new(None),
94            visible_range: Default::default(),
95            direction: Cell::new(Direction::Directionless),
96            has_scheduled_selectionchange_event: Cell::new(false),
97            visible_selection_dirty: Cell::new(false),
98        }
99    }
100
101    pub(crate) fn new(cx: &mut JSContext, document: &Document) -> DomRoot<Selection> {
102        reflect_dom_object(
103            cx,
104            Box::new(Selection::new_inherited(document)),
105            &*document.global(),
106        )
107    }
108
109    pub(crate) fn visible_selection_dirty(&self) -> bool {
110        self.visible_selection_dirty.get()
111    }
112
113    pub(crate) fn collapsed(&self) -> bool {
114        self.range
115            .borrow()
116            .as_ref()
117            .is_none_or(|range| range.collapsed())
118    }
119
120    fn clear_cached_live_range(&self) {
121        if let Some(old_range) = self.live_range.take() {
122            old_range.disassociate_selection(self);
123        }
124    }
125
126    /// Clear this entire [`Selection`] if the [`Document`] of its live range has changed
127    /// or its boundaries are no longer connected. Returns true if the selection was
128    /// cleared or false otherwise.
129    pub(crate) fn clear_selection_if_live_range_document_changed(
130        &self,
131        no_gc: &NoGC,
132        range: &Range,
133    ) -> bool {
134        // The live range might be in an intermediate state where its boundaries
135        // are in different documents, so this must check both ends.
136        let range_start_container = range.start_container();
137        let range_end_container = range.end_container();
138        if self.is_in_composed_tree_of_document_and_is_not_ua_widget(&range_start_container) &&
139            self.is_in_composed_tree_of_document_and_is_not_ua_widget(&range_end_container)
140        {
141            return false;
142        }
143
144        self.set_range(no_gc, None);
145        true
146    }
147
148    /// Update the start of end boundary of this [`Selection`] based on a change to the
149    /// given associated [`Range`]. While the behavior here isn't totally specified yet,
150    /// it follows other browsers. If the change would change the direction of the
151    /// [`Selection`] it is collapsed at the newly-set boundary.
152    pub(crate) fn set_start_or_end_from_live_range(
153        &self,
154        no_gc: &NoGC,
155        start_or_end: StartOrEnd,
156        live_range: &Range,
157    ) -> bool {
158        let mut range = self.range.borrow_mut();
159        let range = range
160            .as_mut()
161            .expect("A live range implies a selection range");
162
163        let (container, offset) = match start_or_end {
164            StartOrEnd::Start if range.start != *live_range.start() => {
165                (live_range.start_container(), live_range.start_offset())
166            },
167            StartOrEnd::End if range.end != *live_range.end() => {
168                (live_range.end_container(), live_range.end_offset())
169            },
170            _ => return false,
171        };
172
173        let would_invert = match start_or_end {
174            StartOrEnd::Start => {
175                compare_shadow_including_dom_positions(
176                    no_gc,
177                    &container,
178                    offset,
179                    &range.end.container,
180                    range.end.offset,
181                ) == Ordering::Greater
182            },
183            StartOrEnd::End => {
184                compare_shadow_including_dom_positions(
185                    no_gc,
186                    &range.start.container,
187                    range.start.offset,
188                    &container,
189                    offset,
190                ) == Ordering::Greater
191            },
192        };
193
194        if would_invert {
195            *range = SelectionRange::collapsed_at(SelectionBoundary::new(&container, offset));
196        } else {
197            match start_or_end {
198                StartOrEnd::Start => range.start = SelectionBoundary::new(&container, offset),
199                StartOrEnd::End => range.end = SelectionBoundary::new(&container, offset),
200            }
201        }
202        true
203    }
204
205    pub(crate) fn update_from_live_range(
206        &self,
207        no_gc: &NoGC,
208        live_range: &Range,
209        notification: SelectionLiveRangeNotification,
210    ) {
211        debug_assert!(Some(live_range) == self.live_range.get().as_deref());
212        let start_changed = notification.contains(SelectionLiveRangeNotification::Start) &&
213            self.set_start_or_end_from_live_range(no_gc, StartOrEnd::Start, live_range);
214        let end_changed = notification.contains(SelectionLiveRangeNotification::End) &&
215            self.set_start_or_end_from_live_range(no_gc, StartOrEnd::End, live_range);
216        if start_changed || end_changed {
217            self.selection_boundaries_changed();
218        }
219    }
220
221    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
222    fn set_range(&self, _no_gc: &NoGC, new_range: Option<SelectionRange>) -> bool {
223        let changed;
224        {
225            let mut range = self.range.borrow_mut();
226            changed = *range != new_range;
227            *range = new_range;
228
229            if range.is_none() {
230                self.direction.set(Direction::Directionless);
231            }
232        }
233
234        // Any changes must unconditionally install a new live range.
235        self.clear_cached_live_range();
236
237        if changed {
238            self.selection_boundaries_changed();
239            #[cfg(debug_assertions)]
240            self.assert_valid_selection(_no_gc);
241        }
242
243        changed
244    }
245
246    pub(crate) fn set_live_range(&self, no_gc: &NoGC, new_range: Option<&Range>) {
247        if new_range == self.live_range.get().as_deref() {
248            return;
249        }
250
251        let boundaries_changed = self.set_range(no_gc, new_range.map(|new_range| new_range.into()));
252
253        // It's possible that `set_range` was a no-op, but still in that case we need to
254        // replace the live range per-specification.
255        if let Some(old_range) = self.live_range.take() {
256            old_range.disassociate_selection(self);
257        }
258        if let Some(new_range) = new_range {
259            self.live_range.set(Some(new_range));
260            new_range.associate_selection(self);
261        }
262
263        // From <https://w3c.github.io/selection-api/#selectionchange-event>:
264        // > When the selection is dissociated with its range, associated with a new
265        // > range, or the associated range's boundary point is mutated either by the user
266        // > or the content script, the user agent must schedule a selectionchange event on
267        // > document.
268        //
269        // This means we should fire the event even if the boundaries themselves did not change. A
270        // change to the range object is enough. Normally, this happens in `set_range`, but
271        // only when the boundaries changed. In this case the call to `set_range` above did
272        // not queue the task.
273        if !boundaries_changed {
274            self.queue_selectionchange_task();
275        }
276    }
277
278    fn selection_boundaries_changed(&self) {
279        self.set_visible_selection_dirty();
280        self.queue_selectionchange_task();
281
282        // See:
283        //  - <https://w3c.github.io/editing/docs/execCommand/#state-override> and
284        //  - <https://w3c.github.io/editing/docs/execCommand/#value-override>
285        //
286        // > Whenever the number of ranges in the selection changes to something
287        // > different, and whenever a boundary point of the range at a given index in the
288        // > selection changes to something different, the state override and value
289        // > override must be unset for every command.
290        self.document.clear_command_overrides();
291    }
292
293    fn iter_nodes_with_overlaps_document_selection_flag<'no_gc>(
294        &self,
295        no_gc: &'no_gc NoGC,
296    ) -> impl Iterator<Item = UnrootedDom<'no_gc, Node>> {
297        let mut traversal = self
298            .document
299            .upcast::<Node>()
300            .following_flat_tree_nodes_unrooted(no_gc);
301        let mut next = traversal.next();
302        std::iter::from_fn(move || {
303            while let Some(node) = next.take() {
304                match node {
305                    PrePostIteration::Enter(node) => {
306                        if node.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION) {
307                            next = traversal.next();
308                            return Some(node);
309                        } else {
310                            // This relies on flags being set consistently: this node
311                            // with the flag unset claims that no part of it overlaps selection,
312                            // which implies that none of its descendant either have any part
313                            // of them overlapping selection, meaning none of them have the flag
314                            next = traversal.next_skipping_subtree();
315                        }
316                    },
317                    PrePostIteration::Leave(_) => next = traversal.next(),
318                }
319            }
320            None
321        })
322    }
323
324    fn set_visible_range(&self, flat_tree_selection: Option<FlatTreeSelection>) {
325        *self.visible_range.borrow_mut() = flat_tree_selection.map(|selection| {
326            SelectionRange::new(
327                SelectionBoundary::new(&selection.start.container, selection.start.offset),
328                SelectionBoundary::new(&selection.end.container, selection.end.offset),
329            )
330        });
331    }
332
333    pub(crate) fn update_overlaps_document_selection_flags(&self, no_gc: &NoGC) {
334        if !self.visible_selection_dirty.take() {
335            return;
336        }
337
338        let previously_flagged_nodes = self
339            .iter_nodes_with_overlaps_document_selection_flag(no_gc)
340            .collect();
341        let flat_tree_selection = FlatTreeSelection::from_selection_if_renderable(no_gc, self);
342        VisibleSelectionFlagUpdate::run(
343            no_gc,
344            previously_flagged_nodes,
345            flat_tree_selection.as_ref(),
346            &self.document,
347        );
348        self.set_visible_range(flat_tree_selection);
349    }
350
351    /// <https://w3c.github.io/selection-api/#dfn-schedule-a-selectionchange-event>
352    pub(crate) fn queue_selectionchange_task(&self) {
353        // Step 1. If target's has scheduled selectionchange event is true, abort these steps.
354        if self.has_scheduled_selectionchange_event.get() {
355            return;
356        }
357        // Step 2. Set target's has scheduled selectionchange event to true.
358        self.has_scheduled_selectionchange_event.set(true);
359        // Step 3. Queue a task on the user interaction task source to fire a
360        // selectionchange event on target.
361        let this = Trusted::new(self);
362        self.document
363            .owner_global()
364            .task_manager()
365            .user_interaction_task_source() // w3c/selection-api#117
366            .queue(
367                // https://w3c.github.io/selection-api/#firing-selectionchange-event
368                task!(selectionchange_task_steps: move |cx| {
369                    let this = this.root();
370                    // Step 1. Set target's has scheduled selectionchange event to false.
371                    this.has_scheduled_selectionchange_event.set(false);
372                    // Step 2. If target is an element, fire an event named
373                    // selectionchange, which bubbles and not cancelable, at target.
374                    //
375                    // n/a
376
377                    // Step 3. Otherwise, if target is a document, fire an event named
378                    // selectionchange, which does not bubble and not cancelable, at
379                    // target.
380                    this.document.upcast::<EventTarget>().fire_event(cx, atom!("selectionchange"));
381                }),
382            );
383    }
384
385    fn is_in_composed_tree_of_document_and_is_not_ua_widget(&self, node: &Node) -> bool {
386        &*node.GetRootNode(&GetRootNodeOptions { composed: true }) == self.document.upcast::<Node>() &&
387            !node.is_in_ua_widget()
388    }
389
390    pub(crate) fn start_boundary(&self, cx: &mut JSContext) -> (DomRoot<Node>, u32) {
391        let range = self.expect_active_range(cx);
392        (range.start_container(), range.start_offset())
393    }
394
395    pub(crate) fn end_boundary(&self, cx: &mut JSContext) -> (DomRoot<Node>, u32) {
396        let range = self.expect_active_range(cx);
397        (range.end_container(), range.end_offset())
398    }
399
400    #[cfg(debug_assertions)]
401    fn assert_valid_selection(&self, no_gc: &NoGC) {
402        let range_borrow = self.range.borrow();
403        let Some(range) = range_borrow.as_ref() else {
404            return;
405        };
406        debug_assert_eq!(
407            range
408                .start
409                .container
410                .GetRootNode(&GetRootNodeOptions { composed: true }),
411            range
412                .end
413                .container
414                .GetRootNode(&GetRootNodeOptions { composed: true })
415        );
416        debug_assert!(
417            compare_shadow_including_dom_positions(
418                no_gc,
419                &range.start.container,
420                range.start.offset,
421                &range.end.container,
422                range.end.offset
423            ) != Ordering::Greater
424        );
425    }
426
427    #[cfg(debug_assertions)]
428    fn assert_valid_selection_and_live_range(&self, no_gc: &NoGC) {
429        self.assert_valid_selection(no_gc);
430
431        let Some(active_range) = self.live_range.get() else {
432            return;
433        };
434        debug_assert!(
435            bp_position(
436                no_gc,
437                &active_range.start_container(),
438                active_range.start_offset(),
439                &active_range.end_container(),
440                active_range.end_offset()
441            ) != Ordering::Greater
442        );
443    }
444
445    /// <https://w3c.github.io/editing/docs/execCommand/#active-range>
446    ///
447    /// > The active range is the range of the selection given by calling
448    /// > getSelection() on the context object. (Thus the active range may be null.)
449    pub(crate) fn active_range(&self, cx: &mut JSContext) -> Option<DomRoot<Range>> {
450        #[cfg(debug_assertions)]
451        self.assert_valid_selection_and_live_range(cx.no_gc());
452
453        if let Some(active_range) = self.live_range.get() {
454            return Some(active_range);
455        }
456
457        let live_range = {
458            let range = self.range.borrow();
459            let range = range.as_ref()?;
460            let live_range = Range::new(
461                cx,
462                &self.document,
463                &range.start.container,
464                range.start.offset,
465                &range.start.container,
466                range.start.offset,
467            );
468            live_range
469                .SetEnd(cx.no_gc(), &range.end.container, range.end.offset)
470                .expect("New end boundary should always be valid");
471            live_range
472        };
473
474        self.live_range.set(Some(&live_range));
475        live_range.associate_selection(self);
476        Some(live_range)
477    }
478
479    pub(crate) fn expect_active_range(&self, cx: &mut JSContext) -> DomRoot<Range> {
480        self.active_range(cx)
481            .expect("Should always have an active range")
482    }
483
484    pub(crate) fn set_visible_selection_dirty(&self) {
485        self.visible_selection_dirty.set(true);
486    }
487
488    pub(crate) fn composed_anchor_position(&self) -> Option<(DomRoot<Node>, u32)> {
489        let range = self.range.borrow();
490        let range = range.as_ref()?;
491        Some(match self.direction.get() {
492            Direction::Forwards => (range.start.container.as_rooted(), range.start.offset),
493            _ => (range.end.container.as_rooted(), range.end.offset),
494        })
495    }
496
497    /// <https://w3c.github.io/selection-api/#dfn-anchor>
498    fn live_anchor_node(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
499        self.active_range(cx)
500            .map(|range| match self.direction.get() {
501                Direction::Forwards => range.start_container(),
502                _ => range.end_container(),
503            })
504    }
505
506    /// <https://w3c.github.io/selection-api/#dfn-anchor>
507    fn live_anchor_offset(&self, cx: &mut JSContext) -> u32 {
508        self.active_range(cx)
509            .map_or(0, |range| match self.direction.get() {
510                Direction::Forwards => range.start_offset(),
511                _ => range.end_offset(),
512            })
513    }
514
515    /// <https://w3c.github.io/selection-api/#dfn-focus>
516    fn live_focus_node(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
517        self.active_range(cx)
518            .map(|range| match self.direction.get() {
519                Direction::Forwards => range.end_container(),
520                _ => range.start_container(),
521            })
522    }
523
524    /// <https://w3c.github.io/selection-api/#dfn-focus>
525    fn live_focus_offset(&self, cx: &mut JSContext) -> u32 {
526        self.active_range(cx)
527            .map_or(0, |range| match self.direction.get() {
528                Direction::Forwards => range.end_offset(),
529                _ => range.start_offset(),
530            })
531    }
532
533    /// <https://dom.spec.whatwg.org/#concept-node-insert> steps 5.1-5.2
534    /// and
535    /// <https://dom.spec.whatwg.org/#move> steps 17.1-17.2
536    /// adapted for selections.
537    pub(crate) fn insert_steps(&self, parent: &Node, child: &Node, count: u32) {
538        let mut range_borrow = self.range.borrow_mut();
539        let Some(range) = &mut *range_borrow else {
540            return;
541        };
542        let child_index = LazyCell::new(|| child.index());
543        // Step 5.1: For each live range whose start node is parent and start offset is
544        // greater than child’s index: increase its start offset by count.
545        if range.start.container == parent && range.start.offset > *child_index {
546            range.start.offset += count;
547            self.selection_boundaries_changed();
548        }
549        // Step 5.2: For each live range whose end node is parent and end offset is
550        // greater than child’s index: increase its end offset by count.
551        if range.end.container == parent && range.end.offset > *child_index {
552            range.end.offset += count;
553            self.selection_boundaries_changed();
554        }
555    }
556
557    /// <https://dom.spec.whatwg.org/#live-range-pre-remove-steps> steps 4-7
558    /// adapted for selections.
559    pub(crate) fn pre_remove_steps(
560        &self,
561        removed_node: &Node,           // "node" in the specification
562        parent_of_removed_node: &Node, // "parent" in the specification
563        index_of_removed_node: &mut dyn FnMut() -> u32, // "index" in the specification
564    ) {
565        let mut range_borrow = self.range.borrow_mut();
566        let Some(range) = &mut *range_borrow else {
567            return;
568        };
569
570        // Step 4: For each live range whose start node is an inclusive descendant of
571        // node, set its start to (parent, index).
572        if removed_node.is_shadow_including_inclusive_ancestor_of(&range.start.container) {
573            range.start = SelectionBoundary::new(parent_of_removed_node, index_of_removed_node());
574            self.selection_boundaries_changed();
575        }
576        // Step 5: For each live range whose end node is an inclusive descendant of node,
577        // set its end to (parent, index).
578        if removed_node.is_shadow_including_inclusive_ancestor_of(&range.end.container) {
579            range.end = SelectionBoundary::new(parent_of_removed_node, index_of_removed_node());
580            self.selection_boundaries_changed();
581        }
582        // Step 6: For each live range whose start node is parent and start offset is
583        // greater than index, decrease its start offset by 1.
584        if range.start.container == parent_of_removed_node &&
585            range.start.offset > index_of_removed_node()
586        {
587            range.start.offset -= 1;
588            self.selection_boundaries_changed();
589        }
590        // Step 7: For each live range whose end node is parent and end offset is greater than
591        // index, decrease its end offset by 1.
592        if range.end.container == parent_of_removed_node &&
593            range.end.offset > index_of_removed_node()
594        {
595            range.end.offset -= 1;
596            self.selection_boundaries_changed();
597        }
598    }
599
600    /// <https://dom.spec.whatwg.org/#dom-node-normalize> Steps 6.1-6.4 adapted for selections.
601    ///
602    /// - `parent`: The parent of both other node arguments.
603    /// - `node`: The node that text is being merged into.
604    /// - `current_node`: The node which has text being merged into `node` and will be
605    ///   removed from the DOM.
606    /// - `length`: The length of the text content that was merged into `node` from
607    ///   siblings before `current_node`.
608    pub(crate) fn normalization_steps(
609        &self,
610        parent: &Node,
611        node: &Node,
612        current_node: &Node,
613        current_node_index: &dyn Fn() -> u32,
614        length: u32,
615    ) {
616        let mut range_borrow = self.range.borrow_mut();
617        let Some(range) = &mut *range_borrow else {
618            return;
619        };
620        // Step 6.1: For each live range whose start node is currentNode: add length to its start
621        // offset and set its start node to node.
622        if range.start.container == current_node {
623            range.start.offset += length;
624            range.start.container = Dom::from_ref(node);
625            self.selection_boundaries_changed();
626        }
627        // Step 6.2 For each live range whose end node is currentNode: add length to its end offset
628        // and set its end node to node.
629        if range.end.container == current_node {
630            range.end.offset += length;
631            range.end.container = Dom::from_ref(node);
632            self.selection_boundaries_changed();
633        }
634        // Step 6.3: For each live range whose start node is currentNode’s parent and start
635        // offset is currentNode’s index: set its start node to node and its start offset
636        // to length.
637        if range.start.container == parent && range.start.offset == current_node_index() {
638            range.start.container = Dom::from_ref(node);
639            range.start.offset = length;
640            self.selection_boundaries_changed();
641        }
642        // Step 6.4: For each live range whose end node is currentNode’s parent and end offset is
643        // currentNode’s index: set its end node to node and its end offset to length.
644        if range.end.container == parent && range.end.offset == current_node_index() {
645            range.end.container = Dom::from_ref(node);
646            range.end.offset = length;
647            self.selection_boundaries_changed();
648        }
649    }
650
651    /// <https://dom.spec.whatwg.org/#concept-cd-replace> steps 8-11
652    /// adapted for selections.
653    pub(crate) fn replace_data_steps(
654        &self,
655        node: &Node,
656        offset: u32,
657        removed_code_units: u32,
658        added_code_units: &mut dyn FnMut() -> u32,
659    ) {
660        let mut range_borrow = self.range.borrow_mut();
661        let Some(range) = &mut *range_borrow else {
662            return;
663        };
664        // Step 8: For each live range whose start node is node and start offset is
665        // greater than offset but less than or equal to offset + count: set its start
666        // offset to offset.
667        let start_container = &range.start.container;
668        let start_offset = range.start.offset;
669        if &**start_container == node &&
670            start_offset > offset &&
671            start_offset <= offset + removed_code_units
672        {
673            range.start.offset = offset;
674            self.selection_boundaries_changed();
675        }
676        // Step 9: For each live range whose end node is node and end offset is
677        // greater than offset but less than or equal to offset + count: set its end
678        // offset to offset.
679        let end_container = &range.end.container;
680        let end_offset = range.end.offset;
681        if &**end_container == node &&
682            end_offset > offset &&
683            end_offset <= offset + removed_code_units
684        {
685            range.end.offset = offset;
686            self.selection_boundaries_changed();
687        }
688        // Step 10: For each live range whose start node is node and start offset is
689        // greater than offset + count: increase its start offset by data’s length and
690        // decrease it by count.
691        if &**start_container == node && start_offset > offset + removed_code_units {
692            range.start.offset = start_offset + added_code_units() - removed_code_units;
693            self.selection_boundaries_changed();
694        }
695        // Step 11: For each live range whose end node is node and end offset is
696        // greater than offset + count: increase its end offset by data’s length and
697        // decrease it by count.
698        if &**end_container == node && end_offset > offset + removed_code_units {
699            range.end.offset = end_offset + added_code_units() - removed_code_units;
700            self.selection_boundaries_changed();
701        }
702    }
703
704    /// <https://dom.spec.whatwg.org/#concept-text-split> steps 7.2-7.3
705    /// adapted for selections.
706    pub(crate) fn text_split_steps(
707        &self,
708        node: &Node,
709        offset: u32,
710        parent_node: &Node,
711        new_node: &Node,
712    ) {
713        let mut range_borrow = self.range.borrow_mut();
714        let Some(range) = &mut *range_borrow else {
715            return;
716        };
717        // Step 7.2: For each live range whose start node is node and start offset is
718        // greater than offset, set its start node to newNode and decrease its start
719        // offset by offset.
720        if range.start.container == node && range.start.offset > offset {
721            range.start.container = Dom::from_ref(new_node);
722            range.start.offset -= offset;
723            self.selection_boundaries_changed();
724        }
725        // Step 7.3: For each live range whose end node is node and end offset is greater
726        // than offset, set its end node to newNode and decrease its end offset by offset.
727        if range.end.container == node && range.end.offset > offset {
728            range.end.container = Dom::from_ref(new_node);
729            range.end.offset -= offset;
730            self.selection_boundaries_changed();
731        }
732        // Step 7.4: For each live range whose start node is parent and start offset is
733        // equal to the index of node plus 1, increase its start offset by 1.
734        let node_index = LazyCell::new(|| node.index());
735        if range.start.container == parent_node && range.start.offset == *node_index + 1 {
736            range.start.offset += 1;
737            self.selection_boundaries_changed();
738        }
739        // Step 7.5: For each live range whose end node is parent and end offset is equal
740        // to the index of node plus 1, increase its end offset by 1.
741        if range.end.container == parent_node && range.end.offset == *node_index + 1 {
742            range.end.offset += 1;
743            self.selection_boundaries_changed();
744        }
745    }
746
747    pub(crate) fn collapse_to_dom_position(
748        &self,
749        cx: &mut JSContext,
750        container: &Node,
751        offset: Utf32CodeUnitsOrNodeOffset,
752    ) {
753        let _ = self.Collapse(
754            cx,
755            Some(container),
756            container.to_sibling_or_utf16_offset(offset),
757        );
758    }
759
760    pub(crate) fn collapse_or_extend_to_dom_position(
761        &self,
762        cx: &mut JSContext,
763        container: &Node,
764        offset: Utf32CodeUnitsOrNodeOffset,
765    ) {
766        let offset = container.to_sibling_or_utf16_offset(offset);
767        let is_anchor =
768            self.composed_anchor_position()
769                .is_some_and(|(anchor_node, anchor_offset)| {
770                    &*anchor_node == container && anchor_offset == offset
771                });
772
773        if self.range.borrow().is_none() || is_anchor {
774            let _ = self.Collapse(cx, Some(container), offset);
775        } else {
776            let _ = self.Extend(cx, container, offset);
777        }
778    }
779}
780
781impl SelectionMethods<crate::DomTypeHolder> for Selection {
782    /// <https://w3c.github.io/selection-api/#dom-selection-anchornode>
783    fn GetAnchorNode(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
784        // > The attribute must return the anchor node of this, or null if the anchor is
785        // > null or anchor is not in the document tree.
786        //
787        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
788        // do the document tree check (other browsers don't either).
789        self.live_anchor_node(cx)
790    }
791
792    /// <https://w3c.github.io/selection-api/#dom-selection-anchoroffset>
793    fn AnchorOffset(&self, cx: &mut JSContext) -> u32 {
794        // > The attribute must return the anchor offset of this, or 0 if the anchor is null
795        // > or anchor is not in the document tree.
796        //
797        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
798        // do the document tree check (other browsers don't either).
799        self.live_anchor_offset(cx)
800    }
801
802    /// <https://w3c.github.io/selection-api/#dom-selection-focusnode>
803    fn GetFocusNode(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
804        // > The attribute must return the focus node of this, or null if the focus is
805        // > null or focus is not in the document tree.
806        //
807        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
808        // do the document tree check (other browsers don't either).
809        self.live_focus_node(cx)
810    }
811
812    /// <https://w3c.github.io/selection-api/#dom-selection-focusoffset>
813    fn FocusOffset(&self, cx: &mut JSContext) -> u32 {
814        // > The attribute must return the focus offset of this, or 0 if the focus is null
815        // > or focus is not in the document tree.
816        //
817        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
818        // do the document tree check (other browsers don't either).
819        self.live_focus_offset(cx)
820    }
821
822    /// <https://w3c.github.io/selection-api/#dom-selection-iscollapsed>
823    fn IsCollapsed(&self, cx: &mut JSContext) -> bool {
824        // > The attribute must return true if and only if the anchor and focus are the
825        // > same (including if both are null). Otherwise it must return false.
826        self.active_range(cx).is_none_or(|range| range.collapsed())
827    }
828
829    /// <https://w3c.github.io/selection-api/#dom-selection-rangecount>
830    fn RangeCount(&self) -> u32 {
831        // > The attribute must return 0 if this is empty or either focus or anchor is not
832        // > in the document tree, and must return 1 otherwise.
833        //
834        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
835        // do the document tree check (other browsers don't either).
836        if self.range.borrow().is_none() {
837            return 0;
838        }
839        1
840    }
841
842    /// <https://w3c.github.io/selection-api/#dom-selection-type>
843    fn Type(&self) -> DOMString {
844        // > The attribute must return "None" if this is empty or either focus or anchor
845        // > is not in the document tree, "Caret" if this's range is collapsed, and "Range"
846        // > otherwise.
847        //
848        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
849        // do the document tree check (other browsers don't either).
850        let range = self.range.borrow();
851        let Some(range) = range.as_ref() else {
852            return DOMString::from_static("None");
853        };
854        if range.collapsed() {
855            DOMString::from_static("Caret")
856        } else {
857            DOMString::from_static("Range")
858        }
859    }
860
861    /// <https://w3c.github.io/selection-api/#dom-selection-direction>
862    fn Direction(&self) -> DOMString {
863        // > The attribute must return "none" if this is empty or this selection is
864        // > directionless. "forward" if this selection's direction is forwards and
865        // > "backward" if this selection's direction is backwards.
866        match self.direction.get() {
867            Direction::Directionless => DOMString::from_static("none"),
868            Direction::Forwards => DOMString::from_static("forward"),
869            Direction::Backwards => DOMString::from_static("backward"),
870        }
871    }
872
873    /// <https://w3c.github.io/selection-api/#dom-selection-getrangeat>
874    fn GetRangeAt(&self, cx: &mut JSContext, index: u32) -> Fallible<DomRoot<Range>> {
875        // > The method must throw an IndexSizeError exception if index is not 0, or if this
876        // > is empty or either focus or anchor is not in the document tree. Otherwise, it
877        // > must return a reference to (not a copy of) this's range.
878        //
879        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
880        // do the document tree check (other browsers don't either).
881        if index != 0 {
882            return Err(Error::IndexSize(Some("Index must be zero".into())));
883        }
884
885        let Some(range) = self.active_range(cx) else {
886            return Err(Error::IndexSize(Some("Selection is empty".into())));
887        };
888
889        Ok(range)
890    }
891
892    /// <https://w3c.github.io/selection-api/#dom-selection-addrange>
893    fn AddRange(&self, no_gc: &NoGC, range: &Range) {
894        // Step 1. If the root of the range's boundary points are not the document
895        // associated with this, abort these steps.
896        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(&range.start_container()) {
897            return;
898        }
899
900        // Step 2. If rangeCount is not 0, abort these steps.
901        if self.RangeCount() != 0 {
902            return;
903        }
904
905        // Step 3. Set this's range to range by a strong reference (not by making a copy).
906        self.set_live_range(no_gc, Some(range));
907
908        // Are we supposed to set Direction here? w3c/selection-api#116
909        self.direction.set(Direction::Forwards);
910    }
911
912    /// <https://w3c.github.io/selection-api/#dom-selection-removerange>
913    fn RemoveRange(&self, no_gc: &NoGC, range: &Range) -> ErrorResult {
914        // > The method must make this empty by disassociating its range if this's range
915        // > is range. Otherwise, it must throw a NotFoundError.
916        if let Some(own_range) = self.live_range.get() &&
917            &*own_range == range
918        {
919            self.set_range(no_gc, None);
920            return Ok(());
921        }
922        Err(Error::NotFound(None))
923    }
924
925    /// <https://w3c.github.io/selection-api/#dom-selection-removeallranges>
926    fn RemoveAllRanges(&self, no_gc: &NoGC) {
927        // > The method must make this empty by disassociating its range if this has an
928        // > associated range.
929        self.set_range(no_gc, None);
930    }
931
932    /// <https://w3c.github.io/selection-api/#dom-selection-empty>
933    fn Empty(&self, no_gc: &NoGC) {
934        // > The method must be an alias, and behave identically, to removeAllRanges().
935        self.RemoveAllRanges(no_gc);
936    }
937
938    /// <https://w3c.github.io/selection-api/#dom-selection-getcomposedranges>
939    fn GetComposedRanges(
940        &self,
941        cx: &mut JSContext,
942        options: &GetComposedRangesOptions,
943    ) -> Vec<DomRoot<StaticRange>> {
944        // Step 1. If this is empty, return an empty array.
945        let borrowed_range = self.range.borrow();
946        let Some(range) = borrowed_range.as_ref() else {
947            return Vec::new();
948        };
949
950        // Step 2. Otherwise, let startNode be start node of the range associated with
951        // this, and let startOffset be start offset of the range.
952        let mut start_node = range.start.container.as_rooted();
953        let mut start_offset = range.start.offset;
954
955        let is_ancestor_of_provided_shadow_roots = |shadow_root: &ShadowRoot| {
956            let shadow_root_node = shadow_root.upcast::<Node>();
957            options.shadowRoots.iter().any(|option_shadow_root| {
958                shadow_root_node
959                    .is_shadow_including_inclusive_ancestor_of(option_shadow_root.upcast())
960            })
961        };
962
963        // Step 3. While startNode is a node, startNode's root is a shadow root, and
964        // startNode's root is not a shadow-including inclusive ancestor of any of
965        // options["shadowRoots"], repeat these steps:
966        while let Some(containing_shadow_root) = start_node.containing_shadow_root() &&
967            !is_ancestor_of_provided_shadow_roots(&containing_shadow_root)
968        {
969            // Step 3.1. Set startOffset to index of startNode's root's host.
970            let host = DomRoot::upcast::<Node>(containing_shadow_root.Host());
971            start_offset = host.index();
972
973            // Step 3.2. Set startNode to startNode's root's host's parent.
974            // See <https://github.com/w3c/selection-api/issues/161> for why
975            // we always know that the start_node is a node.
976            let Some(new_start_node) = host.GetParentNode() else {
977                return Vec::new();
978            };
979            start_node = new_start_node;
980        }
981
982        // Step 4. Let endNode be end node of the range associated with this, and let
983        // endOffset be end offset of the range.
984        let mut end_node = range.end.container.as_rooted();
985        let mut end_offset = range.end.offset;
986
987        // Step 5. While endNode is a node, endNode's root is a shadow root, and endNode's
988        // root is not a shadow-including inclusive ancestor of any of
989        // options["shadowRoots"], repeat these steps:
990        while let Some(containing_shadow_root) = end_node.containing_shadow_root() &&
991            !is_ancestor_of_provided_shadow_roots(&containing_shadow_root)
992        {
993            // Step 5.1. Set endOffset to index of endNode's root's host plus 1.
994            let host = DomRoot::upcast::<Node>(containing_shadow_root.Host());
995            end_offset = host.index() + 1;
996
997            // Step 5.2. Set endNode to endNode's root's host's parent.
998            // See <https://github.com/w3c/selection-api/issues/161> for why
999            // we always know that the end_node is a node.
1000            let Some(new_end_node) = host.GetParentNode() else {
1001                return Vec::new();
1002            };
1003            end_node = new_end_node;
1004        }
1005
1006        drop(borrowed_range);
1007
1008        // Step 6. Return an array consisting of new StaticRange whose start node is
1009        // startNode, start offset is startOffset, end node is endNode, and end offset is
1010        // endOffset.
1011        vec![DomRoot::from_ref(&StaticRange::new(
1012            cx,
1013            &self.document,
1014            &start_node,
1015            start_offset,
1016            &end_node,
1017            end_offset,
1018        ))]
1019    }
1020
1021    /// <https://w3c.github.io/selection-api/#dom-selection-collapse>
1022    fn Collapse(&self, cx: &mut JSContext, node: Option<&Node>, offset: u32) -> ErrorResult {
1023        // Step 1. If node is null, this method must behave identically as
1024        // removeAllRanges() and abort these steps.
1025        let Some(node) = node else {
1026            self.set_range(cx.no_gc(), None);
1027            return Ok(());
1028        };
1029
1030        // Step 2. If node is a DocumentType, throw an InvalidNodeTypeError exception and
1031        // abort these steps.
1032        if node.is_doctype() {
1033            return Err(Error::InvalidNodeType(None));
1034        }
1035
1036        // Step 3. The method must throw an IndexSizeError exception if offset is longer
1037        // than node's length and abort these steps.
1038        if offset > node.len() {
1039            return Err(Error::IndexSize(None));
1040        }
1041
1042        // Step 4. If document associated with this is not a shadow-including inclusive
1043        // ancestor of node, abort these steps.
1044        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1045            return Ok(());
1046        }
1047
1048        // Step 5. Otherwise, let newRange be a new range.
1049        // Step 6. Set the start the start and the end of newRange to (node, offset).
1050        // Step 7. Set this's range to newRange.
1051        self.set_range(
1052            cx.no_gc(),
1053            Some(SelectionRange::collapsed_at(SelectionBoundary::new(
1054                node, offset,
1055            ))),
1056        );
1057
1058        // Are we supposed to set Direction here? w3c/selection-api#116
1059        self.direction.set(Direction::Forwards);
1060
1061        Ok(())
1062    }
1063
1064    /// <https://w3c.github.io/selection-api/#dom-selection-setposition>
1065    fn SetPosition(&self, cx: &mut JSContext, node: Option<&Node>, offset: u32) -> ErrorResult {
1066        // > The method must be an alias, and behave identically, to collapse().
1067        self.Collapse(cx, node, offset)
1068    }
1069
1070    /// <https://w3c.github.io/selection-api/#dom-selection-collapsetostart>
1071    fn CollapseToStart(&self, cx: &mut JSContext) -> ErrorResult {
1072        // > The method must throw InvalidStateError exception if the this is empty.
1073        // > Otherwise, it must create a new range, set the start both its start and end to
1074        // > the start of this's range, and then set this's range to the newly-created
1075        // > range.
1076        let Some((start_container, start_offset)) = self
1077            .range
1078            .borrow()
1079            .as_ref()
1080            .map(|range| (range.start.container.as_rooted(), range.start.offset))
1081        else {
1082            return Err(Error::InvalidState(None));
1083        };
1084        self.Collapse(cx, Some(&*start_container), start_offset)
1085    }
1086
1087    /// <https://w3c.github.io/selection-api/#dom-selection-collapsetoend>
1088    fn CollapseToEnd(&self, cx: &mut JSContext) -> ErrorResult {
1089        // > The method must throw InvalidStateError exception if the this is empty.
1090        // > Otherwise, it must create a new range, set the start both its start and end to
1091        // > the end of this's range, and then set this's range to the newly-created range.
1092        let Some((end_container, end_offset)) = self
1093            .range
1094            .borrow()
1095            .as_ref()
1096            .map(|range| (range.end.container.as_rooted(), range.end.offset))
1097        else {
1098            return Err(Error::InvalidState(None));
1099        };
1100        self.Collapse(cx, Some(&*end_container), end_offset)
1101    }
1102
1103    /// <https://w3c.github.io/selection-api/#dom-selection-extend>
1104    fn Extend(&self, cx: &mut JSContext, node: &Node, offset: u32) -> ErrorResult {
1105        // Step 1. If the document associated with this is not a shadow-including
1106        // inclusive ancestor of node, abort these steps.
1107        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1108            return Ok(());
1109        }
1110
1111        // Step 2. If this is empty, throw an InvalidStateError exception and abort these steps.
1112        let range_borrow = self.range.borrow();
1113        let Some(range) = range_borrow.as_ref() else {
1114            return Err(Error::InvalidState(None));
1115        };
1116
1117        // This isn't specified, but it appears to be implementation behavior of other
1118        // browsers. See w3c/selection-api#118.
1119        if node.is_doctype() {
1120            return Err(Error::InvalidNodeType(None));
1121        }
1122
1123        // As with is_doctype, this is not explicit in the selection specification steps
1124        // here but implied by which exceptions are thrown in WPT tests.
1125        if offset > node.len() {
1126            return Err(Error::IndexSize(None));
1127        }
1128
1129        // Step 3. Let oldAnchor and oldFocus be the this's anchor and focus, and let
1130        // newFocus be the boundary point (node, offset).
1131        //
1132        // Note: oldFocus is unused, so we do not set it here.
1133        let (old_anchor_node, old_anchor_offset) = self
1134            .composed_anchor_position()
1135            .expect("has range, therefore has anchor node");
1136
1137        // Step 4. Let newRange be a new range.
1138        // Note: Set directly to satisfy crown.
1139        let direction;
1140
1141        // Step 5. If node's root is not the same as the this's range's root, set the
1142        // start newRange's start and end to newFocus.
1143        let in_different_roots = !nodes_have_same_shadow_root(&range.start.container, node);
1144        drop(range_borrow);
1145
1146        if in_different_roots {
1147            self.set_range(
1148                cx.no_gc(),
1149                Some(SelectionRange::collapsed_at(SelectionBoundary::new(
1150                    node, offset,
1151                ))),
1152            );
1153            direction = Direction::Forwards;
1154        } else {
1155            let is_old_anchor_before_or_equal = matches!(
1156                compare_shadow_including_dom_positions(
1157                    cx.no_gc(),
1158                    &old_anchor_node,
1159                    old_anchor_offset,
1160                    node,
1161                    offset
1162                ),
1163                Ordering::Less | Ordering::Equal
1164            );
1165            if is_old_anchor_before_or_equal {
1166                // Step 6. Otherwise, if oldAnchor is before or equal to newFocus, set the start
1167                // newRange's start to oldAnchor, then set its end to newFocus.
1168                self.set_range(
1169                    cx.no_gc(),
1170                    Some(SelectionRange::new(
1171                        SelectionBoundary::new(&old_anchor_node, old_anchor_offset),
1172                        SelectionBoundary::new(node, offset),
1173                    )),
1174                );
1175                direction = Direction::Forwards;
1176            } else {
1177                // Step 7. Otherwise, set the start newRange's start to newFocus, then set
1178                // its end to oldAnchor.
1179                self.set_range(
1180                    cx.no_gc(),
1181                    Some(SelectionRange::new(
1182                        SelectionBoundary::new(node, offset),
1183                        SelectionBoundary::new(&old_anchor_node, old_anchor_offset),
1184                    )),
1185                );
1186                direction = Direction::Backwards;
1187            }
1188        }
1189
1190        // Step 8. Set this's range to newRange.
1191        // Note: Done above to satisfy crown.
1192
1193        // Step 9. If newFocus is before oldAnchor, set this's direction to backwards.
1194        // Otherwise, set it to forwards.
1195        self.direction.set(direction);
1196
1197        Ok(())
1198    }
1199
1200    /// <https://w3c.github.io/selection-api/#dom-selection-setbaseandextent>
1201    fn SetBaseAndExtent(
1202        &self,
1203        cx: &mut JSContext,
1204        anchor_node: &Node,
1205        anchor_offset: u32,
1206        focus_node: &Node,
1207        focus_offset: u32,
1208    ) -> ErrorResult {
1209        // This isn't specified, but it appears to be implementation behavior of other
1210        // browsers. See w3c/selection-api#118.
1211        if anchor_node.is_doctype() || focus_node.is_doctype() {
1212            return Err(Error::InvalidNodeType(None));
1213        }
1214
1215        // Step 1. If anchorOffset is longer than anchorNode's length or if focusOffset is
1216        // longer than focusNode's length, throw an IndexSizeError exception and abort
1217        // these steps.
1218        if anchor_offset > anchor_node.len() || focus_offset > focus_node.len() {
1219            return Err(Error::IndexSize(None));
1220        }
1221
1222        // Step 2. If document associated with this is not a shadow-including inclusive
1223        // ancestor of anchorNode or focusNode, abort these steps.
1224        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(anchor_node) ||
1225            !self.is_in_composed_tree_of_document_and_is_not_ua_widget(focus_node)
1226        {
1227            return Ok(());
1228        }
1229
1230        // Step 3. Let anchor be the boundary point (anchorNode, anchorOffset) and let
1231        // focus be the boundary point (focusNode, focusOffset).
1232        //
1233        // Note: We do not model the boundary point in this way.
1234
1235        // Step 4. Let newRange be a new range.
1236        // Note: We set the range directly to satisfy crown.
1237
1238        // Step 5. If anchor is before focus, set the start the newRange's start to anchor
1239        // and its end to focus. Otherwise, set the start them to focus and anchor
1240        // respectively.
1241        let ordering = compare_shadow_including_dom_positions(
1242            cx.no_gc(),
1243            anchor_node,
1244            anchor_offset,
1245            focus_node,
1246            focus_offset,
1247        );
1248        if ordering == Ordering::Less {
1249            self.set_range(
1250                cx.no_gc(),
1251                Some(SelectionRange::new(
1252                    SelectionBoundary::new(anchor_node, anchor_offset),
1253                    SelectionBoundary::new(focus_node, focus_offset),
1254                )),
1255            );
1256        } else {
1257            self.set_range(
1258                cx.no_gc(),
1259                Some(SelectionRange::new(
1260                    SelectionBoundary::new(focus_node, focus_offset),
1261                    SelectionBoundary::new(anchor_node, anchor_offset),
1262                )),
1263            );
1264        };
1265
1266        // Step 6. Set this's range to newRange.
1267        // Note: Done above to satisfy crown.
1268
1269        // Step 7. If focus is before anchor, set this's direction to backwards.
1270        // Otherwise, set it to forwards
1271        if ordering == Ordering::Greater {
1272            self.direction.set(Direction::Backwards);
1273        } else {
1274            self.direction.set(Direction::Forwards);
1275        }
1276
1277        Ok(())
1278    }
1279
1280    /// <https://w3c.github.io/selection-api/#dom-selection-selectallchildren>
1281    fn SelectAllChildren(&self, cx: &mut JSContext, node: &Node) -> ErrorResult {
1282        // Step 1. If node is a DocumentType, throw an InvalidNodeTypeError exception and
1283        // abort these steps.
1284        if node.is_doctype() {
1285            return Err(Error::InvalidNodeType(None));
1286        }
1287
1288        // Step 2. If node's root is not the document associated with this, abort these
1289        // steps.
1290        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1291            return Ok(());
1292        }
1293
1294        // Let newRange be a new range and childCount be the number of children of node.
1295        let child_count = node.children_count();
1296
1297        // Step 4. Set newRange's start to (node, 0).
1298        // Step 5. Set newRange's end to (node, childCount).
1299        // Step 6. Set this's range to newRange.
1300        self.set_range(
1301            cx.no_gc(),
1302            Some(SelectionRange::new(
1303                SelectionBoundary::new(node, 0),
1304                SelectionBoundary::new(node, child_count),
1305            )),
1306        );
1307
1308        // Step 7. Set this's direction to forwards.
1309        self.direction.set(Direction::Forwards);
1310
1311        Ok(())
1312    }
1313
1314    /// <https://w3c.github.io/selection-api/#dom-selection-deletecontents>
1315    fn DeleteFromDocument(&self, cx: &mut JSContext) -> ErrorResult {
1316        // > The method must invoke deleteContents() on this's range if this is not empty
1317        // > and both focus and anchor are in the document tree. Otherwise the method must
1318        // > do nothing.
1319        let Some(active_range) = self
1320            .active_range(cx)
1321            .filter(|range| range.start_and_end_are_in_document_tree())
1322        else {
1323            return Ok(());
1324        };
1325
1326        active_range.DeleteContents(cx)
1327    }
1328
1329    /// <https://w3c.github.io/selection-api/#dom-selection-containsnode>
1330    fn ContainsNode(&self, no_gc: &NoGC, node: &Node, allow_partial_containment: bool) -> bool {
1331        // > The method must return false if this is empty or if node's root is not the document
1332        // > associated with this.
1333        // >
1334        // > Otherwise, if allowPartialContainment is false, the method must return true if and only
1335        // > if start of its range is before or visually equivalent to the first boundary point in
1336        // > the node *and* end of its range is after or visually equivalent to the last boundary
1337        // > point in the node.
1338        // >
1339        // > If allowPartialContainment is true, the method must return true if and only if start of
1340        // > its range is before or visually equivalent to the last boundary point in the node *and*
1341        // > end of its range is after or visually equivalent to the first boundary point in the
1342        // > node.
1343        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1344            return false;
1345        }
1346        let range = self.range.borrow();
1347        let Some(range) = range.as_ref() else {
1348            return false;
1349        };
1350        let start_node = &*range.start.container;
1351        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(start_node) {
1352            return false;
1353        }
1354        let end_node = &*range.end.container;
1355
1356        let first_offset = 0;
1357        let last_offset = node.len();
1358        let (compare_start_to, compare_end_to) = if allow_partial_containment {
1359            (last_offset, first_offset)
1360        } else {
1361            (first_offset, last_offset)
1362        };
1363
1364        // TODO: find out what "visually equivalent" means for boundary points and implement it.
1365        // https://github.com/w3c/selection-api/issues/6
1366        //
1367        // For now it is simplified to "node is in the flat tree" and "position is equal".
1368        if !node.is_in_flat_tree(no_gc) {
1369            return false;
1370        }
1371
1372        matches!(
1373            compare_shadow_including_dom_positions(
1374                no_gc,
1375                start_node,
1376                range.start.offset,
1377                node,
1378                compare_start_to
1379            ),
1380            Ordering::Less | Ordering::Equal
1381        ) && matches!(
1382            compare_shadow_including_dom_positions(
1383                no_gc,
1384                end_node,
1385                range.end.offset,
1386                node,
1387                compare_end_to
1388            ),
1389            Ordering::Greater | Ordering::Equal
1390        )
1391    }
1392
1393    /// <https://w3c.github.io/selection-api/#dom-selection-stringifier>
1394    fn Stringifier(&self, cx: &mut JSContext) -> DOMString {
1395        // > The stringification must return the string, which is the concatenation of the
1396        // > rendered text if there is a range associated with this.
1397        // >
1398        // > If the selection is within a textarea or input element, it must return the
1399        // > selected substring in its value.
1400        let Some(visible_selection) =
1401            FlatTreeSelection::from_selection_if_renderable(cx.no_gc(), self)
1402        else {
1403            return DOMString::new();
1404        };
1405
1406        // Flush all layout before stringifying so that rendered text is up-to-date.
1407        self.document.window().layout_reflow(QueryMsg::StyleQuery);
1408
1409        let mut user_select_cache = Default::default();
1410        let mut string = DOMString::new();
1411        for node in visible_selection.traversal() {
1412            let Some(character_data) = node.downcast::<CharacterData>() else {
1413                continue;
1414            };
1415
1416            if node.used_user_select(cx.no_gc(), &mut user_select_cache) == UsedUserSelect::None {
1417                continue;
1418            }
1419
1420            let range = visible_selection.range_for_character_data(character_data);
1421            let Some(text) = character_data.rendered_text(range) else {
1422                continue;
1423            };
1424            string.push_str(&text);
1425        }
1426
1427        string
1428    }
1429}
1430
1431impl<'dom> LayoutDom<'dom, Selection> {
1432    #[expect(unsafe_code)]
1433    pub(crate) fn range_for_layout(&self) -> &Option<SelectionRange> {
1434        unsafe { self.unsafe_get().visible_range.borrow_for_layout() }
1435    }
1436}
1437
1438enum FlatTreeNodePosition {
1439    Before(DomRoot<Node>),
1440    Inside(DomRoot<Node>),
1441    After(DomRoot<Node>),
1442}
1443
1444impl FlatTreeNodePosition {
1445    fn node(&self) -> &Node {
1446        match self {
1447            FlatTreeNodePosition::Before(node) => node,
1448            FlatTreeNodePosition::Inside(node) => node,
1449            FlatTreeNodePosition::After(node) => node,
1450        }
1451    }
1452}
1453
1454/// Find the position of a node and offset in the flat tree for the purposes of selection
1455/// boundaries. This projects the given position onto the flat tree, accounting for origin
1456/// nodes that may not actually be in the flat tree at all.
1457fn position_in_flat_tree_for_selection(
1458    no_gc: &NoGC,
1459    boundary: &FlatTreeBoundary,
1460) -> FlatTreeNodePosition {
1461    if boundary.container.is::<CharacterData>() {
1462        return FlatTreeNodePosition::Inside(boundary.container.as_rooted());
1463    }
1464
1465    let shadow_host_or_node = |node: &Node| {
1466        boundary
1467            .container
1468            .downcast::<ShadowRoot>()
1469            .map(|shadow_root| DomRoot::upcast(shadow_root.Host()))
1470            .unwrap_or(DomRoot::from_ref(node))
1471    };
1472
1473    if let Some(child) = boundary.container.children().nth(boundary.offset as usize) {
1474        if let FlatTreeParent::Parent(_) = child.parent_in_flat_tree(no_gc) {
1475            return FlatTreeNodePosition::Before(child);
1476        }
1477    } else if let Some(last_child) = boundary.container.GetLastChild() &&
1478        let FlatTreeParent::Parent(_) = last_child.parent_in_flat_tree(no_gc)
1479    {
1480        return FlatTreeNodePosition::After(shadow_host_or_node(&boundary.container.as_rooted()));
1481    }
1482
1483    // The container has no child in the flat tree or the child indicated by the index
1484    // isn't in the flat tree, so just return a position inside that container.
1485    FlatTreeNodePosition::Inside(shadow_host_or_node(&boundary.container.as_rooted()))
1486}
1487
1488impl Node {
1489    /// Get the `Utf16CodeUnits` offset for the given offset if `self` is a
1490    /// `CharacterData` or else return the offset in the child list.
1491    pub(crate) fn to_sibling_or_utf16_offset(&self, offset: Utf32CodeUnitsOrNodeOffset) -> u32 {
1492        if let Some(character_data) = self.downcast::<CharacterData>() {
1493            // TODO: ensure that each `CharacterData` holds no more than 4 GiB?
1494            offset
1495                .to_utf16_code_units_in(AssumeUnder4GB, &character_data.data())
1496                .0
1497        } else {
1498            offset.0
1499        }
1500    }
1501}
1502
1503bitflags! {
1504    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1505    pub(crate) struct SelectionLiveRangeNotification: u8 {
1506        const Start = 1 << 0;
1507        const End = 1 << 1;
1508    }
1509}
1510
1511fn nodes_have_same_shadow_root(a: &Node, b: &Node) -> bool {
1512    a.is_connected() && b.is_connected() && a.containing_shadow_root() == b.containing_shadow_root()
1513}
1514
1515/// Project the position given by `container_a` and `offset_a` into a root that also
1516/// contains `target`. If `target` is not supplied then project the position into the
1517/// `Document` root.
1518///
1519/// This does something very similar to GetComposedRanges, but doesn't have special
1520/// handling for start and end nodes.
1521fn project_into_shared_tree<'a>(
1522    no_gc: &'a NoGC,
1523    container_a: &Node,
1524    mut offset_a: u32,
1525    target: Option<&Node>,
1526) -> (UnrootedDom<'a, Node>, u32) {
1527    let mut start_node = UnrootedDom::from_ref(container_a, no_gc);
1528    while let Some(containing_shadow_root) = start_node.containing_shadow_root_unrooted(no_gc) &&
1529        target.is_none_or(|target| {
1530            !containing_shadow_root
1531                .upcast::<Node>()
1532                .is_shadow_including_inclusive_ancestor_of(target)
1533        })
1534    {
1535        let host = DomRoot::upcast::<Node>(containing_shadow_root.Host());
1536        offset_a = host.index();
1537        start_node = host
1538            .get_parent_node_unrooted(no_gc)
1539            .expect("Should only be called on nodes in the same tree");
1540    }
1541
1542    (start_node, offset_a)
1543}
1544
1545/// Compare two shadow-including DOM positions by first projecting them into
1546/// a shared root and then comparing the two positions. This function assumes
1547/// that the two positions share a [`Document`].
1548fn compare_shadow_including_dom_positions(
1549    no_gc: &NoGC,
1550    container_a: &Node,
1551    offset_a: u32,
1552    container_b: &Node,
1553    offset_b: u32,
1554) -> Ordering {
1555    let (projected_container_a, projected_offset_a) =
1556        project_into_shared_tree(no_gc, container_a, offset_a, Some(container_b));
1557    let (projected_container_b, projected_offset_b) =
1558        project_into_shared_tree(no_gc, container_b, offset_b, Some(container_a));
1559
1560    let a_was_projected = &**projected_container_a != container_a;
1561    let b_was_projected = &**projected_container_b != container_b;
1562
1563    match bp_position(
1564        no_gc,
1565        &projected_container_a,
1566        projected_offset_a,
1567        &projected_container_b,
1568        projected_offset_b,
1569    ) {
1570        Ordering::Equal if a_was_projected && !b_was_projected => Ordering::Greater,
1571        Ordering::Equal if !a_was_projected && b_was_projected => Ordering::Less,
1572        ordering => ordering,
1573    }
1574}
1575
1576struct FlatTreeBoundary<'no_gc> {
1577    container: UnrootedDom<'no_gc, Node>,
1578    offset: u32,
1579}
1580
1581struct FlatTreeSelection<'no_gc> {
1582    no_gc: &'no_gc NoGC,
1583    start: FlatTreeBoundary<'no_gc>,
1584    end: FlatTreeBoundary<'no_gc>,
1585}
1586
1587impl<'no_gc> FlatTreeSelection<'no_gc> {
1588    /// Get the [`FlatTreeSelection`] for this [`Selection`], returning `None` if the [`Selection`]
1589    /// doesn't have a range or if that flat tree range is unrenderable (for instance, because one
1590    /// or both of the endpoints is not in the flat tree).
1591    fn from_selection_if_renderable(no_gc: &'no_gc NoGC, selection: &Selection) -> Option<Self> {
1592        let range = selection.range.borrow();
1593        let range = range.as_ref()?;
1594
1595        let mut start_container = range.start.container.as_unrooted(no_gc);
1596        let mut start_offset = range.start.offset;
1597        let mut end_container = range.end.container.as_unrooted(no_gc);
1598        let mut end_offset = range.end.offset;
1599
1600        if !start_container.is_in_flat_tree(no_gc) {
1601            return None;
1602        }
1603        if !end_container.is_in_flat_tree(no_gc) {
1604            return None;
1605        }
1606
1607        // Compare the position of the two nodes in the flat tree, considering the
1608        // condition when they don't share a common ancestor to be an unrenderable
1609        // selection.
1610        let ordering = compare_dom_positions::<FlatTreeForSelectionNoGcTraversal>(
1611            no_gc,
1612            &range.start.container,
1613            range.start.offset,
1614            &range.end.container,
1615            range.end.offset,
1616        )
1617        .0?;
1618
1619        // If the visible selection is inverted relative to the composed tree range, swap
1620        // the boundaries.
1621        if ordering == Ordering::Greater {
1622            std::mem::swap(&mut start_container, &mut end_container);
1623            std::mem::swap(&mut start_offset, &mut end_offset);
1624        }
1625
1626        Some(FlatTreeSelection {
1627            no_gc,
1628            start: FlatTreeBoundary {
1629                container: start_container,
1630                offset: start_offset,
1631            },
1632            end: FlatTreeBoundary {
1633                container: end_container,
1634                offset: end_offset,
1635            },
1636        })
1637    }
1638
1639    fn range_for_character_data(&self, character_data: &CharacterData) -> RangeAny<Utf32CodeUnits> {
1640        let text = character_data.data();
1641        let node: &Node = character_data.upcast();
1642        RangeAny::new(
1643            (node == &**self.start.container)
1644                .then(|| Utf16CodeUnits(self.start.offset).to_utf32_code_units_in(&text)),
1645            (node == &**self.end.container)
1646                .then(|| Utf16CodeUnits(self.end.offset).to_utf32_code_units_in(&text)),
1647        )
1648    }
1649
1650    fn traversal(&self) -> VisibleSelectionTraversal<'no_gc> {
1651        let start_position = position_in_flat_tree_for_selection(self.no_gc, &self.start);
1652        let end_position = position_in_flat_tree_for_selection(self.no_gc, &self.end);
1653        VisibleSelectionTraversal {
1654            following: start_position
1655                .node()
1656                .following_flat_tree_nodes_unrooted(self.no_gc),
1657            start: start_position,
1658            end: end_position,
1659            finished: false,
1660            skip_subtree: false,
1661        }
1662    }
1663}
1664
1665struct VisibleSelectionTraversal<'no_gc> {
1666    following: UnrootedFollowingFlatTreeNodesTraversal<'no_gc>,
1667    start: FlatTreeNodePosition,
1668    end: FlatTreeNodePosition,
1669    finished: bool,
1670    skip_subtree: bool,
1671}
1672
1673impl<'no_gc> Iterator for VisibleSelectionTraversal<'no_gc> {
1674    type Item = UnrootedDom<'no_gc, Node>;
1675
1676    fn next(&mut self) -> Option<Self::Item> {
1677        while !self.finished {
1678            let following = if std::mem::take(&mut self.skip_subtree) {
1679                self.following.next_skipping_subtree()?
1680            } else {
1681                self.following.next()?
1682            };
1683
1684            match following {
1685                PrePostIteration::Enter(node) => {
1686                    // If the traversal ends right before the final node and this is the
1687                    // final node, just finish now.
1688                    if &**node == self.end.node() &&
1689                        matches!(self.end, FlatTreeNodePosition::Before(_))
1690                    {
1691                        self.finished = true;
1692                        break;
1693                    }
1694                    // If the selection starts after the first node, do not set any flags
1695                    // on that nodes descendants.
1696                    if &**node == self.start.node() &&
1697                        matches!(self.start, FlatTreeNodePosition::After(_))
1698                    {
1699                        self.skip_subtree = true;
1700                    }
1701                    return Some(node);
1702                },
1703                PrePostIteration::Leave(node) => {
1704                    self.finished = &**node == self.end.node();
1705                },
1706            }
1707        }
1708
1709        None
1710    }
1711}
1712
1713struct VisibleSelectionFlagUpdate<'no_gc> {
1714    no_gc: &'no_gc NoGC,
1715    /// The nodes that previously had the OVERLAPS_DOCUMENT_SELECTION set on them before
1716    /// this flag update.
1717    ///
1718    /// Hash keys are pointer addresses which are not directly controlled by web content
1719    /// so we don’t need HashDoS resistance and can use a faster hasher than `std`’s default
1720    previously_flagged_nodes: FxHashSet<UnrootedDom<'no_gc, Node>>,
1721    /// Cache shared between calls to [`Node::used_user_select`]
1722    used_user_select_cache: FxHashMap<UnrootedDom<'no_gc, Node>, UsedUserSelect>,
1723    /// Whether or not this update requires a display list update.
1724    needs_new_display_list: bool,
1725}
1726
1727impl<'no_gc> VisibleSelectionFlagUpdate<'no_gc> {
1728    fn run(
1729        no_gc: &'no_gc NoGC,
1730        previously_flagged_nodes: FxHashSet<UnrootedDom<'no_gc, Node>>,
1731        flat_tree_selection: Option<&FlatTreeSelection<'_>>,
1732        document: &Document,
1733    ) {
1734        let mut update = Self {
1735            no_gc,
1736            previously_flagged_nodes,
1737            used_user_select_cache: Default::default(),
1738            needs_new_display_list: false,
1739        };
1740
1741        if let Some(flat_tree_selection) = flat_tree_selection {
1742            let traversal = flat_tree_selection.traversal();
1743            for ancestor in traversal
1744                .start
1745                .node()
1746                .ancestors_in_flat_tree_unrooted(no_gc)
1747            {
1748                update.set(&ancestor, flat_tree_selection);
1749            }
1750            for node in traversal {
1751                update.set(&node, flat_tree_selection);
1752            }
1753        }
1754
1755        update.finish(document);
1756    }
1757
1758    fn set(&mut self, node: &UnrootedDom<'no_gc, Node>, flat_tree_selection: &FlatTreeSelection) {
1759        if !node.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION) {
1760            node.set_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION, true);
1761            debug_assert!(!self.previously_flagged_nodes.contains(node));
1762        } else {
1763            self.previously_flagged_nodes.remove(node);
1764        }
1765
1766        // TODO: We should ensure that the style is up-to-date before reading the
1767        // `user-select` property and changes to `user-select` should trigger new visual
1768        // selection updates. Not doing this means that the calculations here are one
1769        // layout old and are never run again until the selection changes.
1770        let user_select = node.used_user_select(self.no_gc, &mut self.used_user_select_cache);
1771        let inhibited = user_select == UsedUserSelect::None;
1772        node.set_flag(NodeFlags::SELECTION_INHIBITED, inhibited);
1773
1774        self.set_node_selection(node, (!inhibited).then_some(flat_tree_selection));
1775    }
1776
1777    fn clear(&mut self, node: &Node) {
1778        node.set_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION, false);
1779        node.set_flag(NodeFlags::SELECTION_INHIBITED, false);
1780        self.set_node_selection(node, None);
1781    }
1782
1783    fn set_character_data_selection(
1784        &mut self,
1785        character_data: &CharacterData,
1786        range: Option<RangeAny<Utf32CodeUnits>>,
1787    ) {
1788        if character_data.set_text_run_selection(range) {
1789            self.needs_new_display_list = true;
1790        } else {
1791            // Note: This isn't just necessary when the `CharacterData` doesn't have a
1792            // corresponding box tree node, but *also* for properly handling `:first-letter`
1793            // at the moment. This cannot be removed until `:first-letter` is represented
1794            // properly in the box tree.
1795            character_data
1796                .upcast::<Node>()
1797                .dirty(self.no_gc, NodeDamage::ContentOrHeritage);
1798        }
1799    }
1800
1801    fn set_node_selection(&mut self, node: &Node, flat_tree_selection: Option<&FlatTreeSelection>) {
1802        if let Some(character_data) = node.downcast::<CharacterData>() {
1803            let range = flat_tree_selection.map(|flat_tree_selection| {
1804                flat_tree_selection.range_for_character_data(character_data)
1805            });
1806            self.set_character_data_selection(character_data, range);
1807        } else if node.set_element_selection(flat_tree_selection.is_some()) {
1808            self.needs_new_display_list = true;
1809        }
1810    }
1811
1812    fn finish(mut self, document: &Document) {
1813        for node in std::mem::take(&mut self.previously_flagged_nodes) {
1814            self.clear(&node);
1815        }
1816        if self.needs_new_display_list {
1817            document.window().layout().set_needs_new_display_list();
1818        }
1819    }
1820}