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