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