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::{RootedSelectionBoundary, 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<RootedSelectionBoundary> {
489        let range = self.range.borrow();
490        let range = range.as_ref()?;
491        let (container, offset) = 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        Some(RootedSelectionBoundary::new_with_utf16_offset(
496            container, offset,
497        ))
498    }
499
500    /// <https://w3c.github.io/selection-api/#dfn-anchor>
501    fn live_anchor_node(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
502        self.active_range(cx)
503            .map(|range| match self.direction.get() {
504                Direction::Forwards => range.start_container(),
505                _ => range.end_container(),
506            })
507    }
508
509    /// <https://w3c.github.io/selection-api/#dfn-anchor>
510    fn live_anchor_offset(&self, cx: &mut JSContext) -> u32 {
511        self.active_range(cx)
512            .map_or(0, |range| match self.direction.get() {
513                Direction::Forwards => range.start_offset(),
514                _ => range.end_offset(),
515            })
516    }
517
518    /// <https://w3c.github.io/selection-api/#dfn-focus>
519    fn live_focus_node(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
520        self.active_range(cx)
521            .map(|range| match self.direction.get() {
522                Direction::Forwards => range.end_container(),
523                _ => range.start_container(),
524            })
525    }
526
527    /// <https://w3c.github.io/selection-api/#dfn-focus>
528    fn live_focus_offset(&self, cx: &mut JSContext) -> u32 {
529        self.active_range(cx)
530            .map_or(0, |range| match self.direction.get() {
531                Direction::Forwards => range.end_offset(),
532                _ => range.start_offset(),
533            })
534    }
535
536    /// <https://dom.spec.whatwg.org/#concept-node-insert> steps 5.1-5.2
537    /// and
538    /// <https://dom.spec.whatwg.org/#move> steps 17.1-17.2
539    /// adapted for selections.
540    pub(crate) fn insert_steps(&self, parent: &Node, child: &Node, count: u32) {
541        let mut range_borrow = self.range.borrow_mut();
542        let Some(range) = &mut *range_borrow else {
543            return;
544        };
545        let child_index = LazyCell::new(|| child.index());
546        // Step 5.1: For each live range whose start node is parent and start offset is
547        // greater than child’s index: increase its start offset by count.
548        if range.start.container == parent && range.start.offset > *child_index {
549            range.start.offset += count;
550            self.selection_boundaries_changed();
551        }
552        // Step 5.2: For each live range whose end node is parent and end offset is
553        // greater than child’s index: increase its end offset by count.
554        if range.end.container == parent && range.end.offset > *child_index {
555            range.end.offset += count;
556            self.selection_boundaries_changed();
557        }
558    }
559
560    /// <https://dom.spec.whatwg.org/#live-range-pre-remove-steps> steps 4-7
561    /// adapted for selections.
562    pub(crate) fn pre_remove_steps(
563        &self,
564        removed_node: &Node,           // "node" in the specification
565        parent_of_removed_node: &Node, // "parent" in the specification
566        index_of_removed_node: &mut dyn FnMut() -> u32, // "index" in the specification
567    ) {
568        let mut range_borrow = self.range.borrow_mut();
569        let Some(range) = &mut *range_borrow else {
570            return;
571        };
572
573        // Step 4: For each live range whose start node is an inclusive descendant of
574        // node, set its start to (parent, index).
575        if removed_node.is_shadow_including_inclusive_ancestor_of(&range.start.container) {
576            range.start = SelectionBoundary::new(parent_of_removed_node, index_of_removed_node());
577            self.selection_boundaries_changed();
578        }
579        // Step 5: For each live range whose end node is an inclusive descendant of node,
580        // set its end to (parent, index).
581        if removed_node.is_shadow_including_inclusive_ancestor_of(&range.end.container) {
582            range.end = SelectionBoundary::new(parent_of_removed_node, index_of_removed_node());
583            self.selection_boundaries_changed();
584        }
585        // Step 6: For each live range whose start node is parent and start offset is
586        // greater than index, decrease its start offset by 1.
587        if range.start.container == parent_of_removed_node &&
588            range.start.offset > index_of_removed_node()
589        {
590            range.start.offset -= 1;
591            self.selection_boundaries_changed();
592        }
593        // Step 7: For each live range whose end node is parent and end offset is greater than
594        // index, decrease its end offset by 1.
595        if range.end.container == parent_of_removed_node &&
596            range.end.offset > index_of_removed_node()
597        {
598            range.end.offset -= 1;
599            self.selection_boundaries_changed();
600        }
601    }
602
603    /// <https://dom.spec.whatwg.org/#dom-node-normalize> Steps 6.1-6.4 adapted for selections.
604    ///
605    /// - `parent`: The parent of both other node arguments.
606    /// - `node`: The node that text is being merged into.
607    /// - `current_node`: The node which has text being merged into `node` and will be
608    ///   removed from the DOM.
609    /// - `length`: The length of the text content that was merged into `node` from
610    ///   siblings before `current_node`.
611    pub(crate) fn normalization_steps(
612        &self,
613        parent: &Node,
614        node: &Node,
615        current_node: &Node,
616        current_node_index: &dyn Fn() -> u32,
617        length: u32,
618    ) {
619        let mut range_borrow = self.range.borrow_mut();
620        let Some(range) = &mut *range_borrow else {
621            return;
622        };
623        // Step 6.1: For each live range whose start node is currentNode: add length to its start
624        // offset and set its start node to node.
625        if range.start.container == current_node {
626            range.start.offset += length;
627            range.start.container = Dom::from_ref(node);
628            self.selection_boundaries_changed();
629        }
630        // Step 6.2 For each live range whose end node is currentNode: add length to its end offset
631        // and set its end node to node.
632        if range.end.container == current_node {
633            range.end.offset += length;
634            range.end.container = Dom::from_ref(node);
635            self.selection_boundaries_changed();
636        }
637        // Step 6.3: For each live range whose start node is currentNode’s parent and start
638        // offset is currentNode’s index: set its start node to node and its start offset
639        // to length.
640        if range.start.container == parent && range.start.offset == current_node_index() {
641            range.start.container = Dom::from_ref(node);
642            range.start.offset = length;
643            self.selection_boundaries_changed();
644        }
645        // Step 6.4: For each live range whose end node is currentNode’s parent and end offset is
646        // currentNode’s index: set its end node to node and its end offset to length.
647        if range.end.container == parent && range.end.offset == current_node_index() {
648            range.end.container = Dom::from_ref(node);
649            range.end.offset = length;
650            self.selection_boundaries_changed();
651        }
652    }
653
654    /// <https://dom.spec.whatwg.org/#concept-cd-replace> steps 8-11
655    /// adapted for selections.
656    pub(crate) fn replace_data_steps(
657        &self,
658        node: &Node,
659        offset: u32,
660        removed_code_units: u32,
661        added_code_units: &mut dyn FnMut() -> u32,
662    ) {
663        let mut range_borrow = self.range.borrow_mut();
664        let Some(range) = &mut *range_borrow else {
665            return;
666        };
667        // Step 8: For each live range whose start node is node and start offset is
668        // greater than offset but less than or equal to offset + count: set its start
669        // offset to offset.
670        let start_container = &range.start.container;
671        let start_offset = range.start.offset;
672        if &**start_container == node &&
673            start_offset > offset &&
674            start_offset <= offset + removed_code_units
675        {
676            range.start.offset = offset;
677            self.selection_boundaries_changed();
678        }
679        // Step 9: For each live range whose end node is node and end offset is
680        // greater than offset but less than or equal to offset + count: set its end
681        // offset to offset.
682        let end_container = &range.end.container;
683        let end_offset = range.end.offset;
684        if &**end_container == node &&
685            end_offset > offset &&
686            end_offset <= offset + removed_code_units
687        {
688            range.end.offset = offset;
689            self.selection_boundaries_changed();
690        }
691        // Step 10: For each live range whose start node is node and start offset is
692        // greater than offset + count: increase its start offset by data’s length and
693        // decrease it by count.
694        if &**start_container == node && start_offset > offset + removed_code_units {
695            range.start.offset = start_offset + added_code_units() - removed_code_units;
696            self.selection_boundaries_changed();
697        }
698        // Step 11: For each live range whose end node is node and end offset is
699        // greater than offset + count: increase its end offset by data’s length and
700        // decrease it by count.
701        if &**end_container == node && end_offset > offset + removed_code_units {
702            range.end.offset = end_offset + added_code_units() - removed_code_units;
703            self.selection_boundaries_changed();
704        }
705    }
706
707    /// <https://dom.spec.whatwg.org/#concept-text-split> steps 7.2-7.3
708    /// adapted for selections.
709    pub(crate) fn text_split_steps(
710        &self,
711        node: &Node,
712        offset: u32,
713        parent_node: &Node,
714        new_node: &Node,
715    ) {
716        let mut range_borrow = self.range.borrow_mut();
717        let Some(range) = &mut *range_borrow else {
718            return;
719        };
720        // Step 7.2: For each live range whose start node is node and start offset is
721        // greater than offset, set its start node to newNode and decrease its start
722        // offset by offset.
723        if range.start.container == node && range.start.offset > offset {
724            range.start.container = Dom::from_ref(new_node);
725            range.start.offset -= offset;
726            self.selection_boundaries_changed();
727        }
728        // Step 7.3: For each live range whose end node is node and end offset is greater
729        // than offset, set its end node to newNode and decrease its end offset by offset.
730        if range.end.container == node && range.end.offset > offset {
731            range.end.container = Dom::from_ref(new_node);
732            range.end.offset -= offset;
733            self.selection_boundaries_changed();
734        }
735        // Step 7.4: For each live range whose start node is parent and start offset is
736        // equal to the index of node plus 1, increase its start offset by 1.
737        let node_index = LazyCell::new(|| node.index());
738        if range.start.container == parent_node && range.start.offset == *node_index + 1 {
739            range.start.offset += 1;
740            self.selection_boundaries_changed();
741        }
742        // Step 7.5: For each live range whose end node is parent and end offset is equal
743        // to the index of node plus 1, increase its end offset by 1.
744        if range.end.container == parent_node && range.end.offset == *node_index + 1 {
745            range.end.offset += 1;
746            self.selection_boundaries_changed();
747        }
748    }
749
750    pub(crate) fn collapse_to_dom_position(
751        &self,
752        cx: &mut JSContext,
753        boundary: &RootedSelectionBoundary,
754    ) {
755        let _ = self.Collapse(cx, Some(&boundary.container), boundary.offset);
756    }
757
758    pub(crate) fn collapse_or_extend_to_dom_position(
759        &self,
760        cx: &mut JSContext,
761        boundary: &RootedSelectionBoundary,
762    ) {
763        let is_anchor = self
764            .composed_anchor_position()
765            .is_some_and(|anchor| anchor == *boundary);
766
767        if self.range.borrow().is_none() || is_anchor {
768            let _ = self.Collapse(cx, Some(&boundary.container), boundary.offset);
769        } else {
770            let _ = self.Extend(cx, &boundary.container, boundary.offset);
771        }
772    }
773
774    /// Call [`Selection::SetBaseAndExtent`] with the given `anchor` and `focus`
775    /// arguments. Like `SetBaseAndExtent` the selection direction is determined by the
776    /// order of `anchor` and `focus` in the DOM.
777    pub(crate) fn set_anchor_and_focus(
778        &self,
779        cx: &mut JSContext,
780        anchor: &RootedSelectionBoundary,
781        focus: &RootedSelectionBoundary,
782    ) {
783        let _ = self.SetBaseAndExtent(
784            cx,
785            &anchor.container,
786            anchor.offset,
787            &focus.container,
788            focus.offset,
789        );
790    }
791}
792
793impl SelectionMethods<crate::DomTypeHolder> for Selection {
794    /// <https://w3c.github.io/selection-api/#dom-selection-anchornode>
795    fn GetAnchorNode(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
796        // > The attribute must return the anchor node of this, or null if the anchor is
797        // > null or anchor is not in the document tree.
798        //
799        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
800        // do the document tree check (other browsers don't either).
801        self.live_anchor_node(cx)
802    }
803
804    /// <https://w3c.github.io/selection-api/#dom-selection-anchoroffset>
805    fn AnchorOffset(&self, cx: &mut JSContext) -> u32 {
806        // > The attribute must return the anchor offset of this, or 0 if the anchor is null
807        // > or anchor is not in the document tree.
808        //
809        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
810        // do the document tree check (other browsers don't either).
811        self.live_anchor_offset(cx)
812    }
813
814    /// <https://w3c.github.io/selection-api/#dom-selection-focusnode>
815    fn GetFocusNode(&self, cx: &mut JSContext) -> Option<DomRoot<Node>> {
816        // > The attribute must return the focus node of this, or null if the focus is
817        // > null or focus is not in the document tree.
818        //
819        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
820        // do the document tree check (other browsers don't either).
821        self.live_focus_node(cx)
822    }
823
824    /// <https://w3c.github.io/selection-api/#dom-selection-focusoffset>
825    fn FocusOffset(&self, cx: &mut JSContext) -> u32 {
826        // > The attribute must return the focus offset of this, or 0 if the focus is null
827        // > or focus is not in the document tree.
828        //
829        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
830        // do the document tree check (other browsers don't either).
831        self.live_focus_offset(cx)
832    }
833
834    /// <https://w3c.github.io/selection-api/#dom-selection-iscollapsed>
835    fn IsCollapsed(&self, cx: &mut JSContext) -> bool {
836        // > The attribute must return true if and only if the anchor and focus are the
837        // > same (including if both are null). Otherwise it must return false.
838        self.active_range(cx).is_none_or(|range| range.collapsed())
839    }
840
841    /// <https://w3c.github.io/selection-api/#dom-selection-rangecount>
842    fn RangeCount(&self) -> u32 {
843        // > The attribute must return 0 if this is empty or either focus or anchor is not
844        // > in the document tree, and must return 1 otherwise.
845        //
846        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
847        // do the document tree check (other browsers don't either).
848        if self.range.borrow().is_none() {
849            return 0;
850        }
851        1
852    }
853
854    /// <https://w3c.github.io/selection-api/#dom-selection-type>
855    fn Type(&self) -> DOMString {
856        // > The attribute must return "None" if this is empty or either focus or anchor
857        // > is not in the document tree, "Caret" if this's range is collapsed, and "Range"
858        // > otherwise.
859        //
860        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
861        // do the document tree check (other browsers don't either).
862        let range = self.range.borrow();
863        let Some(range) = range.as_ref() else {
864            return DOMString::from_static("None");
865        };
866        if range.collapsed() {
867            DOMString::from_static("Caret")
868        } else {
869            DOMString::from_static("Range")
870        }
871    }
872
873    /// <https://w3c.github.io/selection-api/#dom-selection-direction>
874    fn Direction(&self) -> DOMString {
875        // > The attribute must return "none" if this is empty or this selection is
876        // > directionless. "forward" if this selection's direction is forwards and
877        // > "backward" if this selection's direction is backwards.
878        match self.direction.get() {
879            Direction::Directionless => DOMString::from_static("none"),
880            Direction::Forwards => DOMString::from_static("forward"),
881            Direction::Backwards => DOMString::from_static("backward"),
882        }
883    }
884
885    /// <https://w3c.github.io/selection-api/#dom-selection-getrangeat>
886    fn GetRangeAt(&self, cx: &mut JSContext, index: u32) -> Fallible<DomRoot<Range>> {
887        // > The method must throw an IndexSizeError exception if index is not 0, or if this
888        // > is empty or either focus or anchor is not in the document tree. Otherwise, it
889        // > must return a reference to (not a copy of) this's range.
890        //
891        // See <https://github.com/w3c/selection-api/issues/361> for why we don't
892        // do the document tree check (other browsers don't either).
893        if index != 0 {
894            return Err(Error::IndexSize(Some("Index must be zero".into())));
895        }
896
897        let Some(range) = self.active_range(cx) else {
898            return Err(Error::IndexSize(Some("Selection is empty".into())));
899        };
900
901        Ok(range)
902    }
903
904    /// <https://w3c.github.io/selection-api/#dom-selection-addrange>
905    fn AddRange(&self, no_gc: &NoGC, range: &Range) {
906        // Step 1. If the root of the range's boundary points are not the document
907        // associated with this, abort these steps.
908        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(&range.start_container()) {
909            return;
910        }
911
912        // Step 2. If rangeCount is not 0, abort these steps.
913        if self.RangeCount() != 0 {
914            return;
915        }
916
917        // Step 3. Set this's range to range by a strong reference (not by making a copy).
918        self.set_live_range(no_gc, Some(range));
919
920        // Are we supposed to set Direction here? w3c/selection-api#116
921        self.direction.set(Direction::Forwards);
922    }
923
924    /// <https://w3c.github.io/selection-api/#dom-selection-removerange>
925    fn RemoveRange(&self, no_gc: &NoGC, range: &Range) -> ErrorResult {
926        // > The method must make this empty by disassociating its range if this's range
927        // > is range. Otherwise, it must throw a NotFoundError.
928        if let Some(own_range) = self.live_range.get() &&
929            &*own_range == range
930        {
931            self.set_range(no_gc, None);
932            return Ok(());
933        }
934        Err(Error::NotFound(None))
935    }
936
937    /// <https://w3c.github.io/selection-api/#dom-selection-removeallranges>
938    fn RemoveAllRanges(&self, no_gc: &NoGC) {
939        // > The method must make this empty by disassociating its range if this has an
940        // > associated range.
941        self.set_range(no_gc, None);
942    }
943
944    /// <https://w3c.github.io/selection-api/#dom-selection-empty>
945    fn Empty(&self, no_gc: &NoGC) {
946        // > The method must be an alias, and behave identically, to removeAllRanges().
947        self.RemoveAllRanges(no_gc);
948    }
949
950    /// <https://w3c.github.io/selection-api/#dom-selection-getcomposedranges>
951    fn GetComposedRanges(
952        &self,
953        cx: &mut JSContext,
954        options: &GetComposedRangesOptions,
955    ) -> Vec<DomRoot<StaticRange>> {
956        // Step 1. If this is empty, return an empty array.
957        let borrowed_range = self.range.borrow();
958        let Some(range) = borrowed_range.as_ref() else {
959            return Vec::new();
960        };
961
962        // Step 2. Otherwise, let startNode be start node of the range associated with
963        // this, and let startOffset be start offset of the range.
964        let mut start_node = range.start.container.as_rooted();
965        let mut start_offset = range.start.offset;
966
967        let is_ancestor_of_provided_shadow_roots = |shadow_root: &ShadowRoot| {
968            let shadow_root_node = shadow_root.upcast::<Node>();
969            options.shadowRoots.iter().any(|option_shadow_root| {
970                shadow_root_node
971                    .is_shadow_including_inclusive_ancestor_of(option_shadow_root.upcast())
972            })
973        };
974
975        // Step 3. While startNode is a node, startNode's root is a shadow root, and
976        // startNode's root is not a shadow-including inclusive ancestor of any of
977        // options["shadowRoots"], repeat these steps:
978        while let Some(containing_shadow_root) = start_node.containing_shadow_root() &&
979            !is_ancestor_of_provided_shadow_roots(&containing_shadow_root)
980        {
981            // Step 3.1. Set startOffset to index of startNode's root's host.
982            let host = DomRoot::upcast::<Node>(containing_shadow_root.Host());
983            start_offset = host.index();
984
985            // Step 3.2. Set startNode to startNode's root's host's parent.
986            // See <https://github.com/w3c/selection-api/issues/161> for why
987            // we always know that the start_node is a node.
988            let Some(new_start_node) = host.GetParentNode() else {
989                return Vec::new();
990            };
991            start_node = new_start_node;
992        }
993
994        // Step 4. Let endNode be end node of the range associated with this, and let
995        // endOffset be end offset of the range.
996        let mut end_node = range.end.container.as_rooted();
997        let mut end_offset = range.end.offset;
998
999        // Step 5. While endNode is a node, endNode's root is a shadow root, and endNode's
1000        // root is not a shadow-including inclusive ancestor of any of
1001        // options["shadowRoots"], repeat these steps:
1002        while let Some(containing_shadow_root) = end_node.containing_shadow_root() &&
1003            !is_ancestor_of_provided_shadow_roots(&containing_shadow_root)
1004        {
1005            // Step 5.1. Set endOffset to index of endNode's root's host plus 1.
1006            let host = DomRoot::upcast::<Node>(containing_shadow_root.Host());
1007            end_offset = host.index() + 1;
1008
1009            // Step 5.2. Set endNode to endNode's root's host's parent.
1010            // See <https://github.com/w3c/selection-api/issues/161> for why
1011            // we always know that the end_node is a node.
1012            let Some(new_end_node) = host.GetParentNode() else {
1013                return Vec::new();
1014            };
1015            end_node = new_end_node;
1016        }
1017
1018        drop(borrowed_range);
1019
1020        // Step 6. Return an array consisting of new StaticRange whose start node is
1021        // startNode, start offset is startOffset, end node is endNode, and end offset is
1022        // endOffset.
1023        vec![DomRoot::from_ref(&StaticRange::new(
1024            cx,
1025            &self.document,
1026            &start_node,
1027            start_offset,
1028            &end_node,
1029            end_offset,
1030        ))]
1031    }
1032
1033    /// <https://w3c.github.io/selection-api/#dom-selection-collapse>
1034    fn Collapse(&self, cx: &mut JSContext, node: Option<&Node>, offset: u32) -> ErrorResult {
1035        // Step 1. If node is null, this method must behave identically as
1036        // removeAllRanges() and abort these steps.
1037        let Some(node) = node else {
1038            self.set_range(cx.no_gc(), None);
1039            return Ok(());
1040        };
1041
1042        // Step 2. If node is a DocumentType, throw an InvalidNodeTypeError exception and
1043        // abort these steps.
1044        if node.is_doctype() {
1045            return Err(Error::InvalidNodeType(None));
1046        }
1047
1048        // Step 3. The method must throw an IndexSizeError exception if offset is longer
1049        // than node's length and abort these steps.
1050        if offset > node.len() {
1051            return Err(Error::IndexSize(None));
1052        }
1053
1054        // Step 4. If document associated with this is not a shadow-including inclusive
1055        // ancestor of node, abort these steps.
1056        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1057            return Ok(());
1058        }
1059
1060        // Step 5. Otherwise, let newRange be a new range.
1061        // Step 6. Set the start the start and the end of newRange to (node, offset).
1062        // Step 7. Set this's range to newRange.
1063        self.set_range(
1064            cx.no_gc(),
1065            Some(SelectionRange::collapsed_at(SelectionBoundary::new(
1066                node, offset,
1067            ))),
1068        );
1069
1070        // Are we supposed to set Direction here? w3c/selection-api#116
1071        self.direction.set(Direction::Forwards);
1072
1073        Ok(())
1074    }
1075
1076    /// <https://w3c.github.io/selection-api/#dom-selection-setposition>
1077    fn SetPosition(&self, cx: &mut JSContext, node: Option<&Node>, offset: u32) -> ErrorResult {
1078        // > The method must be an alias, and behave identically, to collapse().
1079        self.Collapse(cx, node, offset)
1080    }
1081
1082    /// <https://w3c.github.io/selection-api/#dom-selection-collapsetostart>
1083    fn CollapseToStart(&self, cx: &mut JSContext) -> ErrorResult {
1084        // > The method must throw InvalidStateError exception if the this is empty.
1085        // > Otherwise, it must create a new range, set the start both its start and end to
1086        // > the start of this's range, and then set this's range to the newly-created
1087        // > range.
1088        let Some((start_container, start_offset)) = self
1089            .range
1090            .borrow()
1091            .as_ref()
1092            .map(|range| (range.start.container.as_rooted(), range.start.offset))
1093        else {
1094            return Err(Error::InvalidState(None));
1095        };
1096        self.Collapse(cx, Some(&*start_container), start_offset)
1097    }
1098
1099    /// <https://w3c.github.io/selection-api/#dom-selection-collapsetoend>
1100    fn CollapseToEnd(&self, cx: &mut JSContext) -> ErrorResult {
1101        // > The method must throw InvalidStateError exception if the this is empty.
1102        // > Otherwise, it must create a new range, set the start both its start and end to
1103        // > the end of this's range, and then set this's range to the newly-created range.
1104        let Some((end_container, end_offset)) = self
1105            .range
1106            .borrow()
1107            .as_ref()
1108            .map(|range| (range.end.container.as_rooted(), range.end.offset))
1109        else {
1110            return Err(Error::InvalidState(None));
1111        };
1112        self.Collapse(cx, Some(&*end_container), end_offset)
1113    }
1114
1115    /// <https://w3c.github.io/selection-api/#dom-selection-extend>
1116    fn Extend(&self, cx: &mut JSContext, node: &Node, offset: u32) -> ErrorResult {
1117        // Step 1. If the document associated with this is not a shadow-including
1118        // inclusive ancestor of node, abort these steps.
1119        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1120            return Ok(());
1121        }
1122
1123        // Step 2. If this is empty, throw an InvalidStateError exception and abort these steps.
1124        let range_borrow = self.range.borrow();
1125        let Some(range) = range_borrow.as_ref() else {
1126            return Err(Error::InvalidState(None));
1127        };
1128
1129        // This isn't specified, but it appears to be implementation behavior of other
1130        // browsers. See w3c/selection-api#118.
1131        if node.is_doctype() {
1132            return Err(Error::InvalidNodeType(None));
1133        }
1134
1135        // As with is_doctype, this is not explicit in the selection specification steps
1136        // here but implied by which exceptions are thrown in WPT tests.
1137        if offset > node.len() {
1138            return Err(Error::IndexSize(None));
1139        }
1140
1141        // Step 3. Let oldAnchor and oldFocus be the this's anchor and focus, and let
1142        // newFocus be the boundary point (node, offset).
1143        //
1144        // Note: oldFocus is unused, so we do not set it here.
1145        let old_anchor = self
1146            .composed_anchor_position()
1147            .expect("has range, therefore has anchor node");
1148
1149        // Step 4. Let newRange be a new range.
1150        // Note: Set directly to satisfy crown.
1151        let direction;
1152
1153        // Step 5. If node's root is not the same as the this's range's root, set the
1154        // start newRange's start and end to newFocus.
1155        let in_different_roots = !nodes_have_same_shadow_root(&range.start.container, node);
1156        drop(range_borrow);
1157
1158        if in_different_roots {
1159            self.set_range(
1160                cx.no_gc(),
1161                Some(SelectionRange::collapsed_at(SelectionBoundary::new(
1162                    node, offset,
1163                ))),
1164            );
1165            direction = Direction::Forwards;
1166        } else {
1167            let is_old_anchor_before_or_equal = matches!(
1168                compare_shadow_including_dom_positions(
1169                    cx.no_gc(),
1170                    &old_anchor.container,
1171                    old_anchor.offset,
1172                    node,
1173                    offset
1174                ),
1175                Ordering::Less | Ordering::Equal
1176            );
1177            if is_old_anchor_before_or_equal {
1178                // Step 6. Otherwise, if oldAnchor is before or equal to newFocus, set the start
1179                // newRange's start to oldAnchor, then set its end to newFocus.
1180                self.set_range(
1181                    cx.no_gc(),
1182                    Some(SelectionRange::new(
1183                        SelectionBoundary::new(&old_anchor.container, old_anchor.offset),
1184                        SelectionBoundary::new(node, offset),
1185                    )),
1186                );
1187                direction = Direction::Forwards;
1188            } else {
1189                // Step 7. Otherwise, set the start newRange's start to newFocus, then set
1190                // its end to oldAnchor.
1191                self.set_range(
1192                    cx.no_gc(),
1193                    Some(SelectionRange::new(
1194                        SelectionBoundary::new(node, offset),
1195                        SelectionBoundary::new(&old_anchor.container, old_anchor.offset),
1196                    )),
1197                );
1198                direction = Direction::Backwards;
1199            }
1200        }
1201
1202        // Step 8. Set this's range to newRange.
1203        // Note: Done above to satisfy crown.
1204
1205        // Step 9. If newFocus is before oldAnchor, set this's direction to backwards.
1206        // Otherwise, set it to forwards.
1207        self.direction.set(direction);
1208
1209        Ok(())
1210    }
1211
1212    /// <https://w3c.github.io/selection-api/#dom-selection-setbaseandextent>
1213    fn SetBaseAndExtent(
1214        &self,
1215        cx: &mut JSContext,
1216        anchor_node: &Node,
1217        anchor_offset: u32,
1218        focus_node: &Node,
1219        focus_offset: u32,
1220    ) -> ErrorResult {
1221        // This isn't specified, but it appears to be implementation behavior of other
1222        // browsers. See w3c/selection-api#118.
1223        if anchor_node.is_doctype() || focus_node.is_doctype() {
1224            return Err(Error::InvalidNodeType(None));
1225        }
1226
1227        // Step 1. If anchorOffset is longer than anchorNode's length or if focusOffset is
1228        // longer than focusNode's length, throw an IndexSizeError exception and abort
1229        // these steps.
1230        if anchor_offset > anchor_node.len() || focus_offset > focus_node.len() {
1231            return Err(Error::IndexSize(None));
1232        }
1233
1234        // Step 2. If document associated with this is not a shadow-including inclusive
1235        // ancestor of anchorNode or focusNode, abort these steps.
1236        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(anchor_node) ||
1237            !self.is_in_composed_tree_of_document_and_is_not_ua_widget(focus_node)
1238        {
1239            return Ok(());
1240        }
1241
1242        // Step 3. Let anchor be the boundary point (anchorNode, anchorOffset) and let
1243        // focus be the boundary point (focusNode, focusOffset).
1244        //
1245        // Note: We do not model the boundary point in this way.
1246
1247        // Step 4. Let newRange be a new range.
1248        // Note: We set the range directly to satisfy crown.
1249
1250        // Step 5. If anchor is before focus, set the start the newRange's start to anchor
1251        // and its end to focus. Otherwise, set the start them to focus and anchor
1252        // respectively.
1253        let ordering = compare_shadow_including_dom_positions(
1254            cx.no_gc(),
1255            anchor_node,
1256            anchor_offset,
1257            focus_node,
1258            focus_offset,
1259        );
1260        if ordering == Ordering::Less {
1261            self.set_range(
1262                cx.no_gc(),
1263                Some(SelectionRange::new(
1264                    SelectionBoundary::new(anchor_node, anchor_offset),
1265                    SelectionBoundary::new(focus_node, focus_offset),
1266                )),
1267            );
1268        } else {
1269            self.set_range(
1270                cx.no_gc(),
1271                Some(SelectionRange::new(
1272                    SelectionBoundary::new(focus_node, focus_offset),
1273                    SelectionBoundary::new(anchor_node, anchor_offset),
1274                )),
1275            );
1276        };
1277
1278        // Step 6. Set this's range to newRange.
1279        // Note: Done above to satisfy crown.
1280
1281        // Step 7. If focus is before anchor, set this's direction to backwards.
1282        // Otherwise, set it to forwards
1283        if ordering == Ordering::Greater {
1284            self.direction.set(Direction::Backwards);
1285        } else {
1286            self.direction.set(Direction::Forwards);
1287        }
1288
1289        Ok(())
1290    }
1291
1292    /// <https://w3c.github.io/selection-api/#dom-selection-selectallchildren>
1293    fn SelectAllChildren(&self, cx: &mut JSContext, node: &Node) -> ErrorResult {
1294        // Step 1. If node is a DocumentType, throw an InvalidNodeTypeError exception and
1295        // abort these steps.
1296        if node.is_doctype() {
1297            return Err(Error::InvalidNodeType(None));
1298        }
1299
1300        // Step 2. If node's root is not the document associated with this, abort these
1301        // steps.
1302        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1303            return Ok(());
1304        }
1305
1306        // Let newRange be a new range and childCount be the number of children of node.
1307        let child_count = node.children_count();
1308
1309        // Step 4. Set newRange's start to (node, 0).
1310        // Step 5. Set newRange's end to (node, childCount).
1311        // Step 6. Set this's range to newRange.
1312        self.set_range(
1313            cx.no_gc(),
1314            Some(SelectionRange::new(
1315                SelectionBoundary::new(node, 0),
1316                SelectionBoundary::new(node, child_count),
1317            )),
1318        );
1319
1320        // Step 7. Set this's direction to forwards.
1321        self.direction.set(Direction::Forwards);
1322
1323        Ok(())
1324    }
1325
1326    /// <https://w3c.github.io/selection-api/#dom-selection-deletecontents>
1327    fn DeleteFromDocument(&self, cx: &mut JSContext) -> ErrorResult {
1328        // > The method must invoke deleteContents() on this's range if this is not empty
1329        // > and both focus and anchor are in the document tree. Otherwise the method must
1330        // > do nothing.
1331        let Some(active_range) = self
1332            .active_range(cx)
1333            .filter(|range| range.start_and_end_are_in_document_tree())
1334        else {
1335            return Ok(());
1336        };
1337
1338        active_range.DeleteContents(cx)
1339    }
1340
1341    /// <https://w3c.github.io/selection-api/#dom-selection-containsnode>
1342    fn ContainsNode(&self, no_gc: &NoGC, node: &Node, allow_partial_containment: bool) -> bool {
1343        // > The method must return false if this is empty or if node's root is not the document
1344        // > associated with this.
1345        // >
1346        // > Otherwise, if allowPartialContainment is false, the method must return true if and only
1347        // > if start of its range is before or visually equivalent to the first boundary point in
1348        // > the node *and* end of its range is after or visually equivalent to the last boundary
1349        // > point in the node.
1350        // >
1351        // > If allowPartialContainment is true, the method must return true if and only if start of
1352        // > its range is before or visually equivalent to the last boundary point in the node *and*
1353        // > end of its range is after or visually equivalent to the first boundary point in the
1354        // > node.
1355        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(node) {
1356            return false;
1357        }
1358        let range = self.range.borrow();
1359        let Some(range) = range.as_ref() else {
1360            return false;
1361        };
1362        let start_node = &*range.start.container;
1363        if !self.is_in_composed_tree_of_document_and_is_not_ua_widget(start_node) {
1364            return false;
1365        }
1366        let end_node = &*range.end.container;
1367
1368        let first_offset = 0;
1369        let last_offset = node.len();
1370        let (compare_start_to, compare_end_to) = if allow_partial_containment {
1371            (last_offset, first_offset)
1372        } else {
1373            (first_offset, last_offset)
1374        };
1375
1376        // TODO: find out what "visually equivalent" means for boundary points and implement it.
1377        // https://github.com/w3c/selection-api/issues/6
1378        //
1379        // For now it is simplified to "node is in the flat tree" and "position is equal".
1380        if !node.is_in_flat_tree(no_gc) {
1381            return false;
1382        }
1383
1384        matches!(
1385            compare_shadow_including_dom_positions(
1386                no_gc,
1387                start_node,
1388                range.start.offset,
1389                node,
1390                compare_start_to
1391            ),
1392            Ordering::Less | Ordering::Equal
1393        ) && matches!(
1394            compare_shadow_including_dom_positions(
1395                no_gc,
1396                end_node,
1397                range.end.offset,
1398                node,
1399                compare_end_to
1400            ),
1401            Ordering::Greater | Ordering::Equal
1402        )
1403    }
1404
1405    /// <https://w3c.github.io/selection-api/#dom-selection-stringifier>
1406    fn Stringifier(&self, cx: &mut JSContext) -> DOMString {
1407        // > The stringification must return the string, which is the concatenation of the
1408        // > rendered text if there is a range associated with this.
1409        // >
1410        // > If the selection is within a textarea or input element, it must return the
1411        // > selected substring in its value.
1412        let Some(visible_selection) =
1413            FlatTreeSelection::from_selection_if_renderable(cx.no_gc(), self)
1414        else {
1415            return DOMString::new();
1416        };
1417
1418        // Flush all layout before stringifying so that rendered text is up-to-date.
1419        self.document.window().layout_reflow(QueryMsg::StyleQuery);
1420
1421        let mut user_select_cache = Default::default();
1422        let mut string = DOMString::new();
1423        for node in visible_selection.traversal() {
1424            let Some(character_data) = node.downcast::<CharacterData>() else {
1425                continue;
1426            };
1427
1428            if node.used_user_select(cx.no_gc(), &mut user_select_cache) == UsedUserSelect::None {
1429                continue;
1430            }
1431
1432            let range = visible_selection.range_for_character_data(character_data);
1433            let Some(text) = character_data.rendered_text(range) else {
1434                continue;
1435            };
1436            string.push_str(&text);
1437        }
1438
1439        string
1440    }
1441}
1442
1443impl<'dom> LayoutDom<'dom, Selection> {
1444    #[expect(unsafe_code)]
1445    pub(crate) fn range_for_layout(&self) -> &Option<SelectionRange> {
1446        unsafe { self.unsafe_get().visible_range.borrow_for_layout() }
1447    }
1448}
1449
1450enum FlatTreeNodePosition {
1451    Before(DomRoot<Node>),
1452    Inside(DomRoot<Node>),
1453    After(DomRoot<Node>),
1454}
1455
1456impl FlatTreeNodePosition {
1457    fn node(&self) -> &Node {
1458        match self {
1459            FlatTreeNodePosition::Before(node) => node,
1460            FlatTreeNodePosition::Inside(node) => node,
1461            FlatTreeNodePosition::After(node) => node,
1462        }
1463    }
1464}
1465
1466/// Find the position of a node and offset in the flat tree for the purposes of selection
1467/// boundaries. This projects the given position onto the flat tree, accounting for origin
1468/// nodes that may not actually be in the flat tree at all.
1469fn position_in_flat_tree_for_selection(
1470    no_gc: &NoGC,
1471    boundary: &FlatTreeBoundary,
1472) -> FlatTreeNodePosition {
1473    if boundary.container.is::<CharacterData>() {
1474        return FlatTreeNodePosition::Inside(boundary.container.as_rooted());
1475    }
1476
1477    let shadow_host_or_node = |node: &Node| {
1478        boundary
1479            .container
1480            .downcast::<ShadowRoot>()
1481            .map(|shadow_root| DomRoot::upcast(shadow_root.Host()))
1482            .unwrap_or(DomRoot::from_ref(node))
1483    };
1484
1485    if let Some(child) = boundary.container.children().nth(boundary.offset as usize) {
1486        if let FlatTreeParent::Parent(_) = child.parent_in_flat_tree(no_gc) {
1487            return FlatTreeNodePosition::Before(child);
1488        }
1489    } else if let Some(last_child) = boundary.container.GetLastChild() &&
1490        let FlatTreeParent::Parent(_) = last_child.parent_in_flat_tree(no_gc)
1491    {
1492        return FlatTreeNodePosition::After(shadow_host_or_node(&boundary.container.as_rooted()));
1493    }
1494
1495    // The container has no child in the flat tree or the child indicated by the index
1496    // isn't in the flat tree, so just return a position inside that container.
1497    FlatTreeNodePosition::Inside(shadow_host_or_node(&boundary.container.as_rooted()))
1498}
1499
1500impl Node {
1501    /// Get the `Utf16CodeUnits` offset for the given offset if `self` is a
1502    /// `CharacterData` or else return the offset in the child list.
1503    pub(crate) fn to_sibling_or_utf16_offset(&self, offset: Utf32CodeUnitsOrNodeOffset) -> u32 {
1504        if let Some(character_data) = self.downcast::<CharacterData>() {
1505            // TODO: ensure that each `CharacterData` holds no more than 4 GiB?
1506            offset
1507                .to_utf16_code_units_in(AssumeUnder4GB, &character_data.data())
1508                .0
1509        } else {
1510            offset.0
1511        }
1512    }
1513
1514    /// Get the `Utf32CodeUnitsOrNodeOffset` for `self` and the given UTF-16 offset if
1515    /// `self` is `CharacterData` or else return the offset in the child list.
1516    pub(crate) fn to_sibling_or_utf32_offset(&self, offset: u32) -> Utf32CodeUnitsOrNodeOffset {
1517        Utf32CodeUnitsOrNodeOffset(
1518            if let Some(character_data) = self.downcast::<CharacterData>() {
1519                // TODO: ensure that each `CharacterData` holds no more than 4 GiB?
1520                Utf16CodeUnits(offset)
1521                    .to_utf32_code_units_in(&character_data.data())
1522                    .0
1523            } else {
1524                offset
1525            },
1526        )
1527    }
1528}
1529
1530bitflags! {
1531    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1532    pub(crate) struct SelectionLiveRangeNotification: u8 {
1533        const Start = 1 << 0;
1534        const End = 1 << 1;
1535    }
1536}
1537
1538fn nodes_have_same_shadow_root(a: &Node, b: &Node) -> bool {
1539    a.is_connected() && b.is_connected() && a.containing_shadow_root() == b.containing_shadow_root()
1540}
1541
1542/// Project the position given by `container_a` and `offset_a` into a root that also
1543/// contains `target`. If `target` is not supplied then project the position into the
1544/// `Document` root.
1545///
1546/// This does something very similar to GetComposedRanges, but doesn't have special
1547/// handling for start and end nodes.
1548fn project_into_shared_tree<'a>(
1549    no_gc: &'a NoGC,
1550    container_a: &Node,
1551    mut offset_a: u32,
1552    target: Option<&Node>,
1553) -> (UnrootedDom<'a, Node>, u32) {
1554    let mut start_node = UnrootedDom::from_ref(container_a, no_gc);
1555    while let Some(containing_shadow_root) = start_node.containing_shadow_root_unrooted(no_gc) &&
1556        target.is_none_or(|target| {
1557            !containing_shadow_root
1558                .upcast::<Node>()
1559                .is_shadow_including_inclusive_ancestor_of(target)
1560        })
1561    {
1562        let host = DomRoot::upcast::<Node>(containing_shadow_root.Host());
1563        offset_a = host.index();
1564        start_node = host
1565            .get_parent_node_unrooted(no_gc)
1566            .expect("Should only be called on nodes in the same tree");
1567    }
1568
1569    (start_node, offset_a)
1570}
1571
1572/// Compare two shadow-including DOM positions by first projecting them into
1573/// a shared root and then comparing the two positions. This function assumes
1574/// that the two positions share a [`Document`].
1575pub(crate) fn compare_shadow_including_dom_positions(
1576    no_gc: &NoGC,
1577    container_a: &Node,
1578    offset_a: u32,
1579    container_b: &Node,
1580    offset_b: u32,
1581) -> Ordering {
1582    let (projected_container_a, projected_offset_a) =
1583        project_into_shared_tree(no_gc, container_a, offset_a, Some(container_b));
1584    let (projected_container_b, projected_offset_b) =
1585        project_into_shared_tree(no_gc, container_b, offset_b, Some(container_a));
1586
1587    let a_was_projected = &**projected_container_a != container_a;
1588    let b_was_projected = &**projected_container_b != container_b;
1589
1590    match bp_position(
1591        no_gc,
1592        &projected_container_a,
1593        projected_offset_a,
1594        &projected_container_b,
1595        projected_offset_b,
1596    ) {
1597        Ordering::Equal if a_was_projected && !b_was_projected => Ordering::Greater,
1598        Ordering::Equal if !a_was_projected && b_was_projected => Ordering::Less,
1599        ordering => ordering,
1600    }
1601}
1602
1603struct FlatTreeBoundary<'no_gc> {
1604    container: UnrootedDom<'no_gc, Node>,
1605    offset: u32,
1606}
1607
1608struct FlatTreeSelection<'no_gc> {
1609    no_gc: &'no_gc NoGC,
1610    start: FlatTreeBoundary<'no_gc>,
1611    end: FlatTreeBoundary<'no_gc>,
1612}
1613
1614impl<'no_gc> FlatTreeSelection<'no_gc> {
1615    /// Get the [`FlatTreeSelection`] for this [`Selection`], returning `None` if the [`Selection`]
1616    /// doesn't have a range or if that flat tree range is unrenderable (for instance, because one
1617    /// or both of the endpoints is not in the flat tree).
1618    fn from_selection_if_renderable(no_gc: &'no_gc NoGC, selection: &Selection) -> Option<Self> {
1619        let range = selection.range.borrow();
1620        let range = range.as_ref()?;
1621
1622        let mut start_container = range.start.container.as_unrooted(no_gc);
1623        let mut start_offset = range.start.offset;
1624        let mut end_container = range.end.container.as_unrooted(no_gc);
1625        let mut end_offset = range.end.offset;
1626
1627        if !start_container.is_in_flat_tree(no_gc) {
1628            return None;
1629        }
1630        if !end_container.is_in_flat_tree(no_gc) {
1631            return None;
1632        }
1633
1634        // Compare the position of the two nodes in the flat tree, considering the
1635        // condition when they don't share a common ancestor to be an unrenderable
1636        // selection.
1637        let ordering = compare_dom_positions::<FlatTreeForSelectionNoGcTraversal>(
1638            no_gc,
1639            &range.start.container,
1640            range.start.offset,
1641            &range.end.container,
1642            range.end.offset,
1643        )
1644        .0?;
1645
1646        // If the visible selection is inverted relative to the composed tree range, swap
1647        // the boundaries.
1648        if ordering == Ordering::Greater {
1649            std::mem::swap(&mut start_container, &mut end_container);
1650            std::mem::swap(&mut start_offset, &mut end_offset);
1651        }
1652
1653        Some(FlatTreeSelection {
1654            no_gc,
1655            start: FlatTreeBoundary {
1656                container: start_container,
1657                offset: start_offset,
1658            },
1659            end: FlatTreeBoundary {
1660                container: end_container,
1661                offset: end_offset,
1662            },
1663        })
1664    }
1665
1666    fn range_for_character_data(&self, character_data: &CharacterData) -> RangeAny<Utf32CodeUnits> {
1667        let text = character_data.data();
1668        let node: &Node = character_data.upcast();
1669        RangeAny::new(
1670            (node == &**self.start.container)
1671                .then(|| Utf16CodeUnits(self.start.offset).to_utf32_code_units_in(&text)),
1672            (node == &**self.end.container)
1673                .then(|| Utf16CodeUnits(self.end.offset).to_utf32_code_units_in(&text)),
1674        )
1675    }
1676
1677    fn traversal(&self) -> VisibleSelectionTraversal<'no_gc> {
1678        let start_position = position_in_flat_tree_for_selection(self.no_gc, &self.start);
1679        let end_position = position_in_flat_tree_for_selection(self.no_gc, &self.end);
1680        VisibleSelectionTraversal {
1681            following: start_position
1682                .node()
1683                .following_flat_tree_nodes_unrooted(self.no_gc),
1684            start: start_position,
1685            end: end_position,
1686            finished: false,
1687            skip_subtree: false,
1688        }
1689    }
1690}
1691
1692struct VisibleSelectionTraversal<'no_gc> {
1693    following: UnrootedFollowingFlatTreeNodesTraversal<'no_gc>,
1694    start: FlatTreeNodePosition,
1695    end: FlatTreeNodePosition,
1696    finished: bool,
1697    skip_subtree: bool,
1698}
1699
1700impl<'no_gc> Iterator for VisibleSelectionTraversal<'no_gc> {
1701    type Item = UnrootedDom<'no_gc, Node>;
1702
1703    fn next(&mut self) -> Option<Self::Item> {
1704        while !self.finished {
1705            let following = if std::mem::take(&mut self.skip_subtree) {
1706                self.following.next_skipping_subtree()?
1707            } else {
1708                self.following.next()?
1709            };
1710
1711            match following {
1712                PrePostIteration::Enter(node) => {
1713                    // If the traversal ends right before the final node and this is the
1714                    // final node, just finish now.
1715                    if &**node == self.end.node() &&
1716                        matches!(self.end, FlatTreeNodePosition::Before(_))
1717                    {
1718                        self.finished = true;
1719                        break;
1720                    }
1721                    // If the selection starts after the first node, do not set any flags
1722                    // on that nodes descendants.
1723                    if &**node == self.start.node() &&
1724                        matches!(self.start, FlatTreeNodePosition::After(_))
1725                    {
1726                        self.skip_subtree = true;
1727                    }
1728                    return Some(node);
1729                },
1730                PrePostIteration::Leave(node) => {
1731                    self.finished = &**node == self.end.node();
1732                },
1733            }
1734        }
1735
1736        None
1737    }
1738}
1739
1740struct VisibleSelectionFlagUpdate<'no_gc> {
1741    no_gc: &'no_gc NoGC,
1742    /// The nodes that previously had the OVERLAPS_DOCUMENT_SELECTION set on them before
1743    /// this flag update.
1744    ///
1745    /// Hash keys are pointer addresses which are not directly controlled by web content
1746    /// so we don’t need HashDoS resistance and can use a faster hasher than `std`’s default
1747    previously_flagged_nodes: FxHashSet<UnrootedDom<'no_gc, Node>>,
1748    /// Cache shared between calls to [`Node::used_user_select`]
1749    used_user_select_cache: FxHashMap<UnrootedDom<'no_gc, Node>, UsedUserSelect>,
1750    /// Whether or not this update requires a display list update.
1751    needs_new_display_list: bool,
1752}
1753
1754impl<'no_gc> VisibleSelectionFlagUpdate<'no_gc> {
1755    fn run(
1756        no_gc: &'no_gc NoGC,
1757        previously_flagged_nodes: FxHashSet<UnrootedDom<'no_gc, Node>>,
1758        flat_tree_selection: Option<&FlatTreeSelection<'_>>,
1759        document: &Document,
1760    ) {
1761        let mut update = Self {
1762            no_gc,
1763            previously_flagged_nodes,
1764            used_user_select_cache: Default::default(),
1765            needs_new_display_list: false,
1766        };
1767
1768        if let Some(flat_tree_selection) = flat_tree_selection {
1769            let traversal = flat_tree_selection.traversal();
1770            for ancestor in traversal
1771                .start
1772                .node()
1773                .ancestors_in_flat_tree_unrooted(no_gc)
1774            {
1775                update.set(&ancestor, flat_tree_selection);
1776            }
1777            for node in traversal {
1778                update.set(&node, flat_tree_selection);
1779            }
1780        }
1781
1782        update.finish(document);
1783    }
1784
1785    fn set(&mut self, node: &UnrootedDom<'no_gc, Node>, flat_tree_selection: &FlatTreeSelection) {
1786        if !node.get_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION) {
1787            node.set_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION, true);
1788            debug_assert!(!self.previously_flagged_nodes.contains(node));
1789        } else {
1790            self.previously_flagged_nodes.remove(node);
1791        }
1792
1793        // TODO: We should ensure that the style is up-to-date before reading the
1794        // `user-select` property and changes to `user-select` should trigger new visual
1795        // selection updates. Not doing this means that the calculations here are one
1796        // layout old and are never run again until the selection changes.
1797        let user_select = node.used_user_select(self.no_gc, &mut self.used_user_select_cache);
1798        let inhibited = user_select == UsedUserSelect::None;
1799        node.set_flag(NodeFlags::SELECTION_INHIBITED, inhibited);
1800
1801        self.set_node_selection(node, (!inhibited).then_some(flat_tree_selection));
1802    }
1803
1804    fn clear(&mut self, node: &Node) {
1805        node.set_flag(NodeFlags::OVERLAPS_DOCUMENT_SELECTION, false);
1806        node.set_flag(NodeFlags::SELECTION_INHIBITED, false);
1807        self.set_node_selection(node, None);
1808    }
1809
1810    fn set_character_data_selection(
1811        &mut self,
1812        character_data: &CharacterData,
1813        range: Option<RangeAny<Utf32CodeUnits>>,
1814    ) {
1815        if character_data.set_text_run_selection(range) {
1816            self.needs_new_display_list = true;
1817        } else {
1818            // Note: This isn't just necessary when the `CharacterData` doesn't have a
1819            // corresponding box tree node, but *also* for properly handling `:first-letter`
1820            // at the moment. This cannot be removed until `:first-letter` is represented
1821            // properly in the box tree.
1822            character_data
1823                .upcast::<Node>()
1824                .dirty(self.no_gc, NodeDamage::ContentOrHeritage);
1825        }
1826    }
1827
1828    fn set_node_selection(&mut self, node: &Node, flat_tree_selection: Option<&FlatTreeSelection>) {
1829        if let Some(character_data) = node.downcast::<CharacterData>() {
1830            let range = flat_tree_selection.map(|flat_tree_selection| {
1831                flat_tree_selection.range_for_character_data(character_data)
1832            });
1833            self.set_character_data_selection(character_data, range);
1834        } else if node.set_element_selection(flat_tree_selection.is_some()) {
1835            self.needs_new_display_list = true;
1836        }
1837    }
1838
1839    fn finish(mut self, document: &Document) {
1840        for node in std::mem::take(&mut self.previously_flagged_nodes) {
1841            self.clear(&node);
1842        }
1843        if self.needs_new_display_list {
1844            document.window().layout().set_needs_new_display_list();
1845        }
1846    }
1847}