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
180            .start_container()
181            .canonicalize_whitespace(active_range.start_offset(), true);
182
183        // Step 3. Canonicalize whitespace at the active range's end.
184        active_range
185            .end_container()
186            .canonicalize_whitespace(active_range.end_offset(), true);
187
188        // Step 4. Let (start node, start offset) be the last equivalent point for the active range's start.
189        let (mut start_node, mut start_offset) =
190            (active_range.start_container(), active_range.start_offset()).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) =
194            (active_range.end_container(), active_range.end_offset()).first_equivalent_point();
195
196        // Step 6. If (end node, end offset) is not after (start node, start offset):
197        if bp_position(&end_node, end_offset, &start_node, start_offset) != Some(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                self.collapse_current_range(
202                    &active_range.start_container(),
203                    active_range.start_offset(),
204                );
205            } else {
206                // Step 6.2. Otherwise, call collapseToEnd() on the context object's selection.
207                self.collapse_current_range(
208                    &active_range.end_container(),
209                    active_range.end_offset(),
210                );
211            }
212            // Step 6.3. Abort these steps.
213            return;
214        }
215
216        // Step 7. If start node is a Text node and start offset is 0, set start offset to the index of start node,
217        // then set start node to its parent.
218        if start_node.is::<Text>() && start_offset == 0 {
219            start_offset = start_node.index();
220            start_node = start_node
221                .GetParentNode()
222                .expect("Must always have a parent");
223        }
224
225        // 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,
226        // then set end node to its parent.
227        if end_node.is::<Text>() && end_offset == end_node.len() {
228            end_offset = end_node.index() + 1;
229            end_node = end_node.GetParentNode().expect("Must always have a parent");
230        }
231
232        // Step 9. Call collapse(start node, start offset) on the context object's selection.
233        self.collapse_current_range(&start_node, start_offset);
234
235        // Step 10. Call extend(end node, end offset) on the context object's selection.
236        self.extend_current_range(&end_node, end_offset);
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 mut start_block = active_range.start_container();
244
245        // Step 13. While start block's parent is in the same editing host and start block is an inline node,
246        // set start block to its parent.
247        loop {
248            if start_block.is_inline_node() &&
249                let Some(parent) = start_block.GetParentNode() &&
250                parent.same_editing_host(&start_node)
251            {
252                start_block = parent;
253                continue;
254            }
255            break;
256        }
257
258        // Step 14. If start block is neither a block node nor an editing host,
259        // or "span" is not an allowed child of start block,
260        // or start block is a td or th, set start block to null.
261        let start_block = if (!start_block.is_block_node() && !start_block.is_editing_host()) ||
262            !is_allowed_child(
263                NodeOrString::String("span".to_owned()),
264                NodeOrString::Node(start_block.clone()),
265            ) ||
266            start_block.is::<HTMLTableCellElement>()
267        {
268            None
269        } else {
270            Some(start_block)
271        };
272
273        // Step 15. Let end block be the active range's end node.
274        let mut end_block = active_range.end_container();
275
276        // 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.
277        loop {
278            if end_block.is_inline_node() &&
279                let Some(parent) = end_block.GetParentNode() &&
280                parent.same_editing_host(&end_block)
281            {
282                end_block = parent;
283                continue;
284            }
285            break;
286        }
287
288        // Step 17. If end block is neither a block node nor an editing host, or "span" is not an allowed child of end block,
289        // or end block is a td or th, set end block to null.
290        let end_block = if (!end_block.is_block_node() && !end_block.is_editing_host()) ||
291            !is_allowed_child(
292                NodeOrString::String("span".to_owned()),
293                NodeOrString::Node(end_block.clone()),
294            ) ||
295            end_block.is::<HTMLTableCellElement>()
296        {
297            None
298        } else {
299            Some(end_block)
300        };
301
302        // Step 18.
303        //
304        // This step does not exist in the spec
305
306        // Step 19. Record current states and values, and let overrides be the result.
307        let overrides = active_range.record_current_states_and_values(cx);
308
309        // Step 20.
310        //
311        // This step does not exist in the spec
312
313        // Step 21. If start node and end node are the same, and start node is an editable Text node:
314        if start_node == end_node &&
315            start_node.is_editable() &&
316            let Some(start_text) = start_node.downcast::<Text>()
317        {
318            // Step 21.1. Call deleteData(start offset, end offset − start offset) on start node.
319            if start_text
320                .upcast::<CharacterData>()
321                .DeleteData(start_offset, end_offset - start_offset)
322                .is_err()
323            {
324                unreachable!("Must always be able to delete");
325            }
326            // Step 21.2. Canonicalize whitespace at (start node, start offset), with fix collapsed space false.
327            start_node.canonicalize_whitespace(start_offset, false);
328            // Step 21.3. If direction is "forward", call collapseToStart() on the context object's selection.
329            if direction == SelectionDeleteDirection::Forward {
330                self.collapse_current_range(
331                    &active_range.start_container(),
332                    active_range.start_offset(),
333                );
334            } else {
335                // Step 21.4. Otherwise, call collapseToEnd() on the context object's selection.
336                self.collapse_current_range(
337                    &active_range.end_container(),
338                    active_range.end_offset(),
339                );
340            }
341            // Step 21.5. Restore states and values from overrides.
342            active_range.restore_states_and_values(cx, self, context_object, overrides);
343
344            // Step 21.6. Abort these steps.
345            return;
346        }
347
348        // Step 22. If start node is an editable Text node, call deleteData() on it, with start offset as
349        // the first argument and (length of start node − start offset) as the second argument.
350        if start_node.is_editable() &&
351            let Some(start_text) = start_node.downcast::<Text>() &&
352            start_text
353                .upcast::<CharacterData>()
354                .DeleteData(start_offset, start_node.len() - start_offset)
355                .is_err()
356        {
357            unreachable!("Must always be able to delete");
358        }
359
360        // Step 23. Let node list be a list of nodes, initially empty.
361        rooted_vec!(let mut node_list);
362
363        // Step 24. For each node contained in the active range, append node to node list if the
364        // last member of node list (if any) is not an ancestor of node; node is editable;
365        // and node is not a thead, tbody, tfoot, tr, th, or td.
366        let Ok(contained_children) = active_range.contained_children() else {
367            unreachable!("Must always have contained children");
368        };
369        for node in contained_children.contained_children {
370            // This type is only used to tell the compiler how to handle the type of `node_list.last()`.
371            // It is not allowed to add a `& DomRoot<Node>` annotation, as test-tidy disallows that.
372            // However, if we omit the type, the compiler doesn't know what it is, since we also
373            // aren't allowed to add a type annotation to `node_list` itself, as that is handled
374            // by the `rooted_vec` macro. Lastly, we also can't make it `&Node`, since then the compiler
375            // thinks that the contents of the `RootedVec` is `Node`, whereas it is should be
376            // `RootedVec<DomRoot<Node>>`. The type alias here doesn't upset test-tidy,
377            // while also providing the necessary information to the compiler to work.
378            type DomRootNode = DomRoot<Node>;
379            if node.is_editable() &&
380                !(node.is::<HTMLTableSectionElement>() ||
381                    node.is::<HTMLTableRowElement>() ||
382                    node.is::<HTMLTableCellElement>()) &&
383                node_list
384                    .last()
385                    .is_none_or(|last: &DomRootNode| !last.is_ancestor_of(&node))
386            {
387                node_list.push(node);
388            }
389        }
390
391        // Step 25. For each node in node list:
392        for node in node_list.iter() {
393            // Step 25.1. Let parent be the parent of node.
394            let parent = node.GetParentNode().expect("Must always have a parent");
395            // Step 25.2. Remove node from parent.
396            assert!(node.has_parent());
397            node.remove_self(cx);
398            // Step 25.3. If the block node of parent has no visible children, and parent is editable or an editing host,
399            // call createElement("br") on the context object and append the result as the last child of parent.
400            if parent.block_node_of().is_some_and(|block_node| {
401                block_node
402                    .children_unrooted(cx.no_gc())
403                    .all(|child| child.is_invisible())
404            }) && parent.is_editable_or_editing_host()
405            {
406                let br = context_object.create_element(cx, "br");
407                if parent.AppendChild(cx, br.upcast()).is_err() {
408                    unreachable!("Must always be able to append");
409                }
410            }
411            // Step 25.4. If strip wrappers is true or parent is not an inclusive ancestor of start node,
412            // while parent is an editable inline node with length 0, let grandparent be the parent of parent,
413            // then remove parent from grandparent, then set parent to grandparent.
414            if strip_wrappers == SelectionDeletionStripWrappers::Strip ||
415                !parent.is_inclusive_ancestor_of(&start_node)
416            {
417                let mut parent = parent;
418                loop {
419                    if parent.is_editable() && parent.is_inline_node() && parent.is_empty() {
420                        let grand_parent =
421                            parent.GetParentNode().expect("Must always have a parent");
422                        assert!(parent.has_parent());
423                        parent.remove_self(cx);
424                        parent = grand_parent;
425                        continue;
426                    }
427                    break;
428                }
429            }
430        }
431
432        // Step 26. If end node is an editable Text node, call deleteData(0, end offset) on it.
433        if end_node.is_editable() &&
434            let Some(end_text) = end_node.downcast::<Text>() &&
435            end_text
436                .upcast::<CharacterData>()
437                .DeleteData(0, end_offset)
438                .is_err()
439        {
440            unreachable!("Must always be able to delete");
441        }
442
443        // Step 27. Canonicalize whitespace at the active range's start, with fix collapsed space false.
444        active_range
445            .start_container()
446            .canonicalize_whitespace(active_range.start_offset(), false);
447
448        // Step 28. Canonicalize whitespace at the active range's end, with fix collapsed space false.
449        active_range
450            .end_container()
451            .canonicalize_whitespace(active_range.end_offset(), false);
452
453        // Step 29.
454        //
455        // This step does not exist in the spec
456
457        // Step 30. If block merging is false, or start block or end block is null, or start block is not
458        // in the same editing host as end block, or start block and end block are the same:
459        if block_merging == SelectionDeletionBlockMerging::Skip ||
460            start_block.as_ref().zip(end_block.as_ref()).is_none_or(
461                |(start_block, end_block)| {
462                    start_block == end_block || !start_block.same_editing_host(end_block)
463                },
464            )
465        {
466            // Step 30.1. If direction is "forward", call collapseToStart() on the context object's selection.
467            if direction == SelectionDeleteDirection::Forward {
468                self.collapse_current_range(
469                    &active_range.start_container(),
470                    active_range.start_offset(),
471                );
472            } else {
473                // Step 30.2. Otherwise, call collapseToEnd() on the context object's selection.
474                self.collapse_current_range(
475                    &active_range.end_container(),
476                    active_range.end_offset(),
477                );
478            }
479            // Step 30.3. Restore states and values from overrides.
480            active_range.restore_states_and_values(cx, self, context_object, overrides);
481
482            // Step 30.4. Abort these steps.
483            return;
484        }
485        let start_block = start_block.expect("Already checked for None in previous statement");
486        let end_block = end_block.expect("Already checked for None in previous statement");
487
488        // Step 31. If start block has one child, which is a collapsed block prop, remove its child from it.
489        if start_block.children_count() == 1 {
490            let Some(child) = start_block.children().nth(0) else {
491                unreachable!("Must always have a single child");
492            };
493            if child.is_collapsed_block_prop() {
494                assert!(child.has_parent());
495                child.remove_self(cx);
496            }
497        }
498
499        // Step 32. If start block is an ancestor of end block:
500        let values = if start_block.is_ancestor_of(&end_block) {
501            // Step 32.1. Let reference node be end block.
502            let mut reference_node = end_block.clone();
503            // Step 32.2. While reference node is not a child of start block, set reference node to its parent.
504            loop {
505                if start_block
506                    .children_unrooted(cx.no_gc())
507                    .all(|child| child != &reference_node)
508                {
509                    reference_node = reference_node
510                        .GetParentNode()
511                        .expect("Must always have a parent, at least start_block");
512                    continue;
513                }
514                break;
515            }
516            // Step 32.3. Call collapse() on the context object's selection,
517            // with first argument start block and second argument the index of reference node.
518            self.collapse_current_range(&start_block, reference_node.index());
519            // Step 32.4. If end block has no children:
520            if end_block.children_count() == 0 {
521                let mut end_block = end_block;
522                // 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,
523                // let parent equal end block, then remove end block from parent, then set end block to parent.
524                loop {
525                    if end_block.is_editable() &&
526                        start_block.children().all(|child| child != end_block) &&
527                        let Some(parent) = end_block.GetParentNode() &&
528                        parent.children_count() == 1
529                    {
530                        assert!(end_block.has_parent());
531                        end_block.remove_self(cx);
532                        end_block = parent;
533                        continue;
534                    }
535                    break;
536                }
537                // Step 32.4.2. If end block is editable and is not an inline node,
538                // and its previousSibling and nextSibling are both inline nodes,
539                // call createElement("br") on the context object and insert it into end block's parent immediately after end block.
540                if end_block.is_editable() &&
541                    !end_block.is_inline_node() &&
542                    end_block
543                        .GetPreviousSibling()
544                        .is_some_and(|previous| previous.is_inline_node()) &&
545                    let Some(next_of_end_block) = end_block.GetNextSibling() &&
546                    next_of_end_block.is_inline_node()
547                {
548                    let br = context_object.create_element(cx, "br");
549                    let parent = end_block
550                        .GetParentNode()
551                        .expect("Must always have a parent");
552                    if parent
553                        .InsertBefore(cx, br.upcast(), Some(&next_of_end_block))
554                        .is_err()
555                    {
556                        unreachable!("Must always be able to insert into parent");
557                    }
558                }
559                // Step 32.4.3. If end block is editable, remove it from its parent.
560                if end_block.is_editable() {
561                    assert!(end_block.has_parent());
562                    end_block.remove_self(cx);
563                }
564                // Step 32.4.4. Restore states and values from overrides.
565                active_range.restore_states_and_values(cx, self, context_object, overrides);
566
567                // Step 32.4.5. Abort these steps.
568                return;
569            }
570            let first_child = end_block
571                .children()
572                .nth(0)
573                .expect("Already checked at least 1 child in previous statement");
574            // Step 32.5. If end block's firstChild is not an inline node,
575            // restore states and values from record, then abort these steps.
576            if !first_child.is_inline_node() {
577                active_range.restore_states_and_values(cx, self, context_object, overrides);
578                return;
579            }
580            // Step 32.6. Let children be a list of nodes, initially empty.
581            rooted_vec!(let mut children);
582            // Step 32.7. Append the first child of end block to children.
583            children.push(first_child.as_traced());
584            // Step 32.8. While children's last member is not a br,
585            // and children's last member's nextSibling is an inline node,
586            // append children's last member's nextSibling to children.
587            while let Some(last) = children.last() {
588                if last.is::<HTMLBRElement>() {
589                    break;
590                }
591                let Some(next) = last.GetNextSibling() else {
592                    break;
593                };
594                if next.is_inline_node() {
595                    children.push(next.as_traced());
596                    continue;
597                }
598                break;
599            }
600            // Step 32.9. Record the values of children, and let values be the result.
601            let values = record_the_values(children.iter().map(|dom| dom.as_rooted()).collect());
602
603            // Step 32.10. While children's first member's parent is not start block,
604            // split the parent of children.
605            loop {
606                if children
607                    .first()
608                    .and_then(|child| child.GetParentNode())
609                    .is_some_and(|parent_of_child| parent_of_child != start_block)
610                {
611                    split_the_parent(cx, children.r());
612                    continue;
613                }
614                break;
615            }
616            // Step 32.11. If children's first member's previousSibling is an editable br,
617            // remove that br from its parent.
618            if let Some(first) = children.first() &&
619                let Some(previous_of_first) = first.GetPreviousSibling() &&
620                previous_of_first.is_editable() &&
621                previous_of_first.is::<HTMLBRElement>()
622            {
623                assert!(previous_of_first.has_parent());
624                previous_of_first.remove_self(cx);
625            }
626
627            values
628        // Step 33. Otherwise, if start block is a descendant of end block:
629        } else if end_block.is_ancestor_of(&start_block) {
630            // Step 33.1. Call collapse() on the context object's selection,
631            // with first argument start block and second argument start block's length.
632            self.collapse_current_range(&start_block, start_block.len());
633            // Step 33.2. Let reference node be start block.
634            let mut reference_node = start_block.clone();
635            // Step 33.3. While reference node is not a child of end block, set reference node to its parent.
636            loop {
637                if end_block.children().all(|child| child != reference_node) &&
638                    let Some(parent) = reference_node.GetParentNode()
639                {
640                    reference_node = parent;
641                    continue;
642                }
643                break;
644            }
645            // Step 33.4. If reference node's nextSibling is an inline node and start block's lastChild is a br,
646            // remove start block's lastChild from it.
647            if reference_node
648                .GetNextSibling()
649                .is_some_and(|next| next.is_inline_node()) &&
650                let Some(last) = start_block.children().last() &&
651                last.is::<HTMLBRElement>()
652            {
653                assert!(last.has_parent());
654                last.remove_self(cx);
655            }
656            // Step 33.5. Let nodes to move be a list of nodes, initially empty.
657            rooted_vec!(let mut nodes_to_move);
658            // Step 33.6. If reference node's nextSibling is neither null nor a block node,
659            // append it to nodes to move.
660            if let Some(next) = reference_node.GetNextSibling() &&
661                !next.is_block_node()
662            {
663                nodes_to_move.push(next);
664            }
665            // Step 33.7. While nodes to move is nonempty and its last member isn't a br
666            // and its last member's nextSibling is neither null nor a block node,
667            // append its last member's nextSibling to nodes to move.
668            loop {
669                if let Some(last) = nodes_to_move.last() &&
670                    !last.is::<HTMLBRElement>() &&
671                    let Some(next_of_last) = last.GetNextSibling() &&
672                    !next_of_last.is_block_node()
673                {
674                    nodes_to_move.push(next_of_last);
675                    continue;
676                }
677                break;
678            }
679            // Step 33.8. Record the values of nodes to move, and let values be the result.
680            let values = record_the_values(nodes_to_move.iter().cloned().collect());
681
682            // Step 33.9. For each node in nodes to move,
683            // append node as the last child of start block, preserving ranges.
684            for node in nodes_to_move.iter() {
685                move_preserving_ranges(cx, node, |cx| start_block.AppendChild(cx, node));
686            }
687
688            values
689        // Step 34. Otherwise:
690        } else {
691            // Step 34.1. Call collapse() on the context object's selection,
692            // with first argument start block and second argument start block's length.
693            self.collapse_current_range(&start_block, start_block.len());
694            // Step 34.2. If end block's firstChild is an inline node and start block's lastChild is a br,
695            // remove start block's lastChild from it.
696            if end_block
697                .children()
698                .nth(0)
699                .is_some_and(|next| next.is_inline_node()) &&
700                let Some(last) = start_block.children().last() &&
701                last.is::<HTMLBRElement>()
702            {
703                assert!(last.has_parent());
704                last.remove_self(cx);
705            }
706            // Step 34.3. Record the values of end block's children, and let values be the result.
707            let values = record_the_values(end_block.children().collect());
708
709            // Step 34.4. While end block has children,
710            // append the first child of end block to start block, preserving ranges.
711            loop {
712                if let Some(first_child) = end_block.children().nth(0) {
713                    move_preserving_ranges(cx, &first_child, |cx| {
714                        start_block.AppendChild(cx, &first_child)
715                    });
716                    continue;
717                }
718                break;
719            }
720            // Step 34.5. While end block has no children,
721            // let parent be the parent of end block, then remove end block from parent,
722            // then set end block to parent.
723            let mut end_block = end_block;
724            loop {
725                if end_block.children_count() == 0 &&
726                    let Some(parent) = end_block.GetParentNode()
727                {
728                    assert!(end_block.has_parent());
729                    end_block.remove_self(cx);
730                    end_block = parent;
731                    continue;
732                }
733                break;
734            }
735
736            values
737        };
738
739        // Step 35.
740        //
741        // This step does not exist in the spec
742
743        // Step 36. Let ancestor be start block.
744        // TODO
745
746        // Step 37. While ancestor has an inclusive ancestor ol in the same editing host whose nextSibling is
747        // also an ol in the same editing host, or an inclusive ancestor ul in the same editing host whose nextSibling
748        // is also a ul in the same editing host:
749        // TODO
750
751        // Step 38. Restore the values from values.
752        restore_the_values(cx, values);
753
754        // Step 39. If start block has no children, call createElement("br") on the context object and
755        // append the result as the last child of start block.
756        if start_block.children_count() == 0 {
757            let br = context_object.create_element(cx, "br");
758            if start_block.AppendChild(cx, br.upcast()).is_err() {
759                unreachable!("Must always be able to append");
760            }
761        }
762
763        // Step 40. Remove extraneous line breaks at the end of start block.
764        start_block.remove_extraneous_line_breaks_at_the_end_of(cx);
765
766        // Step 41. Restore states and values from overrides.
767        active_range.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        let active_range = self
779            .active_range()
780            .expect("Must always have an active range");
781
782        // Step 1. Let command be the current command.
783        //
784        // Passed as argument
785
786        // Step 2. If there is no formattable node effectively contained in the active range:
787        if active_range.first_formattable_contained_node().is_none() {
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 = active_range.start_container();
818        let start_offset = active_range.start_offset();
819        if start_node.is_editable() &&
820            start_offset != 0 &&
821            start_offset != start_node.len() &&
822            let Some(start_text) = start_node.downcast::<Text>()
823        {
824            let Ok(start_text) = start_text.SplitText(cx, start_offset) else {
825                unreachable!("Must always be able to split");
826            };
827            active_range.set_start(start_text.upcast(), 0);
828        }
829        // Step 4. If the active range's end node is an editable Text node,
830        // and its end offset is neither zero nor its end node's length,
831        // call splitText() on the active range's end node,
832        // with argument equal to the active range's end offset.
833        let end_node = active_range.end_container();
834        let end_offset = active_range.end_offset();
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        active_range.for_each_effectively_contained_child(|child| {
846            if child.is_editable() &&
847                let Some(element_child) = child.downcast::<HTMLElement>()
848            {
849                element_child.clear_the_value(cx, &command);
850            }
851        });
852        // Step 7. Let node list be all editable nodes effectively contained in the active range.
853        // Step 8. For each node in node list:
854        active_range.for_each_effectively_contained_child(|child| {
855            if child.is_editable() {
856                // Step 8.1. Push down values on node.
857                child.push_down_values(cx, &command, new_value.clone());
858                // Step 8.2. If node is an allowed child of "span", force the value of node.
859                if is_allowed_child(
860                    NodeOrString::Node(DomRoot::from_ref(child)),
861                    NodeOrString::String("span".to_owned()),
862                ) {
863                    child.force_the_value(cx, &command, new_value.as_ref());
864                }
865            }
866        });
867    }
868}