script/dom/execcommand/contenteditable/
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::cmp::Ordering;
6
7use js::context::JSContext;
8use script_bindings::inheritance::Castable;
9
10use crate::dom::abstractrange::bp_position;
11use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
12use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
13use crate::dom::bindings::codegen::Bindings::SelectionBinding::SelectionMethods;
14use crate::dom::bindings::codegen::Bindings::TextBinding::TextMethods;
15use crate::dom::bindings::root::{DomRoot, DomSlice};
16use crate::dom::bindings::str::DOMString;
17use crate::dom::characterdata::CharacterData;
18use crate::dom::document::Document;
19use crate::dom::execcommand::basecommand::CommandName;
20use crate::dom::execcommand::contenteditable::node::{
21    NodeOrString, is_allowed_child, move_preserving_ranges, record_the_values, restore_the_values,
22    split_the_parent,
23};
24use crate::dom::html::htmlbrelement::HTMLBRElement;
25use crate::dom::html::htmlelement::HTMLElement;
26use crate::dom::html::htmltablecellelement::HTMLTableCellElement;
27use crate::dom::html::htmltablerowelement::HTMLTableRowElement;
28use crate::dom::html::htmltablesectionelement::HTMLTableSectionElement;
29use crate::dom::node::Node;
30use crate::dom::selection::Selection;
31use crate::dom::text::Text;
32
33#[derive(Default, PartialEq)]
34pub(crate) enum SelectionDeletionBlockMerging {
35    #[default]
36    Merge,
37    Skip,
38}
39
40#[derive(Default, PartialEq)]
41pub(crate) enum SelectionDeletionStripWrappers {
42    #[default]
43    Strip,
44}
45
46#[derive(Default, PartialEq)]
47pub(crate) enum SelectionDeleteDirection {
48    #[default]
49    Forward,
50    Backward,
51}
52
53trait EquivalentPoint {
54    fn previous_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)>;
55    fn next_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)>;
56    fn first_equivalent_point(self) -> (DomRoot<Node>, u32);
57    fn last_equivalent_point(self) -> (DomRoot<Node>, u32);
58}
59
60impl EquivalentPoint for (DomRoot<Node>, u32) {
61    /// <https://w3c.github.io/editing/docs/execCommand/#previous-equivalent-point>
62    fn previous_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)> {
63        let (node, offset) = self;
64        // Step 1. If node's length is zero, return null.
65        let len = node.len();
66        if len == 0 {
67            return None;
68        }
69        // Step 2. If offset is 0, and node's parent is not null, and node is an inline node,
70        // return (node's parent, node's index).
71        if *offset == 0 && node.is_inline_node() {
72            if let Some(parent) = node.GetParentNode() {
73                return Some((parent, node.index()));
74            }
75        }
76        // Step 3. If node has a child with index offset − 1, and that child's length is not zero,
77        // and that child is an inline node, return (that child, that child's length).
78        if *offset > 0 {
79            if let Some(child) = node.children().nth(*offset as usize - 1) {
80                if !child.is_empty() && child.is_inline_node() {
81                    let len = child.len();
82                    return Some((child, len));
83                }
84            }
85        }
86
87        // Step 4. Return null.
88        None
89    }
90
91    /// <https://w3c.github.io/editing/docs/execCommand/#next-equivalent-point>
92    fn next_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)> {
93        let (node, offset) = self;
94        // Step 1. If node's length is zero, return null.
95        let len = node.len();
96        if len == 0 {
97            return None;
98        }
99
100        // Step 2.
101        //
102        // This step does not exist in the spec
103
104        // Step 3. If offset is node's length, and node's parent is not null, and node is an inline node,
105        // return (node's parent, 1 + node's index).
106        if *offset == len && node.is_inline_node() {
107            if let Some(parent) = node.GetParentNode() {
108                return Some((parent, node.index() + 1));
109            }
110        }
111
112        // Step 4.
113        //
114        // This step does not exist in the spec
115
116        // Step 5. If node has a child with index offset, and that child's length is not zero,
117        // and that child is an inline node, return (that child, 0).
118        if let Some(child) = node.children().nth(*offset as usize) {
119            if !child.is_empty() && child.is_inline_node() {
120                return Some((child, 0));
121            }
122        }
123
124        // Step 6.
125        //
126        // This step does not exist in the spec
127
128        // Step 7. Return null.
129        None
130    }
131
132    /// <https://w3c.github.io/editing/docs/execCommand/#first-equivalent-point>
133    fn first_equivalent_point(self) -> (DomRoot<Node>, u32) {
134        let mut previous_equivalent_point = self;
135        // Step 1. While (node, offset)'s previous equivalent point is not null, set (node, offset) to its previous equivalent point.
136        loop {
137            if let Some(next) = previous_equivalent_point.previous_equivalent_point() {
138                previous_equivalent_point = next;
139            } else {
140                // Step 2. Return (node, offset).
141                return previous_equivalent_point;
142            }
143        }
144    }
145
146    /// <https://w3c.github.io/editing/docs/execCommand/#last-equivalent-point>
147    fn last_equivalent_point(self) -> (DomRoot<Node>, u32) {
148        let mut next_equivalent_point = self;
149        // Step 1. While (node, offset)'s next equivalent point is not null, set (node, offset) to its next equivalent point.
150        loop {
151            if let Some(next) = next_equivalent_point.next_equivalent_point() {
152                next_equivalent_point = next;
153            } else {
154                // Step 2. Return (node, offset).
155                return next_equivalent_point;
156            }
157        }
158    }
159}
160
161impl Selection {
162    /// <https://w3c.github.io/editing/docs/execCommand/#delete-the-selection>
163    pub(crate) fn delete_the_selection(
164        &self,
165        cx: &mut JSContext,
166        context_object: &Document,
167        block_merging: SelectionDeletionBlockMerging,
168        strip_wrappers: SelectionDeletionStripWrappers,
169        direction: SelectionDeleteDirection,
170    ) {
171        // Step 1. If the active range is null, abort these steps and do nothing.
172        let Some(active_range) = self.active_range() else {
173            return;
174        };
175
176        // Step 2. Canonicalize whitespace at the active range's start.
177        active_range
178            .start_container()
179            .canonicalize_whitespace(active_range.start_offset(), true);
180
181        // Step 3. Canonicalize whitespace at the active range's end.
182        active_range
183            .end_container()
184            .canonicalize_whitespace(active_range.end_offset(), true);
185
186        // Step 4. Let (start node, start offset) be the last equivalent point for the active range's start.
187        let (mut start_node, mut start_offset) =
188            (active_range.start_container(), active_range.start_offset()).last_equivalent_point();
189
190        // Step 5. Let (end node, end offset) be the first equivalent point for the active range's end.
191        let (mut end_node, mut end_offset) =
192            (active_range.end_container(), active_range.end_offset()).first_equivalent_point();
193
194        // Step 6. If (end node, end offset) is not after (start node, start offset):
195        if bp_position(&end_node, end_offset, &start_node, start_offset) != Some(Ordering::Greater)
196        {
197            // Step 6.1. If direction is "forward", call collapseToStart() on the context object's selection.
198            if direction == SelectionDeleteDirection::Forward {
199                if self.CollapseToStart(cx).is_err() {
200                    unreachable!("Should be able to collapse to start");
201                }
202            } else {
203                // Step 6.2. Otherwise, call collapseToEnd() on the context object's selection.
204                if self.CollapseToEnd(cx).is_err() {
205                    unreachable!("Should be able to collapse to end");
206                }
207            }
208            // Step 6.3. Abort these steps.
209            return;
210        }
211
212        // Step 7. If start node is a Text node and start offset is 0, set start offset to the index of start node,
213        // then set start node to its parent.
214        if start_node.is::<Text>() && start_offset == 0 {
215            start_offset = start_node.index();
216            start_node = start_node
217                .GetParentNode()
218                .expect("Must always have a parent");
219        }
220
221        // Step 8. If end node is a Text node and end offset is its length, set end offset to one plus the index of end node,
222        // then set end node to its parent.
223        if end_node.is::<Text>() && end_offset == end_node.len() {
224            end_offset = end_node.index() + 1;
225            end_node = end_node.GetParentNode().expect("Must always have a parent");
226        }
227
228        // Step 9. Call collapse(start node, start offset) on the context object's selection.
229        if self.Collapse(cx, Some(&start_node), start_offset).is_err() {
230            unreachable!("Must always be able to collapse");
231        }
232
233        // Step 10. Call extend(end node, end offset) on the context object's selection.
234        if self.Extend(cx, &end_node, end_offset).is_err() {
235            unreachable!("Must always be able to extend");
236        }
237
238        // Step 11.
239        //
240        // This step does not exist in the spec
241
242        // Step 12. Let start block be the active range's start node.
243        let Some(active_range) = self.active_range() else {
244            return;
245        };
246        let mut start_block = active_range.start_container();
247
248        // Step 13. While start block's parent is in the same editing host and start block is an inline node,
249        // set start block to its parent.
250        loop {
251            if start_block.is_inline_node() {
252                if let Some(parent) = start_block.GetParentNode() {
253                    if parent.same_editing_host(&start_node) {
254                        start_block = parent;
255                        continue;
256                    }
257                }
258            }
259            break;
260        }
261
262        // Step 14. If start block is neither a block node nor an editing host,
263        // or "span" is not an allowed child of start block,
264        // or start block is a td or th, set start block to null.
265        let start_block = if (!start_block.is_block_node() && !start_block.is_editing_host()) ||
266            !is_allowed_child(
267                NodeOrString::String("span".to_owned()),
268                NodeOrString::Node(start_block.clone()),
269            ) ||
270            start_block.is::<HTMLTableCellElement>()
271        {
272            None
273        } else {
274            Some(start_block)
275        };
276
277        // Step 15. Let end block be the active range's end node.
278        let mut end_block = active_range.end_container();
279
280        // Step 16. While end block's parent is in the same editing host and end block is an inline node, set end block to its parent.
281        loop {
282            if end_block.is_inline_node() {
283                if let Some(parent) = end_block.GetParentNode() {
284                    if parent.same_editing_host(&end_block) {
285                        end_block = parent;
286                        continue;
287                    }
288                }
289            }
290            break;
291        }
292
293        // Step 17. If end block is neither a block node nor an editing host, or "span" is not an allowed child of end block,
294        // or end block is a td or th, set end block to null.
295        let end_block = if (!end_block.is_block_node() && !end_block.is_editing_host()) ||
296            !is_allowed_child(
297                NodeOrString::String("span".to_owned()),
298                NodeOrString::Node(end_block.clone()),
299            ) ||
300            end_block.is::<HTMLTableCellElement>()
301        {
302            None
303        } else {
304            Some(end_block)
305        };
306
307        // Step 18.
308        //
309        // This step does not exist in the spec
310
311        // Step 19. Record current states and values, and let overrides be the result.
312        let overrides = active_range.record_current_states_and_values();
313
314        // Step 20.
315        //
316        // This step does not exist in the spec
317
318        // Step 21. If start node and end node are the same, and start node is an editable Text node:
319        if start_node == end_node && start_node.is_editable() {
320            if let Some(start_text) = start_node.downcast::<Text>() {
321                // Step 21.1. Call deleteData(start offset, end offset − start offset) on start node.
322                if start_text
323                    .upcast::<CharacterData>()
324                    .DeleteData(start_offset, end_offset - start_offset)
325                    .is_err()
326                {
327                    unreachable!("Must always be able to delete");
328                }
329                // Step 21.2. Canonicalize whitespace at (start node, start offset), with fix collapsed space false.
330                start_node.canonicalize_whitespace(start_offset, false);
331                // Step 21.3. If direction is "forward", call collapseToStart() on the context object's selection.
332                if direction == SelectionDeleteDirection::Forward {
333                    if self.CollapseToStart(cx).is_err() {
334                        unreachable!("Should be able to collapse to start");
335                    }
336                } else {
337                    // Step 21.4. Otherwise, call collapseToEnd() on the context object's selection.
338                    if self.CollapseToEnd(cx).is_err() {
339                        unreachable!("Should be able to collapse to end");
340                    }
341                }
342                // Step 21.5. Restore states and values from overrides.
343                active_range.restore_states_and_values(cx, self, context_object, overrides);
344
345                // Step 21.6. Abort these steps.
346                return;
347            }
348        }
349
350        // Step 22. If start node is an editable Text node, call deleteData() on it, with start offset as
351        // the first argument and (length of start node − start offset) as the second argument.
352        if start_node.is_editable() {
353            if let Some(start_text) = start_node.downcast::<Text>() {
354                if start_text
355                    .upcast::<CharacterData>()
356                    .DeleteData(start_offset, start_node.len() - start_offset)
357                    .is_err()
358                {
359                    unreachable!("Must always be able to delete");
360                }
361            }
362        }
363
364        // Step 23. Let node list be a list of nodes, initially empty.
365        rooted_vec!(let mut node_list);
366
367        // Step 24. For each node contained in the active range, append node to node list if the
368        // last member of node list (if any) is not an ancestor of node; node is editable;
369        // and node is not a thead, tbody, tfoot, tr, th, or td.
370        let Ok(contained_children) = active_range.contained_children() else {
371            unreachable!("Must always have contained children");
372        };
373        for node in contained_children.contained_children {
374            // This type is only used to tell the compiler how to handle the type of `node_list.last()`.
375            // It is not allowed to add a `& DomRoot<Node>` annotation, as test-tidy disallows that.
376            // However, if we omit the type, the compiler doesn't know what it is, since we also
377            // aren't allowed to add a type annotation to `node_list` itself, as that is handled
378            // by the `rooted_vec` macro. Lastly, we also can't make it `&Node`, since then the compiler
379            // thinks that the contents of the `RootedVec` is `Node`, whereas it is should be
380            // `RootedVec<DomRoot<Node>>`. The type alias here doesn't upset test-tidy,
381            // while also providing the necessary information to the compiler to work.
382            type DomRootNode = DomRoot<Node>;
383            if node.is_editable() &&
384                !(node.is::<HTMLTableSectionElement>() ||
385                    node.is::<HTMLTableRowElement>() ||
386                    node.is::<HTMLTableCellElement>()) &&
387                node_list
388                    .last()
389                    .is_none_or(|last: &DomRootNode| !last.is_ancestor_of(&node))
390            {
391                node_list.push(node);
392            }
393        }
394
395        // Step 25. For each node in node list:
396        for node in node_list.iter() {
397            // Step 25.1. Let parent be the parent of node.
398            let parent = node.GetParentNode().expect("Must always have a parent");
399            // Step 25.2. Remove node from parent.
400            assert!(node.has_parent());
401            node.remove_self(cx);
402            // Step 25.3. If the block node of parent has no visible children, and parent is editable or an editing host,
403            // call createElement("br") on the context object and append the result as the last child of parent.
404            if parent.block_node_of().is_some_and(|block_node| {
405                block_node
406                    .children_unrooted(cx.no_gc())
407                    .all(|child| child.is_invisible())
408            }) && parent.is_editable_or_editing_host()
409            {
410                let br = context_object.create_element(cx, "br");
411                if parent.AppendChild(cx, br.upcast()).is_err() {
412                    unreachable!("Must always be able to append");
413                }
414            }
415            // Step 25.4. If strip wrappers is true or parent is not an inclusive ancestor of start node,
416            // while parent is an editable inline node with length 0, let grandparent be the parent of parent,
417            // then remove parent from grandparent, then set parent to grandparent.
418            if strip_wrappers == SelectionDeletionStripWrappers::Strip ||
419                !parent.is_inclusive_ancestor_of(&start_node)
420            {
421                let mut parent = parent;
422                loop {
423                    if parent.is_editable() && parent.is_inline_node() && parent.is_empty() {
424                        let grand_parent =
425                            parent.GetParentNode().expect("Must always have a parent");
426                        assert!(parent.has_parent());
427                        parent.remove_self(cx);
428                        parent = grand_parent;
429                        continue;
430                    }
431                    break;
432                }
433            }
434        }
435
436        // Step 26. If end node is an editable Text node, call deleteData(0, end offset) on it.
437        if end_node.is_editable() {
438            if let Some(end_text) = end_node.downcast::<Text>() {
439                if end_text
440                    .upcast::<CharacterData>()
441                    .DeleteData(0, end_offset)
442                    .is_err()
443                {
444                    unreachable!("Must always be able to delete");
445                }
446            }
447        }
448
449        // Step 27. Canonicalize whitespace at the active range's start, with fix collapsed space false.
450        active_range
451            .start_container()
452            .canonicalize_whitespace(active_range.start_offset(), false);
453
454        // Step 28. Canonicalize whitespace at the active range's end, with fix collapsed space false.
455        active_range
456            .end_container()
457            .canonicalize_whitespace(active_range.end_offset(), false);
458
459        // Step 29.
460        //
461        // This step does not exist in the spec
462
463        // Step 30. If block merging is false, or start block or end block is null, or start block is not
464        // in the same editing host as end block, or start block and end block are the same:
465        if block_merging == SelectionDeletionBlockMerging::Skip ||
466            start_block.as_ref().zip(end_block.as_ref()).is_none_or(
467                |(start_block, end_block)| {
468                    start_block == end_block || !start_block.same_editing_host(end_block)
469                },
470            )
471        {
472            // Step 30.1. If direction is "forward", call collapseToStart() on the context object's selection.
473            if direction == SelectionDeleteDirection::Forward {
474                if self.CollapseToStart(cx).is_err() {
475                    unreachable!("Should be able to collapse to start");
476                }
477            } else {
478                // Step 30.2. Otherwise, call collapseToEnd() on the context object's selection.
479                if self.CollapseToEnd(cx).is_err() {
480                    unreachable!("Should be able to collapse to end");
481                }
482            }
483            // Step 30.3. Restore states and values from overrides.
484            active_range.restore_states_and_values(cx, self, context_object, overrides);
485
486            // Step 30.4. Abort these steps.
487            return;
488        }
489        let start_block = start_block.expect("Already checked for None in previous statement");
490        let end_block = end_block.expect("Already checked for None in previous statement");
491
492        // Step 31. If start block has one child, which is a collapsed block prop, remove its child from it.
493        if start_block.children_count() == 1 {
494            let Some(child) = start_block.children().nth(0) else {
495                unreachable!("Must always have a single child");
496            };
497            if child.is_collapsed_block_prop() {
498                assert!(child.has_parent());
499                child.remove_self(cx);
500            }
501        }
502
503        // Step 32. If start block is an ancestor of end block:
504        let values = if start_block.is_ancestor_of(&end_block) {
505            // Step 32.1. Let reference node be end block.
506            let mut reference_node = end_block.clone();
507            // Step 32.2. While reference node is not a child of start block, set reference node to its parent.
508            loop {
509                if start_block
510                    .children_unrooted(cx.no_gc())
511                    .all(|child| child != &reference_node)
512                {
513                    reference_node = reference_node
514                        .GetParentNode()
515                        .expect("Must always have a parent, at least start_block");
516                    continue;
517                }
518                break;
519            }
520            // Step 32.3. Call collapse() on the context object's selection,
521            // with first argument start block and second argument the index of reference node.
522            if self
523                .Collapse(cx, Some(&start_block), reference_node.index())
524                .is_err()
525            {
526                unreachable!("Must always be able to collapse");
527            }
528            // Step 32.4. If end block has no children:
529            if end_block.children_count() == 0 {
530                let mut end_block = end_block;
531                // Step 32.4.1. While end block is editable and is the only child of its parent and is not a child of start block,
532                // let parent equal end block, then remove end block from parent, then set end block to parent.
533                loop {
534                    if end_block.is_editable() &&
535                        start_block.children().all(|child| child != end_block)
536                    {
537                        if let Some(parent) = end_block.GetParentNode() {
538                            if parent.children_count() == 1 {
539                                assert!(end_block.has_parent());
540                                end_block.remove_self(cx);
541                                end_block = parent;
542                                continue;
543                            }
544                        }
545                    }
546                    break;
547                }
548                // Step 32.4.2. If end block is editable and is not an inline node,
549                // and its previousSibling and nextSibling are both inline nodes,
550                // call createElement("br") on the context object and insert it into end block's parent immediately after end block.
551                if end_block.is_editable() &&
552                    !end_block.is_inline_node() &&
553                    end_block
554                        .GetPreviousSibling()
555                        .is_some_and(|previous| previous.is_inline_node())
556                {
557                    if let Some(next_of_end_block) = end_block.GetNextSibling() {
558                        if next_of_end_block.is_inline_node() {
559                            let br = context_object.create_element(cx, "br");
560                            let parent = end_block
561                                .GetParentNode()
562                                .expect("Must always have a parent");
563                            if parent
564                                .InsertBefore(cx, br.upcast(), Some(&next_of_end_block))
565                                .is_err()
566                            {
567                                unreachable!("Must always be able to insert into parent");
568                            }
569                        }
570                    }
571                }
572                // Step 32.4.3. If end block is editable, remove it from its parent.
573                if end_block.is_editable() {
574                    assert!(end_block.has_parent());
575                    end_block.remove_self(cx);
576                }
577                // Step 32.4.4. Restore states and values from overrides.
578                active_range.restore_states_and_values(cx, self, context_object, overrides);
579
580                // Step 32.4.5. Abort these steps.
581                return;
582            }
583            let first_child = end_block
584                .children()
585                .nth(0)
586                .expect("Already checked at least 1 child in previous statement");
587            // Step 32.5. If end block's firstChild is not an inline node,
588            // restore states and values from record, then abort these steps.
589            if !first_child.is_inline_node() {
590                active_range.restore_states_and_values(cx, self, context_object, overrides);
591                return;
592            }
593            // Step 32.6. Let children be a list of nodes, initially empty.
594            rooted_vec!(let mut children);
595            // Step 32.7. Append the first child of end block to children.
596            children.push(first_child.as_traced());
597            // Step 32.8. While children's last member is not a br,
598            // and children's last member's nextSibling is an inline node,
599            // append children's last member's nextSibling to children.
600            loop {
601                let Some(last) = children.last() else {
602                    break;
603                };
604                if last.is::<HTMLBRElement>() {
605                    break;
606                }
607                let Some(next) = last.GetNextSibling() else {
608                    break;
609                };
610                if next.is_inline_node() {
611                    children.push(next.as_traced());
612                    continue;
613                }
614                break;
615            }
616            // Step 32.9. Record the values of children, and let values be the result.
617            let values = record_the_values(children.iter().map(|dom| dom.as_rooted()).collect());
618
619            // Step 32.10. While children's first member's parent is not start block,
620            // split the parent of children.
621            loop {
622                if children
623                    .first()
624                    .and_then(|child| child.GetParentNode())
625                    .is_some_and(|parent_of_child| parent_of_child != start_block)
626                {
627                    split_the_parent(cx, children.r());
628                    continue;
629                }
630                break;
631            }
632            // Step 32.11. If children's first member's previousSibling is an editable br,
633            // remove that br from its parent.
634            if let Some(first) = children.first() {
635                if let Some(previous_of_first) = first.GetPreviousSibling() {
636                    if previous_of_first.is_editable() && previous_of_first.is::<HTMLBRElement>() {
637                        assert!(previous_of_first.has_parent());
638                        previous_of_first.remove_self(cx);
639                    }
640                }
641            }
642
643            values
644        // Step 33. Otherwise, if start block is a descendant of end block:
645        } else if end_block.is_ancestor_of(&start_block) {
646            // Step 33.1. Call collapse() on the context object's selection,
647            // with first argument start block and second argument start block's length.
648            if self
649                .Collapse(cx, Some(&start_block), start_block.len())
650                .is_err()
651            {
652                unreachable!("Must always be able to collapse");
653            }
654            // Step 33.2. Let reference node be start block.
655            let mut reference_node = start_block.clone();
656            // Step 33.3. While reference node is not a child of end block, set reference node to its parent.
657            loop {
658                if end_block.children().all(|child| child != reference_node) {
659                    if let Some(parent) = reference_node.GetParentNode() {
660                        reference_node = parent;
661                        continue;
662                    }
663                }
664                break;
665            }
666            // Step 33.4. If reference node's nextSibling is an inline node and start block's lastChild is a br,
667            // remove start block's lastChild from it.
668            if reference_node
669                .GetNextSibling()
670                .is_some_and(|next| next.is_inline_node())
671            {
672                if let Some(last) = start_block.children().last() {
673                    if last.is::<HTMLBRElement>() {
674                        assert!(last.has_parent());
675                        last.remove_self(cx);
676                    }
677                }
678            }
679            // Step 33.5. Let nodes to move be a list of nodes, initially empty.
680            rooted_vec!(let mut nodes_to_move);
681            // Step 33.6. If reference node's nextSibling is neither null nor a block node,
682            // append it to nodes to move.
683            if let Some(next) = reference_node.GetNextSibling() {
684                if !next.is_block_node() {
685                    nodes_to_move.push(next);
686                }
687            }
688            // Step 33.7. While nodes to move is nonempty and its last member isn't a br
689            // and its last member's nextSibling is neither null nor a block node,
690            // append its last member's nextSibling to nodes to move.
691            loop {
692                if let Some(last) = nodes_to_move.last() {
693                    if !last.is::<HTMLBRElement>() {
694                        if let Some(next_of_last) = last.GetNextSibling() {
695                            if !next_of_last.is_block_node() {
696                                nodes_to_move.push(next_of_last);
697                                continue;
698                            }
699                        }
700                    }
701                }
702                break;
703            }
704            // Step 33.8. Record the values of nodes to move, and let values be the result.
705            let values = record_the_values(nodes_to_move.iter().cloned().collect());
706
707            // Step 33.9. For each node in nodes to move,
708            // append node as the last child of start block, preserving ranges.
709            for node in nodes_to_move.iter() {
710                move_preserving_ranges(cx, node, |cx| start_block.AppendChild(cx, node));
711            }
712
713            values
714        // Step 34. Otherwise:
715        } else {
716            // Step 34.1. Call collapse() on the context object's selection,
717            // with first argument start block and second argument start block's length.
718            if self
719                .Collapse(cx, Some(&start_block), start_block.len())
720                .is_err()
721            {
722                unreachable!("Must always be able to collapse");
723            }
724            // Step 34.2. If end block's firstChild is an inline node and start block's lastChild is a br,
725            // remove start block's lastChild from it.
726            if end_block
727                .children()
728                .nth(0)
729                .is_some_and(|next| next.is_inline_node())
730            {
731                if let Some(last) = start_block.children().last() {
732                    if last.is::<HTMLBRElement>() {
733                        assert!(last.has_parent());
734                        last.remove_self(cx);
735                    }
736                }
737            }
738            // Step 34.3. Record the values of end block's children, and let values be the result.
739            let values = record_the_values(end_block.children().collect());
740
741            // Step 34.4. While end block has children,
742            // append the first child of end block to start block, preserving ranges.
743            loop {
744                if let Some(first_child) = end_block.children().nth(0) {
745                    move_preserving_ranges(cx, &first_child, |cx| {
746                        start_block.AppendChild(cx, &first_child)
747                    });
748                    continue;
749                }
750                break;
751            }
752            // Step 34.5. While end block has no children,
753            // let parent be the parent of end block, then remove end block from parent,
754            // then set end block to parent.
755            let mut end_block = end_block;
756            loop {
757                if end_block.children_count() == 0 {
758                    if let Some(parent) = end_block.GetParentNode() {
759                        assert!(end_block.has_parent());
760                        end_block.remove_self(cx);
761                        end_block = parent;
762                        continue;
763                    }
764                }
765                break;
766            }
767
768            values
769        };
770
771        // Step 35.
772        //
773        // This step does not exist in the spec
774
775        // Step 36. Let ancestor be start block.
776        // TODO
777
778        // Step 37. While ancestor has an inclusive ancestor ol in the same editing host whose nextSibling is
779        // also an ol in the same editing host, or an inclusive ancestor ul in the same editing host whose nextSibling
780        // is also a ul in the same editing host:
781        // TODO
782
783        // Step 38. Restore the values from values.
784        restore_the_values(cx, values);
785
786        // Step 39. If start block has no children, call createElement("br") on the context object and
787        // append the result as the last child of start block.
788        if start_block.children_count() == 0 {
789            let br = context_object.create_element(cx, "br");
790            if start_block.AppendChild(cx, br.upcast()).is_err() {
791                unreachable!("Must always be able to append");
792            }
793        }
794
795        // Step 40. Remove extraneous line breaks at the end of start block.
796        start_block.remove_extraneous_line_breaks_at_the_end_of(cx);
797
798        // Step 41. Restore states and values from overrides.
799        active_range.restore_states_and_values(cx, self, context_object, overrides);
800    }
801
802    /// <https://w3c.github.io/editing/docs/execCommand/#set-the-selection%27s-value>
803    pub(crate) fn set_the_selection_value(
804        &self,
805        cx: &mut JSContext,
806        new_value: Option<DOMString>,
807        command: CommandName,
808        context_object: &Document,
809    ) {
810        let active_range = self
811            .active_range()
812            .expect("Must always have an active range");
813
814        // Step 1. Let command be the current command.
815        //
816        // Passed as argument
817
818        // Step 2. If there is no formattable node effectively contained in the active range:
819        if active_range.first_formattable_contained_node().is_none() {
820            // Step 2.1. If command has inline command activated values, set the state override to true if new value is among them and false if it's not.
821            let inline_command_activated_values = command.inline_command_activated_values();
822            if !inline_command_activated_values.is_empty() {
823                context_object.set_state_override(
824                    command,
825                    Some(new_value.as_ref().is_some_and(|new_value| {
826                        inline_command_activated_values.contains(&new_value.str().as_ref())
827                    })),
828                );
829            }
830            // Step 2.2. If command is "subscript", unset the state override for "superscript".
831            if command == CommandName::Subscript {
832                context_object.set_state_override(CommandName::Superscript, None);
833            }
834            // Step 2.3. If command is "superscript", unset the state override for "subscript".
835            if command == CommandName::Superscript {
836                context_object.set_state_override(CommandName::Subscript, None);
837            }
838            // Step 2.4. If new value is null, unset the value override (if any).
839            // Step 2.5. Otherwise, if command is "createLink" or it has a value specified, set the value override to new value.
840            context_object.set_value_override(command, new_value);
841            // Step 2.6. Abort these steps.
842            return;
843        }
844        // Step 3. If the active range's start node is an editable Text node,
845        // and its start offset is neither zero nor its start node's length,
846        // call splitText() on the active range's start node,
847        // with argument equal to the active range's start offset.
848        // Then set the active range's start node to the result, and its start offset to zero.
849        let start_node = active_range.start_container();
850        let start_offset = active_range.start_offset();
851        if start_node.is_editable() && start_offset != 0 && start_offset != start_node.len() {
852            if let Some(start_text) = start_node.downcast::<Text>() {
853                let Ok(start_text) = start_text.SplitText(cx, start_offset) else {
854                    unreachable!("Must always be able to split");
855                };
856                active_range.set_start(start_text.upcast(), 0);
857            }
858        }
859        // Step 4. If the active range's end node is an editable Text node,
860        // and its end offset is neither zero nor its end node's length,
861        // call splitText() on the active range's end node,
862        // with argument equal to the active range's end offset.
863        let end_node = active_range.end_container();
864        let end_offset = active_range.end_offset();
865        if end_node.is_editable() && end_offset != 0 && end_offset != end_node.len() {
866            if let Some(end_text) = end_node.downcast::<Text>() {
867                if end_text.SplitText(cx, end_offset).is_err() {
868                    unreachable!("Must always be able to split");
869                };
870            }
871        }
872        // Step 5. Let element list be all editable Elements effectively contained in the active range.
873        // Step 6. For each element in element list, clear the value of element.
874        active_range.for_each_effectively_contained_child(|child| {
875            if child.is_editable() {
876                if let Some(element_child) = child.downcast::<HTMLElement>() {
877                    element_child.clear_the_value(cx, &command);
878                }
879            }
880        });
881        // Step 7. Let node list be all editable nodes effectively contained in the active range.
882        // Step 8. For each node in node list:
883        active_range.for_each_effectively_contained_child(|child| {
884            if child.is_editable() {
885                // Step 8.1. Push down values on node.
886                child.push_down_values(cx, &command, new_value.clone());
887                // Step 8.2. If node is an allowed child of "span", force the value of node.
888                if is_allowed_child(
889                    NodeOrString::Node(DomRoot::from_ref(child)),
890                    NodeOrString::String("span".to_owned()),
891                ) {
892                    child.force_the_value(cx, &command, new_value.as_ref());
893                }
894            }
895        });
896    }
897}