Skip to main content

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