script/dom/execcommand/
contenteditable.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;
6use std::ops::Deref;
7
8use html5ever::local_name;
9use script_bindings::inheritance::Castable;
10use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
11use style::values::specified::box_::DisplayOutside;
12
13use crate::dom::abstractrange::bp_position;
14use crate::dom::bindings::cell::Ref;
15use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
16use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
17    DocumentMethods, ElementCreationOptions,
18};
19use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
20use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
21use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
22use crate::dom::bindings::codegen::Bindings::SelectionBinding::SelectionMethods;
23use crate::dom::bindings::codegen::Bindings::TextBinding::TextMethods;
24use crate::dom::bindings::codegen::UnionTypes::StringOrElementCreationOptions;
25use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
26use crate::dom::bindings::root::{DomRoot, DomSlice};
27use crate::dom::bindings::str::DOMString;
28use crate::dom::characterdata::CharacterData;
29use crate::dom::document::Document;
30use crate::dom::element::Element;
31use crate::dom::execcommand::basecommand::{CommandName, CssPropertyName};
32use crate::dom::html::htmlanchorelement::HTMLAnchorElement;
33use crate::dom::html::htmlbrelement::HTMLBRElement;
34use crate::dom::html::htmlelement::HTMLElement;
35use crate::dom::html::htmlfontelement::HTMLFontElement;
36use crate::dom::html::htmlimageelement::HTMLImageElement;
37use crate::dom::html::htmllielement::HTMLLIElement;
38use crate::dom::html::htmltablecellelement::HTMLTableCellElement;
39use crate::dom::html::htmltablerowelement::HTMLTableRowElement;
40use crate::dom::html::htmltablesectionelement::HTMLTableSectionElement;
41use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
42use crate::dom::range::Range;
43use crate::dom::selection::Selection;
44use crate::dom::text::Text;
45use crate::script_runtime::CanGc;
46
47impl Text {
48    /// <https://dom.spec.whatwg.org/#concept-cd-data>
49    fn data(&self) -> Ref<'_, String> {
50        self.upcast::<CharacterData>().data()
51    }
52
53    /// <https://w3c.github.io/editing/docs/execCommand/#whitespace-node>
54    fn is_whitespace_node(&self) -> bool {
55        // > A whitespace node is either a Text node whose data is the empty string;
56        let data = self.data();
57        if data.is_empty() {
58            return true;
59        }
60        // > or a Text node whose data consists only of one or more tabs (0x0009), line feeds (0x000A),
61        // > carriage returns (0x000D), and/or spaces (0x0020),
62        // > and whose parent is an Element whose resolved value for "white-space" is "normal" or "nowrap";
63        let Some(parent) = self.upcast::<Node>().GetParentElement() else {
64            return false;
65        };
66        // TODO: Optimize the below to only do a traversal once and in the match handle the expected collapse value
67        let Some(style) = parent.style() else {
68            return false;
69        };
70        let white_space_collapse = style.get_inherited_text().white_space_collapse;
71        if data
72            .bytes()
73            .all(|byte| matches!(byte, b'\t' | b'\n' | b'\r' | b' ')) &&
74            // Note that for "normal" and "nowrap", the longhand "white-space-collapse: collapse" applies
75            // https://www.w3.org/TR/css-text-4/#white-space-property
76            white_space_collapse == WhiteSpaceCollapse::Collapse
77        {
78            return true;
79        }
80        // > or a Text node whose data consists only of one or more tabs (0x0009), carriage returns (0x000D),
81        // > and/or spaces (0x0020), and whose parent is an Element whose resolved value for "white-space" is "pre-line".
82        data.bytes()
83            .all(|byte| matches!(byte, b'\t' | b'\r' | b' ')) &&
84            // Note that for "pre-line", the longhand "white-space-collapse: preserve-breaks" applies
85            // https://www.w3.org/TR/css-text-4/#white-space-property
86            white_space_collapse == WhiteSpaceCollapse::PreserveBreaks
87    }
88
89    /// <https://w3c.github.io/editing/docs/execCommand/#collapsed-whitespace-node>
90    fn is_collapsed_whitespace_node(&self) -> bool {
91        // Step 1. If node is not a whitespace node, return false.
92        if !self.is_whitespace_node() {
93            return false;
94        }
95        // Step 2. If node's data is the empty string, return true.
96        if self.data().is_empty() {
97            return true;
98        }
99        // Step 3. Let ancestor be node's parent.
100        let node = self.upcast::<Node>();
101        let Some(ancestor) = node.GetParentNode() else {
102            // Step 4. If ancestor is null, return true.
103            return true;
104        };
105        let mut resolved_ancestor = ancestor.clone();
106        for parent in ancestor.ancestors() {
107            // Step 5. If the "display" property of some ancestor of node has resolved value "none", return true.
108            if parent.is_display_none() {
109                return true;
110            }
111            // Step 6. While ancestor is not a block node and its parent is not null, set ancestor to its parent.
112            //
113            // Note that the spec is written as "while not". Since this is the end-condition, we need to invert
114            // the condition to decide when to stop.
115            if parent.is_block_node() {
116                break;
117            }
118            resolved_ancestor = parent;
119        }
120        // Step 7. Let reference be node.
121        // Step 8. While reference is a descendant of ancestor:
122        // Step 8.1. Let reference be the node before it in tree order.
123        for reference in node.preceding_nodes(&resolved_ancestor) {
124            // Step 8.2. If reference is a block node or a br, return true.
125            if reference.is_block_node() || reference.is::<HTMLBRElement>() {
126                return true;
127            }
128            // Step 8.3. If reference is a Text node that is not a whitespace node, or is an img, break from this loop.
129            if reference
130                .downcast::<Text>()
131                .is_some_and(|text| !text.is_whitespace_node()) ||
132                reference.is::<HTMLImageElement>()
133            {
134                break;
135            }
136        }
137        // Step 9. Let reference be node.
138        // Step 10. While reference is a descendant of ancestor:
139        // Step 10.1. Let reference be the node after it in tree order, or null if there is no such node.
140        for reference in node.following_nodes(&resolved_ancestor) {
141            // Step 10.2. If reference is a block node or a br, return true.
142            if reference.is_block_node() || reference.is::<HTMLBRElement>() {
143                return true;
144            }
145            // Step 10.3. If reference is a Text node that is not a whitespace node, or is an img, break from this loop.
146            if reference
147                .downcast::<Text>()
148                .is_some_and(|text| !text.is_whitespace_node()) ||
149                reference.is::<HTMLImageElement>()
150            {
151                break;
152            }
153        }
154        // Step 11. Return false.
155        false
156    }
157
158    /// Part of <https://w3c.github.io/editing/docs/execCommand/#canonicalize-whitespace>
159    /// and deduplicated here, since we need to do this for both start and end nodes
160    fn has_whitespace_and_has_parent_with_whitespace_preserve(
161        &self,
162        offset: u32,
163        space_characters: &'static [&'static char],
164    ) -> bool {
165        // if node is a Text node and its parent's resolved value for "white-space" is neither "pre" nor "pre-wrap"
166        // and start offset is not zero and the (start offset − 1)st code unit of start node's data is a space (0x0020) or
167        // non-breaking space (0x00A0)
168        let has_preserve_space = self
169            .upcast::<Node>()
170            .GetParentNode()
171            .and_then(|parent| parent.style())
172            .is_some_and(|style| {
173                // Note that for "pre" and "pre-wrap", the longhand "white-space-collapse: preserve" applies
174                // https://www.w3.org/TR/css-text-4/#white-space-property
175                style.get_inherited_text().white_space_collapse != WhiteSpaceCollapse::Preserve
176            });
177        let has_space_character = self
178            .data()
179            .chars()
180            .nth(offset as usize)
181            .is_some_and(|c| space_characters.contains(&&c));
182        has_preserve_space && has_space_character
183    }
184}
185
186impl HTMLBRElement {
187    /// <https://w3c.github.io/editing/docs/execCommand/#extraneous-line-break>
188    fn is_extraneous_line_break(&self) -> bool {
189        let node = self.upcast::<Node>();
190        // > An extraneous line break is a br that has no visual effect, in that removing it from the DOM would not change layout,
191        // except that a br that is the sole child of an li is not extraneous.
192        if node
193            .GetParentNode()
194            .filter(|parent| parent.is::<HTMLLIElement>())
195            .is_some_and(|li| li.children_count() == 1)
196        {
197            return false;
198        }
199        // TODO: Figure out what this actually makes it have no visual effect
200        !node.is_block_node()
201    }
202}
203
204impl Document {
205    pub(crate) fn create_element(
206        &self,
207        cx: &mut js::context::JSContext,
208        name: &str,
209    ) -> DomRoot<Element> {
210        let element_options =
211            StringOrElementCreationOptions::ElementCreationOptions(ElementCreationOptions {
212                is: None,
213            });
214        self.CreateElement(cx, name.into(), element_options)
215            .expect("Must always be able to create element")
216    }
217}
218
219impl HTMLElement {
220    fn local_name(&self) -> &str {
221        self.upcast::<Element>().local_name()
222    }
223
224    /// <https://w3c.github.io/editing/docs/execCommand/#clear-the-value>
225    fn clear_the_value(&self, cx: &mut js::context::JSContext, command: &CommandName) {
226        // Step 1. Let command be the current command.
227        //
228        // Passed in as argument
229
230        let node = self.upcast::<Node>();
231        let element = self.upcast::<Element>();
232
233        // Step 2. If element is not editable, return the empty list.
234        if !node.is_editable() {
235            return;
236        }
237        // Step 3. If element's specified command value for command is null,
238        // return the empty list.
239        if element.specified_command_value(command).is_none() {
240            return;
241        }
242        // Step 4. If element is a simple modifiable element:
243        if element.is_simple_modifiable_element() {
244            // Step 4.1. Let children be the children of element.
245            // Step 4.2. For each child in children, insert child into element's parent immediately before element, preserving ranges.
246            let element_parent = node.GetParentNode().expect("Must always have a parent");
247            for child in node.children() {
248                if element_parent.InsertBefore(cx, &child, Some(node)).is_err() {
249                    unreachable!("Must always be able to insert");
250                }
251            }
252            // Step 4.3. Remove element from its parent.
253            node.remove_self(cx);
254            // Step 4.4. Return children.
255            return;
256        }
257        match command {
258            // Step 5. If command is "strikethrough", and element has a style attribute
259            // that sets "text-decoration" to some value containing "line-through",
260            // delete "line-through" from the value.
261            CommandName::Strikethrough => {
262                let property = CssPropertyName::TextDecorationLine;
263                if property.value_for_element(cx, self) == "line-through" {
264                    // TODO: Only remove line-through
265                    property.remove_from_element(cx, self);
266                }
267            },
268            // Step 6. If command is "underline", and element has a style attribute that
269            // sets "text-decoration" to some value containing "underline", delete "underline" from the value.
270            CommandName::Underline => {
271                let property = CssPropertyName::TextDecorationLine;
272                if property.value_for_element(cx, self) == "underline" {
273                    // TODO: Only remove underline
274                    property.remove_from_element(cx, self);
275                }
276            },
277            _ => {},
278        }
279        // Step 7. If the relevant CSS property for command is not null,
280        // unset that property of element.
281        if let Some(property) = command.relevant_css_property() {
282            property.remove_from_element(cx, self);
283        }
284        // Step 8. If element is a font element:
285        if self.is::<HTMLFontElement>() {
286            match command {
287                // Step 8.1. If command is "foreColor", unset element's color attribute, if set.
288                CommandName::ForeColor => {
289                    element.remove_attribute_by_name(&local_name!("color"), CanGc::from_cx(cx));
290                },
291                // Step 8.2. If command is "fontName", unset element's face attribute, if set.
292                CommandName::FontName => {
293                    element.remove_attribute_by_name(&local_name!("face"), CanGc::from_cx(cx));
294                },
295                // Step 8.3. If command is "fontSize", unset element's size attribute, if set.
296                CommandName::FontSize => {
297                    element.remove_attribute_by_name(&local_name!("size"), CanGc::from_cx(cx));
298                },
299                _ => {},
300            }
301        }
302        // Step 9. If element is an a element and command is "createLink" or "unlink",
303        // unset the href property of element.
304        if self.is::<HTMLAnchorElement>() &&
305            matches!(command, CommandName::CreateLink | CommandName::Unlink)
306        {
307            element.remove_attribute_by_name(&local_name!("href"), CanGc::from_cx(cx));
308        }
309        // Step 10. If element's specified command value for command is null,
310        // return the empty list.
311        if element.specified_command_value(command).is_none() {
312            // TODO
313        }
314        // Step 11. Set the tag name of element to "span",
315        // and return the one-node list consisting of the result.
316        // TODO
317    }
318}
319
320impl Element {
321    /// <https://w3c.github.io/editing/docs/execCommand/#specified-command-value>
322    pub(crate) fn specified_command_value(&self, command: &CommandName) -> Option<DOMString> {
323        match command {
324            // Step 1. If command is "backColor" or "hiliteColor" and the Element's display property does not have resolved value "inline", return null.
325            CommandName::BackColor | CommandName::HiliteColor => {
326                // TODO
327            },
328            // Step 2. If command is "createLink" or "unlink":
329            CommandName::CreateLink | CommandName::Unlink => {
330                // TODO
331            },
332            // Step 3. If command is "subscript" or "superscript":
333            CommandName::Subscript | CommandName::Superscript => {
334                // TODO
335            },
336            CommandName::Strikethrough => {
337                // Step 4. If command is "strikethrough", and element has a style attribute set, and that attribute sets "text-decoration":
338                // TODO
339                // Step 5. If command is "strikethrough" and element is an s or strike element, return "line-through".
340                // TODO
341            },
342            CommandName::Underline => {
343                // Step 6. If command is "underline", and element has a style attribute set, and that attribute sets "text-decoration":
344                // TODO
345                // Step 7. If command is "underline" and element is a u element, return "underline".
346                // TODO
347            },
348            _ => {},
349        };
350        // Step 8. Let property be the relevant CSS property for command.
351        // Step 9. If property is null, return null.
352        let property = command.relevant_css_property()?;
353        // Step 10. If element has a style attribute set, and that attribute has the effect of setting property,
354        // return the value that it sets property to.
355        if let Some(value) = property.value_set_for_style(self) {
356            return Some(value);
357        }
358        // Step 11. If element is a font element that has an attribute whose effect is to create a presentational hint for property,
359        // return the value that the hint sets property to. (For a size of 7, this will be the non-CSS value "xxx-large".)
360        // TODO
361
362        // Step 12. If element is in the following list, and property is equal to the CSS property name listed for it,
363        // return the string listed for it.
364        let element_name = self.local_name();
365        match property {
366            CssPropertyName::FontWeight
367                if element_name == &local_name!("b") || element_name == &local_name!("strong") =>
368            {
369                Some("bold".into())
370            },
371            CssPropertyName::FontStyle
372                if element_name == &local_name!("i") || element_name == &local_name!("em") =>
373            {
374                Some("italic".into())
375            },
376            // Step 13. Return null.
377            _ => None,
378        }
379    }
380
381    /// <https://w3c.github.io/editing/docs/execCommand/#simple-modifiable-element>
382    fn is_simple_modifiable_element(&self) -> bool {
383        // > It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u element with no attributes.
384        // TODO
385
386        // > It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u element
387        // > with exactly one attribute, which is style,
388        // > which sets no CSS properties (including invalid or unrecognized properties).
389        // TODO
390
391        // > It is an a element with exactly one attribute, which is href.
392        // TODO
393
394        // > It is a font element with exactly one attribute, which is either color, face, or size.
395        // TODO
396
397        // > It is a b or strong element with exactly one attribute, which is style,
398        // > and the style attribute sets exactly one CSS property
399        // > (including invalid or unrecognized properties), which is "font-weight".
400        // TODO
401
402        // > It is an i or em element with exactly one attribute, which is style,
403        // > and the style attribute sets exactly one CSS property (including invalid or unrecognized properties),
404        // > which is "font-style".
405        // TODO
406
407        // > It is an a, font, or span element with exactly one attribute, which is style,
408        // > and the style attribute sets exactly one CSS property (including invalid or unrecognized properties),
409        // > and that property is not "text-decoration".
410        // TODO
411
412        // > It is an a, font, s, span, strike, or u element with exactly one attribute,
413        // > which is style, and the style attribute sets exactly one CSS property
414        // > (including invalid or unrecognized properties), which is "text-decoration",
415        // > which is set to "line-through" or "underline" or "overline" or "none".
416        // TODO
417
418        false
419    }
420}
421
422pub(crate) enum NodeOrString {
423    String(String),
424    Node(DomRoot<Node>),
425}
426
427impl NodeOrString {
428    fn name(&self) -> &str {
429        match self {
430            NodeOrString::String(str_) => str_,
431            NodeOrString::Node(node) => node
432                .downcast::<Element>()
433                .map(|element| element.local_name().as_ref())
434                .unwrap_or_default(),
435        }
436    }
437
438    fn as_node(&self) -> Option<DomRoot<Node>> {
439        match self {
440            NodeOrString::String(_) => None,
441            NodeOrString::Node(node) => Some(node.clone()),
442        }
443    }
444}
445
446/// <https://w3c.github.io/editing/docs/execCommand/#prohibited-paragraph-child-name>
447const PROHIBITED_PARAGRAPH_CHILD_NAMES: [&str; 47] = [
448    "address",
449    "article",
450    "aside",
451    "blockquote",
452    "caption",
453    "center",
454    "col",
455    "colgroup",
456    "dd",
457    "details",
458    "dir",
459    "div",
460    "dl",
461    "dt",
462    "fieldset",
463    "figcaption",
464    "figure",
465    "footer",
466    "form",
467    "h1",
468    "h2",
469    "h3",
470    "h4",
471    "h5",
472    "h6",
473    "header",
474    "hgroup",
475    "hr",
476    "li",
477    "listing",
478    "menu",
479    "nav",
480    "ol",
481    "p",
482    "plaintext",
483    "pre",
484    "section",
485    "summary",
486    "table",
487    "tbody",
488    "td",
489    "tfoot",
490    "th",
491    "thead",
492    "tr",
493    "ul",
494    "xmp",
495];
496/// <https://w3c.github.io/editing/docs/execCommand/#name-of-an-element-with-inline-contents>
497const NAME_OF_AN_ELEMENT_WITH_INLINE_CONTENTS: [&str; 43] = [
498    "a", "abbr", "b", "bdi", "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5",
499    "h6", "i", "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small", "span",
500    "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike", "xmp", "big", "blink",
501    "font", "marquee", "nobr", "tt",
502];
503
504/// <https://w3c.github.io/editing/docs/execCommand/#element-with-inline-contents>
505fn is_element_with_inline_contents(element: &Node) -> bool {
506    // > An element with inline contents is an HTML element whose local name is a name of an element with inline contents.
507    let Some(html_element) = element.downcast::<HTMLElement>() else {
508        return false;
509    };
510    NAME_OF_AN_ELEMENT_WITH_INLINE_CONTENTS.contains(&html_element.local_name())
511}
512
513/// <https://w3c.github.io/editing/docs/execCommand/#allowed-child>
514fn is_allowed_child(child: NodeOrString, parent: NodeOrString) -> bool {
515    // Step 1. If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr",
516    // or an HTML element with local name equal to one of those,
517    // and child is a Text node whose data does not consist solely of space characters, return false.
518    if matches!(
519        parent.name(),
520        "colgroup" | "table" | "tbody" | "tfoot" | "thead" | "tr"
521    ) && child.as_node().is_some_and(|node| {
522        // Note: cannot use `.and_then` here, since `downcast` would outlive its reference
523        node.downcast::<Text>()
524            .is_some_and(|text| !text.data().bytes().all(|byte| byte == b' '))
525    }) {
526        return false;
527    }
528    // Step 2. If parent is "script", "style", "plaintext", or "xmp",
529    // or an HTML element with local name equal to one of those, and child is not a Text node, return false.
530    if matches!(parent.name(), "script" | "style" | "plaintext" | "xmp") &&
531        child.as_node().is_none_or(|node| !node.is::<Text>())
532    {
533        return false;
534    }
535    // Step 3. If child is a document, DocumentFragment, or DocumentType, return false.
536    if let NodeOrString::Node(ref node) = child {
537        if matches!(
538            node.type_id(),
539            NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) | NodeTypeId::DocumentType
540        ) {
541            return false;
542        }
543    }
544    // Step 4. If child is an HTML element, set child to the local name of child.
545    let child_name = match child {
546        NodeOrString::String(str_) => str_,
547        NodeOrString::Node(node) => match node.downcast::<HTMLElement>() {
548            // Step 5. If child is not a string, return true.
549            None => return true,
550            Some(html_element) => html_element.local_name().to_owned(),
551        },
552    };
553    let child = child_name.as_str();
554    let parent_name = match parent {
555        NodeOrString::String(str_) => str_,
556        NodeOrString::Node(parent) => {
557            // Step 6. If parent is an HTML element:
558            if let Some(parent_element) = parent.downcast::<HTMLElement>() {
559                // Step 6.1. If child is "a", and parent or some ancestor of parent is an a, return false.
560                if child == "a" &&
561                    parent
562                        .inclusive_ancestors(ShadowIncluding::No)
563                        .any(|node| node.is::<HTMLAnchorElement>())
564                {
565                    return false;
566                }
567                // Step 6.2. If child is a prohibited paragraph child name and parent or some ancestor of parent
568                // is an element with inline contents, return false.
569                if PROHIBITED_PARAGRAPH_CHILD_NAMES.contains(&child) &&
570                    parent
571                        .inclusive_ancestors(ShadowIncluding::No)
572                        .any(|node| is_element_with_inline_contents(&node))
573                {
574                    return false;
575                }
576                // Step 6.3. If child is "h1", "h2", "h3", "h4", "h5", or "h6",
577                // and parent or some ancestor of parent is an HTML element with local name
578                // "h1", "h2", "h3", "h4", "h5", or "h6", return false.
579                if matches!(child, "h1" | "h2" | "h3" | "h4" | "h5" | "h6") &&
580                    parent.inclusive_ancestors(ShadowIncluding::No).any(|node| {
581                        node.downcast::<HTMLElement>().is_some_and(|html_element| {
582                            matches!(
583                                html_element.local_name(),
584                                "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
585                            )
586                        })
587                    })
588                {
589                    return false;
590                }
591                // Step 6.4. Let parent be the local name of parent.
592                parent_element.local_name().to_owned()
593            } else {
594                // Step 7. If parent is an Element or DocumentFragment, return true.
595                // Step 8. If parent is not a string, return false.
596                return matches!(
597                    parent.type_id(),
598                    NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(_)
599                );
600            }
601        },
602    };
603    let parent = parent_name.as_str();
604    // Step 9. If parent is on the left-hand side of an entry on the following list,
605    // then return true if child is listed on the right-hand side of that entry, and false otherwise.
606    match parent {
607        "colgroup" => return child == "col",
608        "table" => {
609            return matches!(
610                child,
611                "caption" | "col" | "colgroup" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr"
612            );
613        },
614        "tbody" | "tfoot" | "thead" => return matches!(child, "td" | "th" | "tr"),
615        "tr" => return matches!(child, "td" | "th"),
616        "dl" => return matches!(child, "dt" | "dd"),
617        "dir" | "ol" | "ul" => return matches!(child, "dir" | "li" | "ol" | "ul"),
618        "hgroup" => return matches!(child, "h1" | "h2" | "h3" | "h4" | "h5" | "h6"),
619        _ => {},
620    };
621    // Step 10. If child is "body", "caption", "col", "colgroup", "frame", "frameset", "head",
622    // "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return false.
623    if matches!(
624        child,
625        "body" |
626            "caption" |
627            "col" |
628            "colgroup" |
629            "frame" |
630            "frameset" |
631            "head" |
632            "html" |
633            "tbody" |
634            "td" |
635            "tfoot" |
636            "th" |
637            "thead" |
638            "tr"
639    ) {
640        return false;
641    }
642    // Step 11. If child is "dd" or "dt" and parent is not "dl", return false.
643    if matches!(child, "dd" | "dt") && parent != "dl" {
644        return false;
645    }
646    // Step 12. If child is "li" and parent is not "ol" or "ul", return false.
647    if child == "li" && !matches!(parent, "ol" | "ul") {
648        return false;
649    }
650    // Step 13. If parent is on the left-hand side of an entry on the following list
651    // and child is listed on the right-hand side of that entry, return false.
652    if match parent {
653        "a" => child == "a",
654        "dd" | "dt" => matches!(child, "dd" | "dt"),
655        "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => {
656            matches!(child, "h1" | "h2" | "h3" | "h4" | "h5" | "h6")
657        },
658        "li" => child == "li",
659        "nobr" => child == "nobr",
660        "td" | "th" => {
661            matches!(
662                child,
663                "caption" | "col" | "colgroup" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr"
664            )
665        },
666        _ if NAME_OF_AN_ELEMENT_WITH_INLINE_CONTENTS.contains(&parent) => {
667            PROHIBITED_PARAGRAPH_CHILD_NAMES.contains(&child)
668        },
669        _ => false,
670    } {
671        return false;
672    }
673    // Step 14. Return true.
674    true
675}
676
677/// <https://w3c.github.io/editing/docs/execCommand/#split-the-parent>
678pub(crate) fn split_the_parent<'a>(cx: &mut js::context::JSContext, node_list: &'a [&'a Node]) {
679    assert!(!node_list.is_empty());
680    // Step 1. Let original parent be the parent of the first member of node list.
681    let Some(original_parent) = node_list.first().and_then(|first| first.GetParentNode()) else {
682        return;
683    };
684    let context_object = original_parent.owner_document();
685    // Step 2. If original parent is not editable or its parent is null, do nothing and abort these steps.
686    if !original_parent.is_editable() {
687        return;
688    }
689    let Some(parent_of_original_parent) = original_parent.GetParentNode() else {
690        return;
691    };
692    // Step 3. If the first child of original parent is in node list, remove extraneous line breaks before original parent.
693    if original_parent
694        .children()
695        .next()
696        .is_some_and(|first_child| node_list.contains(&first_child.deref()))
697    {
698        original_parent.remove_extraneous_line_breaks_before(cx);
699    }
700    // Step 4. If the first child of original parent is in node list, and original parent follows a line break,
701    // set follows line break to true. Otherwise, set follows line break to false.
702    let first_child_is_in_node_list = original_parent
703        .children()
704        .next()
705        .is_some_and(|first_child| node_list.contains(&first_child.deref()));
706    let follows_line_break = first_child_is_in_node_list && original_parent.follows_a_line_break();
707    // Step 5. If the last child of original parent is in node list, and original parent precedes a line break,
708    // set precedes line break to true. Otherwise, set precedes line break to false.
709    let last_child_is_in_node_list = original_parent
710        .children()
711        .last()
712        .is_some_and(|last_child| node_list.contains(&last_child.deref()));
713    let precedes_line_break = last_child_is_in_node_list && original_parent.precedes_a_line_break();
714    // Step 6. If the first child of original parent is not in node list, but its last child is:
715    if !first_child_is_in_node_list && last_child_is_in_node_list {
716        // Step 6.1. For each node in node list, in reverse order,
717        // insert node into the parent of original parent immediately after original parent, preserving ranges.
718        for node in node_list.iter().rev() {
719            // TODO: Preserving ranges
720            if parent_of_original_parent
721                .InsertBefore(cx, node, original_parent.GetNextSibling().as_deref())
722                .is_err()
723            {
724                unreachable!("Must always have a parent");
725            }
726        }
727        // Step 6.2. If precedes line break is true, and the last member of node list does not precede a line break,
728        // call createElement("br") on the context object and insert the result immediately after the last member of node list.
729        if precedes_line_break {
730            if let Some(last) = node_list.last() {
731                if !last.precedes_a_line_break() {
732                    let br = context_object.create_element(cx, "br");
733                    if last
734                        .GetParentNode()
735                        .expect("Must always have a parent")
736                        .InsertBefore(cx, br.upcast(), last.GetNextSibling().as_deref())
737                        .is_err()
738                    {
739                        unreachable!("Must always be able to append");
740                    }
741                }
742            }
743        }
744        // Step 6.3. Remove extraneous line breaks at the end of original parent.
745        original_parent.remove_extraneous_line_breaks_at_the_end_of(cx);
746        // Step 6.4. Abort these steps.
747        return;
748    }
749    // Step 7. If the first child of original parent is not in node list:
750    if first_child_is_in_node_list {
751        // Step 7.1. Let cloned parent be the result of calling cloneNode(false) on original parent.
752        let Ok(cloned_parent) = original_parent.CloneNode(cx, false) else {
753            unreachable!("Must always be able to clone node");
754        };
755        // Step 7.2. If original parent has an id attribute, unset it.
756        if let Some(element) = original_parent.downcast::<Element>() {
757            element.remove_attribute_by_name(&local_name!("id"), CanGc::from_cx(cx));
758        }
759        // Step 7.3. Insert cloned parent into the parent of original parent immediately before original parent.
760        if parent_of_original_parent
761            .InsertBefore(cx, &cloned_parent, Some(&original_parent))
762            .is_err()
763        {
764            unreachable!("Must always have a parent");
765        }
766        // Step 7.4. While the previousSibling of the first member of node list is not null,
767        // append the first child of original parent as the last child of cloned parent, preserving ranges.
768        loop {
769            if node_list
770                .first()
771                .and_then(|first| first.GetPreviousSibling())
772                .is_some()
773            {
774                if let Some(first_of_original) = original_parent.children().next() {
775                    // TODO: Preserving ranges
776                    if cloned_parent.AppendChild(cx, &first_of_original).is_err() {
777                        unreachable!("Must always have a parent");
778                    }
779                    continue;
780                }
781            }
782            break;
783        }
784    }
785    // Step 8. For each node in node list, insert node into the parent of original parent immediately before original parent, preserving ranges.
786    for node in node_list.iter() {
787        // TODO: Preserving ranges
788        if parent_of_original_parent
789            .InsertBefore(cx, node, Some(&original_parent))
790            .is_err()
791        {
792            unreachable!("Must always have a parent");
793        }
794    }
795    // Step 9. If follows line break is true, and the first member of node list does not follow a line break,
796    // call createElement("br") on the context object and insert the result immediately before the first member of node list.
797    if follows_line_break {
798        if let Some(first) = node_list.first() {
799            if !first.follows_a_line_break() {
800                let br = context_object.create_element(cx, "br");
801                if first
802                    .GetParentNode()
803                    .expect("Must always have a parent")
804                    .InsertBefore(cx, br.upcast(), Some(first))
805                    .is_err()
806                {
807                    unreachable!("Must always be able to insert");
808                }
809            }
810        }
811    }
812    // Step 10. If the last member of node list is an inline node other than a br,
813    // and the first child of original parent is a br, and original parent is not an inline node,
814    // remove the first child of original parent from original parent.
815    if node_list
816        .last()
817        .is_some_and(|last| last.is_inline_node() && !last.is::<HTMLBRElement>()) &&
818        !original_parent.is_inline_node()
819    {
820        if let Some(first_of_original) = original_parent.children().next() {
821            if first_of_original.is::<HTMLBRElement>() {
822                assert!(first_of_original.has_parent());
823                first_of_original.remove_self(cx);
824            }
825        }
826    }
827    // Step 11. If original parent has no children:
828    if original_parent.children_count() == 0 {
829        // Step 11.1. Remove original parent from its parent.
830        assert!(original_parent.has_parent());
831        original_parent.remove_self(cx);
832        // Step 11.2. If precedes line break is true, and the last member of node list does not precede a line break,
833        // call createElement("br") on the context object and insert the result immediately after the last member of node list.
834        if precedes_line_break {
835            if let Some(last) = node_list.last() {
836                if !last.precedes_a_line_break() {
837                    let br = context_object.create_element(cx, "br");
838                    if last
839                        .GetParentNode()
840                        .expect("Must always have a parent")
841                        .InsertBefore(cx, br.upcast(), last.GetNextSibling().as_deref())
842                        .is_err()
843                    {
844                        unreachable!("Must always be able to insert");
845                    }
846                }
847            }
848        }
849    } else {
850        // Step 12. Otherwise, remove extraneous line breaks before original parent.
851        original_parent.remove_extraneous_line_breaks_before(cx);
852    }
853    // Step 13. If node list's last member's nextSibling is null, but its parent is not null,
854    // remove extraneous line breaks at the end of node list's last member's parent.
855    if let Some(last) = node_list.last() {
856        if last.GetNextSibling().is_none() {
857            if let Some(parent_of_last) = last.GetParentNode() {
858                parent_of_last.remove_extraneous_line_breaks_at_the_end_of(cx);
859            }
860        }
861    }
862}
863
864/// <https://w3c.github.io/editing/docs/execCommand/#wrap>
865fn wrap_node_list<'a, SiblingCriteria, NewParentInstructions>(
866    cx: &mut js::context::JSContext,
867    node_list: &'a [&'a Node],
868    sibling_criteria: SiblingCriteria,
869    new_parent_instructions: NewParentInstructions,
870) -> Option<DomRoot<Node>>
871where
872    SiblingCriteria: Fn(&Node) -> bool,
873    NewParentInstructions: Fn() -> Option<DomRoot<Node>>,
874{
875    // Step 1. If every member of node list is invisible,
876    // and none is a br, return null and abort these steps.
877    // TODO
878    // Step 2. If node list's first member's parent is null, return null and abort these steps.
879    // TODO
880    // Step 3. If node list's last member is an inline node that's not a br,
881    // and node list's last member's nextSibling is a br, append that br to node list.
882    // TODO
883    // Step 4. While node list's first member's previousSibling is invisible, prepend it to node list.
884    // TODO
885    // Step 5. While node list's last member's nextSibling is invisible, append it to node list.
886    // TODO
887    // Step 6. If the previousSibling of the first member of node list is editable
888    // and running sibling criteria on it returns true,
889    // let new parent be the previousSibling of the first member of node list.
890    let new_parent = node_list
891        .first()
892        .and_then(|first| first.GetPreviousSibling())
893        .filter(|previous_of_first| {
894            previous_of_first.is_editable() && sibling_criteria(previous_of_first)
895        });
896    // Step 7. Otherwise, if the nextSibling of the last member of node list is editable
897    // and running sibling criteria on it returns true,
898    // let new parent be the nextSibling of the last member of node list.
899    let new_parent = new_parent.or_else(|| {
900        node_list
901            .last()
902            .and_then(|first| first.GetNextSibling())
903            .filter(|next_of_last| next_of_last.is_editable() && sibling_criteria(next_of_last))
904    });
905    // Step 8. Otherwise, run new parent instructions, and let new parent be the result.
906    // Step 9. If new parent is null, abort these steps and return null.
907    let new_parent = new_parent.or_else(new_parent_instructions)?;
908    // Step 11. Let original parent be the parent of the first member of node list.
909    let first_in_node_list = node_list
910        .first()
911        .expect("Must always have at least one node");
912    let original_parent = first_in_node_list
913        .GetParentNode()
914        .expect("First node must have a parent");
915    // Step 10. If new parent's parent is null:
916    if new_parent.GetParentNode().is_none() {
917        // Step 10.1. Insert new parent into the parent of the first member
918        // of node list immediately before the first member of node list.
919        if original_parent
920            .InsertBefore(cx, &new_parent, Some(first_in_node_list))
921            .is_err()
922        {
923            unreachable!("Must always be able to insert");
924        }
925        // Step 10.2. If any range has a boundary point with node equal
926        // to the parent of new parent and offset equal to the index of new parent,
927        // add one to that boundary point's offset.
928        // TODO
929    }
930    // Step 12. If new parent is before the first member of node list in tree order:
931    if new_parent.is_before(first_in_node_list) {
932        // Step 12.1. If new parent is not an inline node, but the last visible child of new parent
933        // and the first visible member of node list are both inline nodes,
934        // and the last child of new parent is not a br,
935        // call createElement("br") on the ownerDocument of new parent
936        // and append the result as the last child of new parent.
937        // TODO
938        // Step 12.2. For each node in node list, append node as the last child of new parent, preserving ranges.
939        for node in node_list {
940            if new_parent.AppendChild(cx, node).is_err() {
941                unreachable!("Must always be able to append");
942            }
943        }
944    } else {
945        // Step 13. Otherwise:
946        // Step 13.1. If new parent is not an inline node, but the first visible child of new parent
947        // and the last visible member of node list are both inline nodes,
948        // and the last member of node list is not a br,
949        // call createElement("br") on the ownerDocument of new parent
950        // and insert the result as the first child of new parent.
951        // TODO
952        // Step 13.2. For each node in node list, in reverse order,
953        // insert node as the first child of new parent, preserving ranges.
954        let mut before = new_parent.GetFirstChild();
955        for node in node_list.iter().rev() {
956            if let Err(err) = new_parent.InsertBefore(cx, node, before.as_deref()) {
957                unreachable!("Must always be able to append: {:?}", err);
958            }
959            before = Some(DomRoot::from_ref(node));
960        }
961    }
962    // Step 14. If original parent is editable and has no children, remove it from its parent.
963    if original_parent.is_editable() && original_parent.children_count() == 0 {
964        original_parent.remove_self(cx);
965    }
966    // Step 15. If new parent's nextSibling is editable and running sibling criteria on it returns true:
967    // TODO
968    // Step 16. Remove extraneous line breaks from new parent.
969    new_parent.remove_extraneous_line_breaks_from(cx);
970    // Step 17. Return new parent.
971    Some(new_parent)
972}
973
974impl Node {
975    fn resolved_display_value(&self) -> Option<DisplayOutside> {
976        self.style().map(|style| style.get_box().display.outside())
977    }
978
979    /// <https://w3c.github.io/editing/docs/execCommand/#push-down-values>
980    fn push_down_values(
981        &self,
982        cx: &mut js::context::JSContext,
983        command: &CommandName,
984        new_value: Option<DOMString>,
985    ) {
986        // Step 1. Let command be the current command.
987        //
988        // Passed in as argument
989
990        // Step 4. Let current ancestor be node's parent.
991        let mut current_ancestor = self.GetParentElement();
992        // Step 2. If node's parent is not an Element, abort this algorithm.
993        if current_ancestor.is_none() {
994            return;
995        };
996        // Step 3. If the effective command value of command is loosely equivalent to new value on node,
997        // abort this algorithm.
998        if command.are_loosely_equivalent_values(
999            self.effective_command_value(command).as_ref(),
1000            new_value.as_ref(),
1001        ) {
1002            return;
1003        }
1004        // Step 5. Let ancestor list be a list of nodes, initially empty.
1005        rooted_vec!(let mut ancestor_list);
1006        // Step 6. While current ancestor is an editable Element and
1007        // the effective command value of command is not loosely equivalent to new value on it,
1008        // append current ancestor to ancestor list, then set current ancestor to its parent.
1009        while let Some(ancestor) = current_ancestor {
1010            let ancestor_node = ancestor.upcast::<Node>();
1011            if ancestor_node.is_editable() &&
1012                !command.are_loosely_equivalent_values(
1013                    ancestor_node.effective_command_value(command).as_ref(),
1014                    new_value.as_ref(),
1015                )
1016            {
1017                ancestor_list.push(ancestor.clone());
1018                current_ancestor = ancestor_node.GetParentElement();
1019                continue;
1020            }
1021            break;
1022        }
1023        let Some(last_ancestor) = ancestor_list.last() else {
1024            // Step 7. If ancestor list is empty, abort this algorithm.
1025            return;
1026        };
1027        // Step 8. Let propagated value be the specified command value of command on the last member of ancestor list.
1028        let mut propagated_value = last_ancestor.specified_command_value(command);
1029        // Step 9. If propagated value is null and is not equal to new value, abort this algorithm.
1030        if propagated_value.is_none() && new_value.is_some() {
1031            return;
1032        }
1033        // Step 10. If the effective command value of command is not loosely equivalent to new value on the parent
1034        // of the last member of ancestor list, and new value is not null, abort this algorithm.
1035        if new_value.is_some() &&
1036            !last_ancestor
1037                .upcast::<Node>()
1038                .GetParentNode()
1039                .is_some_and(|last_ancestor| {
1040                    command.are_loosely_equivalent_values(
1041                        last_ancestor.effective_command_value(command).as_ref(),
1042                        new_value.as_ref(),
1043                    )
1044                })
1045        {
1046            return;
1047        }
1048        // Step 11. While ancestor list is not empty:
1049        let mut ancestor_list_iter = ancestor_list.iter().rev().peekable();
1050        while let Some(current_ancestor) = ancestor_list_iter.next() {
1051            let current_ancestor_node = current_ancestor.upcast::<Node>();
1052            // Step 11.1. Let current ancestor be the last member of ancestor list.
1053            // Step 11.2. Remove the last member from ancestor list.
1054            //
1055            // Both of these steps done by iterating and reversing the iterator
1056
1057            // Step 11.3. If the specified command value of current ancestor for command is not null, set propagated value to that value.
1058            let command_value = current_ancestor.specified_command_value(command);
1059            let has_command_value = command_value.is_some();
1060            propagated_value = command_value.or(propagated_value);
1061            // Step 11.4. Let children be the children of current ancestor.
1062            let children = current_ancestor_node.children();
1063            // Step 11.5. If the specified command value of current ancestor for command is not null, clear the value of current ancestor.
1064            if has_command_value {
1065                if let Some(html_element) = current_ancestor.downcast::<HTMLElement>() {
1066                    html_element.clear_the_value(cx, command);
1067                }
1068            }
1069            // Step 11.6. For every child in children:
1070            for child in children {
1071                // Step 11.6.1. If child is node, continue with the next child.
1072                if *child == *self {
1073                    continue;
1074                }
1075                // Step 11.6.2. If child is an Element whose specified command value for command is neither null
1076                // nor equivalent to propagated value, continue with the next child.
1077                // TODO
1078
1079                // Step 11.6.3. If child is the last member of ancestor list, continue with the next child.
1080                //
1081                // Since we had to remove the last member in step 11.2, if we now peek at the next possible
1082                // value, we essentially have the "last member after removal"
1083                if ancestor_list_iter
1084                    .peek()
1085                    .is_some_and(|ancestor| *ancestor.upcast::<Node>() == *child)
1086                {
1087                    continue;
1088                }
1089                // step 11.6.4. Force the value of child, with command as in this algorithm and new value equal to propagated value.
1090                child.force_the_value(cx, command, propagated_value.as_ref());
1091            }
1092        }
1093    }
1094
1095    /// <https://w3c.github.io/editing/docs/execCommand/#force-the-value>
1096    pub(crate) fn force_the_value(
1097        &self,
1098        cx: &mut js::context::JSContext,
1099        command: &CommandName,
1100        new_value: Option<&DOMString>,
1101    ) {
1102        // Step 1. Let command be the current command.
1103        //
1104        // That's command
1105
1106        // Step 2. If node's parent is null, abort this algorithm.
1107        if self.GetParentNode().is_none() {
1108            return;
1109        }
1110        // Step 3. If new value is null, abort this algorithm.
1111        let Some(new_value) = new_value else {
1112            return;
1113        };
1114        // Step 4. If node is an allowed child of "span":
1115        if is_allowed_child(
1116            NodeOrString::Node(DomRoot::from_ref(self)),
1117            NodeOrString::String("span".to_owned()),
1118        ) {
1119            // Step 4.1. Reorder modifiable descendants of node's previousSibling.
1120            // TODO
1121            // Step 4.2. Reorder modifiable descendants of node's nextSibling.
1122            // TODO
1123            // Step 4.3. Wrap the one-node list consisting of node,
1124            // with sibling criteria returning true for a simple modifiable element whose
1125            // specified command value is equivalent to new value and whose effective command value
1126            // is loosely equivalent to new value and false otherwise,
1127            // and with new parent instructions returning null.
1128            wrap_node_list(
1129                cx,
1130                &[self],
1131                |sibling| {
1132                    // TODO: Check for simple modifiable
1133                    sibling
1134                        .downcast::<Element>()
1135                        .is_some_and(|sibling_element| {
1136                            command.are_equivalent_values(
1137                                sibling_element.specified_command_value(command).as_ref(),
1138                                Some(new_value),
1139                            ) && command.are_loosely_equivalent_values(
1140                                sibling.effective_command_value(command).as_ref(),
1141                                Some(new_value),
1142                            )
1143                        })
1144                },
1145                || None,
1146            );
1147        }
1148        // Step 5. If node is invisible, abort this algorithm.
1149        if self.is_invisible() {
1150            return;
1151        }
1152        // Step 6. If the effective command value of command is loosely equivalent to new value on node, abort this algorithm.
1153        if command.are_loosely_equivalent_values(
1154            self.effective_command_value(command).as_ref(),
1155            Some(new_value),
1156        ) {
1157            return;
1158        }
1159        // Step 7. If node is not an allowed child of "span":
1160        // TODO
1161        // Step 8. If the effective command value of command is loosely equivalent to new value on node, abort this algorithm.
1162        if command.are_loosely_equivalent_values(
1163            self.effective_command_value(command).as_ref(),
1164            Some(new_value),
1165        ) {
1166            return;
1167        }
1168        // Step 9. Let new parent be null.
1169        let mut new_parent = None;
1170        let document = self.owner_document();
1171        let css_styling_flag = document.css_styling_flag();
1172        // Step 10. If the CSS styling flag is false:
1173        // TODO
1174        match command {
1175            // Step 11. If command is "createLink" or "unlink":
1176            // TODO
1177            // Step 12. If command is "fontSize"; and new value is one of
1178            // "x-small", "small", "medium", "large", "x-large", "xx-large", or "xxx-large";
1179            // and either the CSS styling flag is false, or new value is "xxx-large":
1180            // let new parent be the result of calling createElement("font") on the ownerDocument of node,
1181            // then set the size attribute of new parent to the number from the following table based on new value:
1182            CommandName::FontSize => {
1183                if !css_styling_flag || new_value == "xxx-large" {
1184                    let size = match &*new_value.str() {
1185                        "x-small" => 1,
1186                        "small" => 2,
1187                        "medium" => 3,
1188                        "large" => 4,
1189                        "x-large" => 5,
1190                        "xx-large" => 6,
1191                        "xxx-large" => 7,
1192                        _ => 0,
1193                    };
1194
1195                    if size > 0 {
1196                        let new_font_element = document.create_element(cx, "font");
1197                        new_font_element.set_int_attribute(
1198                            &local_name!("size"),
1199                            size,
1200                            CanGc::from_cx(cx),
1201                        );
1202                        new_parent = Some(new_font_element);
1203                    }
1204                }
1205            },
1206            CommandName::Subscript | CommandName::Superscript => {
1207                // Step 13. If command is "subscript" or "superscript" and new value is "subscript",
1208                // let new parent be the result of calling createElement("sub") on the ownerDocument of node.
1209                if new_value == "subscript" {
1210                    new_parent = Some(document.create_element(cx, "sub"));
1211                }
1212                // Step 14. If command is "subscript" or "superscript" and new value is "superscript",
1213                // let new parent be the result of calling createElement("sup") on the ownerDocument of node.
1214                if new_value == "superscript" {
1215                    new_parent = Some(document.create_element(cx, "sup"));
1216                }
1217            },
1218            _ => {},
1219        }
1220        // Step 15. If new parent is null, let new parent be the result of calling createElement("span") on the ownerDocument of node.
1221        let new_parent = new_parent.unwrap_or_else(|| document.create_element(cx, "span"));
1222        // Step 16. Insert new parent in node's parent before node.
1223        if self
1224            .GetParentNode()
1225            .expect("Must always have a parent")
1226            .InsertBefore(cx, new_parent.upcast(), Some(self))
1227            .is_err()
1228        {
1229            unreachable!("Must always be able to insert");
1230        }
1231        // Step 17. If the effective command value of command for new parent is not loosely equivalent to new value,
1232        // and the relevant CSS property for command is not null,
1233        // set that CSS property of new parent to new value (if the new value would be valid).
1234        if !command.are_loosely_equivalent_values(
1235            self.effective_command_value(command).as_ref(),
1236            Some(new_value),
1237        ) {
1238            if let Some(css_property) = command.relevant_css_property() {
1239                css_property.set_for_element(
1240                    cx,
1241                    new_parent
1242                        .downcast::<HTMLElement>()
1243                        .expect("Must always create a HTML element"),
1244                    new_value.clone(),
1245                );
1246            }
1247        }
1248        // Step 18. If command is "strikethrough", and new value is "line-through",
1249        // and the effective command value of "strikethrough" for new parent is not "line-through",
1250        // set the "text-decoration" property of new parent to "line-through".
1251        // TODO
1252        // Step 19. If command is "underline", and new value is "underline",
1253        // and the effective command value of "underline" for new parent is not "underline",
1254        // set the "text-decoration" property of new parent to "underline".
1255        // TODO
1256        // Step 20. Append node to new parent as its last child, preserving ranges.
1257        let new_parent = new_parent.upcast::<Node>();
1258        if new_parent.AppendChild(cx, self).is_err() {
1259            unreachable!("Must always be able to append");
1260        }
1261        // Step 21. If node is an Element and the effective command value of command for node is not loosely equivalent to new value:
1262        if self.is::<Element>() &&
1263            !command.are_loosely_equivalent_values(
1264                self.effective_command_value(command).as_ref(),
1265                Some(new_value),
1266            )
1267        {
1268            // Step 21.1. Insert node into the parent of new parent before new parent, preserving ranges.
1269            let parent_of_new_parent = new_parent.GetParentNode().expect("Must have a parent");
1270            if parent_of_new_parent
1271                .InsertBefore(cx, self, Some(new_parent))
1272                .is_err()
1273            {
1274                unreachable!("Must always be able to insert");
1275            }
1276            // Step 21.2. Remove new parent from its parent.
1277            new_parent.remove_self(cx);
1278            // Step 21.3. Let children be all children of node,
1279            // omitting any that are Elements whose specified command value for command is neither null nor equivalent to new value.
1280            for child in self.children() {
1281                if child.downcast::<Element>().is_some_and(|child_element| {
1282                    let specified_command_value = child_element.specified_command_value(command);
1283                    specified_command_value.is_some() &&
1284                        !command.are_equivalent_values(
1285                            specified_command_value.as_ref(),
1286                            Some(new_value),
1287                        )
1288                }) {
1289                    continue;
1290                }
1291                // Step 21.4. Force the value of each node in children,
1292                // with command and new value as in this invocation of the algorithm.
1293                child.force_the_value(cx, command, Some(new_value));
1294            }
1295        }
1296    }
1297}
1298
1299pub(crate) trait NodeExecCommandSupport {
1300    fn same_editing_host(&self, other: &Node) -> bool;
1301    fn is_block_node(&self) -> bool;
1302    fn is_inline_node(&self) -> bool;
1303    fn block_node_of(&self) -> Option<DomRoot<Node>>;
1304    fn is_visible(&self) -> bool;
1305    fn is_invisible(&self) -> bool;
1306    fn is_formattable(&self) -> bool;
1307    fn is_block_start_point(&self, offset: usize) -> bool;
1308    fn is_block_end_point(&self, offset: u32) -> bool;
1309    fn is_block_boundary_point(&self, offset: u32) -> bool;
1310    fn is_collapsed_block_prop(&self) -> bool;
1311    fn follows_a_line_break(&self) -> bool;
1312    fn precedes_a_line_break(&self) -> bool;
1313    fn canonical_space_sequence(
1314        n: usize,
1315        non_breaking_start: bool,
1316        non_breaking_end: bool,
1317    ) -> String;
1318    fn canonicalize_whitespace(&self, offset: u32, fix_collapsed_space: bool);
1319    fn remove_extraneous_line_breaks_before(&self, cx: &mut js::context::JSContext);
1320    fn remove_extraneous_line_breaks_at_the_end_of(&self, cx: &mut js::context::JSContext);
1321    fn remove_extraneous_line_breaks_from(&self, cx: &mut js::context::JSContext);
1322    fn remove_preserving_its_descendants(&self, cx: &mut js::context::JSContext);
1323    fn effective_command_value(&self, command: &CommandName) -> Option<DOMString>;
1324}
1325
1326impl NodeExecCommandSupport for Node {
1327    /// <https://w3c.github.io/editing/docs/execCommand/#in-the-same-editing-host>
1328    fn same_editing_host(&self, other: &Node) -> bool {
1329        // > Two nodes are in the same editing host if the editing host of the first is non-null and the same as the editing host of the second.
1330        self.editing_host_of()
1331            .is_some_and(|editing_host| other.editing_host_of() == Some(editing_host))
1332    }
1333
1334    /// <https://w3c.github.io/editing/docs/execCommand/#block-node>
1335    fn is_block_node(&self) -> bool {
1336        // > A block node is either an Element whose "display" property does not have resolved value "inline" or "inline-block" or "inline-table" or "none",
1337        if self.is::<Element>() &&
1338            self.resolved_display_value().is_some_and(|display| {
1339                display != DisplayOutside::Inline && display != DisplayOutside::None
1340            })
1341        {
1342            return true;
1343        }
1344        // > or a document, or a DocumentFragment.
1345        matches!(
1346            self.type_id(),
1347            NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_)
1348        )
1349    }
1350
1351    /// <https://w3c.github.io/editing/docs/execCommand/#inline-node>
1352    fn is_inline_node(&self) -> bool {
1353        // > An inline node is a node that is not a block node.
1354        !self.is_block_node()
1355    }
1356
1357    /// <https://w3c.github.io/editing/docs/execCommand/#block-node-of>
1358    fn block_node_of(&self) -> Option<DomRoot<Node>> {
1359        let mut node = DomRoot::from_ref(self);
1360
1361        loop {
1362            // Step 1. While node is an inline node, set node to its parent.
1363            if node.is_inline_node() {
1364                node = node.GetParentNode()?;
1365                continue;
1366            }
1367            // Step 2. Return node.
1368            return Some(node);
1369        }
1370    }
1371
1372    /// <https://w3c.github.io/editing/docs/execCommand/#visible>
1373    fn is_visible(&self) -> bool {
1374        for parent in self.inclusive_ancestors(ShadowIncluding::No) {
1375            // > excluding any node with an inclusive ancestor Element whose "display" property has resolved value "none".
1376            if parent.is::<Element>() &&
1377                parent
1378                    .resolved_display_value()
1379                    .is_some_and(|display| display == DisplayOutside::None)
1380            {
1381                return false;
1382            }
1383        }
1384        // > Something is visible if it is a node that either is a block node,
1385        if self.is_block_node() {
1386            return true;
1387        }
1388        // > or a Text node that is not a collapsed whitespace node,
1389        if self
1390            .downcast::<Text>()
1391            .is_some_and(|text| !text.is_collapsed_whitespace_node())
1392        {
1393            return true;
1394        }
1395        // > or an img, or a br that is not an extraneous line break, or any node with a visible descendant;
1396        if self.is::<HTMLImageElement>() {
1397            return true;
1398        }
1399        if self
1400            .downcast::<HTMLBRElement>()
1401            .is_some_and(|br| !br.is_extraneous_line_break())
1402        {
1403            return true;
1404        }
1405        for child in self.children() {
1406            if child.is_visible() {
1407                return true;
1408            }
1409        }
1410        false
1411    }
1412
1413    /// <https://w3c.github.io/editing/docs/execCommand/#invisible>
1414    fn is_invisible(&self) -> bool {
1415        // > Something is invisible if it is a node that is not visible.
1416        !self.is_visible()
1417    }
1418
1419    /// <https://w3c.github.io/editing/docs/execCommand/#formattable-node>
1420    fn is_formattable(&self) -> bool {
1421        // > A formattable node is an editable visible node that is either a Text node, an img, or a br.
1422        self.is_editable() &&
1423            self.is_visible() &&
1424            (self.is::<Text>() || self.is::<HTMLImageElement>() || self.is::<HTMLBRElement>())
1425    }
1426
1427    /// <https://w3c.github.io/editing/docs/execCommand/#block-start-point>
1428    fn is_block_start_point(&self, offset: usize) -> bool {
1429        // > A boundary point (node, offset) is a block start point if either node's parent is null and offset is zero;
1430        if offset == 0 {
1431            return self.GetParentNode().is_none();
1432        }
1433        // > or node has a child with index offset − 1, and that child is either a visible block node or a visible br.
1434        self.children().nth(offset - 1).is_some_and(|child| {
1435            child.is_visible() && (child.is_block_node() || child.is::<HTMLBRElement>())
1436        })
1437    }
1438
1439    /// <https://w3c.github.io/editing/docs/execCommand/#block-end-point>
1440    fn is_block_end_point(&self, offset: u32) -> bool {
1441        // > A boundary point (node, offset) is a block end point if either node's parent is null and offset is node's length;
1442        if self.GetParentNode().is_none() && offset == self.len() {
1443            return true;
1444        }
1445        // > or node has a child with index offset, and that child is a visible block node.
1446        self.children()
1447            .nth(offset as usize)
1448            .is_some_and(|child| child.is_visible() && child.is_block_node())
1449    }
1450
1451    /// <https://w3c.github.io/editing/docs/execCommand/#block-boundary-point>
1452    fn is_block_boundary_point(&self, offset: u32) -> bool {
1453        // > A boundary point is a block boundary point if it is either a block start point or a block end point.
1454        self.is_block_start_point(offset as usize) || self.is_block_end_point(offset)
1455    }
1456
1457    /// <https://w3c.github.io/editing/docs/execCommand/#collapsed-block-prop>
1458    fn is_collapsed_block_prop(&self) -> bool {
1459        // > A collapsed block prop is either a collapsed line break that is not an extraneous line break,
1460
1461        // TODO: Check for collapsed line break
1462        if self
1463            .downcast::<HTMLBRElement>()
1464            .is_some_and(|br| !br.is_extraneous_line_break())
1465        {
1466            return true;
1467        }
1468        // > or an Element that is an inline node and whose children are all either invisible or collapsed block props
1469        if !self.is::<Element>() {
1470            return false;
1471        };
1472        if !self.is_inline_node() {
1473            return false;
1474        }
1475        let mut at_least_one_collapsed_block_prop = false;
1476        for child in self.children() {
1477            if child.is_collapsed_block_prop() {
1478                at_least_one_collapsed_block_prop = true;
1479                continue;
1480            }
1481            if child.is_invisible() {
1482                continue;
1483            }
1484
1485            return false;
1486        }
1487        // > and that has at least one child that is a collapsed block prop.
1488        at_least_one_collapsed_block_prop
1489    }
1490
1491    /// <https://w3c.github.io/editing/docs/execCommand/#follows-a-line-break>
1492    fn follows_a_line_break(&self) -> bool {
1493        // Step 1. Let offset be zero.
1494        let mut offset = 0;
1495        // Step 2. While (node, offset) is not a block boundary point:
1496        let mut node = DomRoot::from_ref(self);
1497        while !node.is_block_boundary_point(offset) {
1498            // Step 2.2. If offset is zero or node has no children, set offset to node's index, then set node to its parent.
1499            if offset == 0 || node.children_count() == 0 {
1500                offset = node.index();
1501                node = node.GetParentNode().expect("Must always have a parent");
1502                continue;
1503            }
1504            // Step 2.1. If node has a visible child with index offset minus one, return false.
1505            let child = node.children().nth(offset as usize - 1);
1506            let Some(child) = child else {
1507                return false;
1508            };
1509            if child.is_visible() {
1510                return false;
1511            }
1512            // Step 2.3. Otherwise, set node to its child with index offset minus one, then set offset to node's length.
1513            node = child;
1514            offset = node.len();
1515        }
1516        // Step 3. Return true.
1517        true
1518    }
1519
1520    /// <https://w3c.github.io/editing/docs/execCommand/#precedes-a-line-break>
1521    fn precedes_a_line_break(&self) -> bool {
1522        let mut node = DomRoot::from_ref(self);
1523        // Step 1. Let offset be node's length.
1524        let mut offset = node.len();
1525        // Step 2. While (node, offset) is not a block boundary point:
1526        while !node.is_block_boundary_point(offset) {
1527            // Step 2.1. If node has a visible child with index offset, return false.
1528            if node
1529                .children()
1530                .nth(offset as usize)
1531                .is_some_and(|child| child.is_visible())
1532            {
1533                return false;
1534            }
1535            // Step 2.2. If offset is node's length or node has no children, set offset to one plus node's index, then set node to its parent.
1536            if offset == node.len() || node.children_count() == 0 {
1537                offset = 1 + node.index();
1538                node = node.GetParentNode().expect("Must always have a parent");
1539                continue;
1540            }
1541            // Step 2.3. Otherwise, set node to its child with index offset and set offset to zero.
1542            let child = node.children().nth(offset as usize);
1543            node = match child {
1544                None => return false,
1545                Some(child) => child,
1546            };
1547            offset = 0;
1548        }
1549        // Step 3. Return true.
1550        true
1551    }
1552
1553    /// <https://w3c.github.io/editing/docs/execCommand/#canonical-space-sequence>
1554    fn canonical_space_sequence(
1555        n: usize,
1556        non_breaking_start: bool,
1557        non_breaking_end: bool,
1558    ) -> String {
1559        let mut n = n;
1560        // Step 1. If n is zero, return the empty string.
1561        if n == 0 {
1562            return String::new();
1563        }
1564        // Step 2. If n is one and both non-breaking start and non-breaking end are false, return a single space (U+0020).
1565        if n == 1 {
1566            if !non_breaking_start && !non_breaking_end {
1567                return "\u{0020}".to_owned();
1568            }
1569            // Step 3. If n is one, return a single non-breaking space (U+00A0).
1570            return "\u{00A0}".to_owned();
1571        }
1572        // Step 4. Let buffer be the empty string.
1573        let mut buffer = String::new();
1574        // Step 5. If non-breaking start is true, let repeated pair be U+00A0 U+0020. Otherwise, let it be U+0020 U+00A0.
1575        let repeated_pair = if non_breaking_start {
1576            "\u{00A0}\u{0020}"
1577        } else {
1578            "\u{0020}\u{00A0}"
1579        };
1580        // Step 6. While n is greater than three, append repeated pair to buffer and subtract two from n.
1581        while n > 3 {
1582            buffer.push_str(repeated_pair);
1583            n -= 2;
1584        }
1585        // Step 7. If n is three, append a three-code unit string to buffer depending on non-breaking start and non-breaking end:
1586        if n == 3 {
1587            buffer.push_str(match (non_breaking_start, non_breaking_end) {
1588                (false, false) => "\u{0020}\u{00A0}\u{0020}",
1589                (true, false) => "\u{00A0}\u{00A0}\u{0020}",
1590                (false, true) => "\u{0020}\u{00A0}\u{00A0}",
1591                (true, true) => "\u{00A0}\u{0020}\u{00A0}",
1592            });
1593        } else {
1594            // Step 8. Otherwise, append a two-code unit string to buffer depending on non-breaking start and non-breaking end:
1595            buffer.push_str(match (non_breaking_start, non_breaking_end) {
1596                (false, false) | (true, false) => "\u{00A0}\u{0020}",
1597                (false, true) => "\u{0020}\u{00A0}",
1598                (true, true) => "\u{00A0}\u{00A0}",
1599            });
1600        }
1601        // Step 9. Return buffer.
1602        buffer
1603    }
1604
1605    /// <https://w3c.github.io/editing/docs/execCommand/#canonicalize-whitespace>
1606    fn canonicalize_whitespace(&self, offset: u32, fix_collapsed_space: bool) {
1607        // Step 1. If node is neither editable nor an editing host, abort these steps.
1608        if !self.is_editable_or_editing_host() {
1609            return;
1610        }
1611        // Step 2. Let start node equal node and let start offset equal offset.
1612        let mut start_node = DomRoot::from_ref(self);
1613        let mut start_offset = offset;
1614        // Step 3. Repeat the following steps:
1615        loop {
1616            // Step 3.1. If start node has a child in the same editing host with index start offset minus one,
1617            // set start node to that child, then set start offset to start node's length.
1618            if start_offset > 0 {
1619                let child = start_node.children().nth(start_offset as usize - 1);
1620                if let Some(child) = child {
1621                    if start_node.same_editing_host(&child) {
1622                        start_node = child;
1623                        start_offset = start_node.len();
1624                        continue;
1625                    }
1626                };
1627            }
1628            // Step 3.2. Otherwise, if start offset is zero and start node does not follow a line break
1629            // and start node's parent is in the same editing host, set start offset to start node's index,
1630            // then set start node to its parent.
1631            if start_offset == 0 && !start_node.follows_a_line_break() {
1632                if let Some(parent) = start_node.GetParentNode() {
1633                    if parent.same_editing_host(&start_node) {
1634                        start_offset = start_node.index();
1635                        start_node = parent;
1636                    }
1637                }
1638            }
1639            // Step 3.3. Otherwise, if start node is a Text node and its parent's resolved
1640            // value for "white-space" is neither "pre" nor "pre-wrap" and start offset is not zero
1641            // and the (start offset − 1)st code unit of start node's data is a space (0x0020) or
1642            // non-breaking space (0x00A0), subtract one from start offset.
1643            if start_offset != 0 &&
1644                start_node.downcast::<Text>().is_some_and(|text| {
1645                    text.has_whitespace_and_has_parent_with_whitespace_preserve(
1646                        start_offset - 1,
1647                        &[&'\u{0020}', &'\u{00A0}'],
1648                    )
1649                })
1650            {
1651                start_offset -= 1;
1652            }
1653            // Step 3.4. Otherwise, break from this loop.
1654            break;
1655        }
1656        // Step 4. Let end node equal start node and end offset equal start offset.
1657        let mut end_node = start_node.clone();
1658        let mut end_offset = start_offset;
1659        // Step 5. Let length equal zero.
1660        let mut length = 0;
1661        // Step 6. Let collapse spaces be true if start offset is zero and start node follows a line break, otherwise false.
1662        let mut collapse_spaces = start_offset == 0 && start_node.follows_a_line_break();
1663        // Step 7. Repeat the following steps:
1664        loop {
1665            // Step 7.1. If end node has a child in the same editing host with index end offset,
1666            // set end node to that child, then set end offset to zero.
1667            if let Some(child) = end_node.children().nth(end_offset as usize) {
1668                if child.same_editing_host(&end_node) {
1669                    end_node = child;
1670                    end_offset = 0;
1671                    continue;
1672                }
1673            }
1674            // Step 7.2. Otherwise, if end offset is end node's length
1675            // and end node does not precede a line break
1676            // and end node's parent is in the same editing host,
1677            // set end offset to one plus end node's index, then set end node to its parent.
1678            if end_offset == end_node.len() && !end_node.precedes_a_line_break() {
1679                if let Some(parent) = end_node.GetParentNode() {
1680                    if parent.same_editing_host(&end_node) {
1681                        end_offset = 1 + end_node.index();
1682                        end_node = parent;
1683                    }
1684                }
1685                continue;
1686            }
1687            // Step 7.3. Otherwise, if end node is a Text node and its parent's resolved value for "white-space"
1688            // is neither "pre" nor "pre-wrap"
1689            // and end offset is not end node's length and the end offsetth code unit of end node's data
1690            // is a space (0x0020) or non-breaking space (0x00A0):
1691            if let Some(text) = end_node.downcast::<Text>() {
1692                if text.has_whitespace_and_has_parent_with_whitespace_preserve(
1693                    end_offset,
1694                    &[&'\u{0020}', &'\u{00A0}'],
1695                ) {
1696                    // Step 7.3.1. If fix collapsed space is true, and collapse spaces is true,
1697                    // and the end offsetth code unit of end node's data is a space (0x0020):
1698                    // call deleteData(end offset, 1) on end node, then continue this loop from the beginning.
1699                    let has_space_at_offset = text
1700                        .data()
1701                        .chars()
1702                        .nth(end_offset as usize)
1703                        .is_some_and(|c| c == '\u{0020}');
1704                    if fix_collapsed_space && collapse_spaces && has_space_at_offset {
1705                        if text
1706                            .upcast::<CharacterData>()
1707                            .DeleteData(end_offset, 1)
1708                            .is_err()
1709                        {
1710                            unreachable!("Invalid deletion for character at end offset");
1711                        }
1712                        continue;
1713                    }
1714                    // Step 7.3.2. Set collapse spaces to true if the end offsetth code unit of
1715                    // end node's data is a space (0x0020), false otherwise.
1716                    collapse_spaces = text
1717                        .data()
1718                        .chars()
1719                        .nth(end_offset as usize)
1720                        .is_some_and(|c| c == '\u{0020}');
1721                    // Step 7.3.3. Add one to end offset.
1722                    end_offset += 1;
1723                    // Step 7.3.4. Add one to length.
1724                    length += 1;
1725                    continue;
1726                }
1727            }
1728            // Step 7.4. Otherwise, break from this loop.
1729            break;
1730        }
1731        // Step 8. If fix collapsed space is true, then while (start node, start offset)
1732        // is before (end node, end offset):
1733        if fix_collapsed_space {
1734            while bp_position(&start_node, start_offset, &end_node, end_offset) ==
1735                Some(Ordering::Less)
1736            {
1737                // Step 8.1. If end node has a child in the same editing host with index end offset − 1,
1738                // set end node to that child, then set end offset to end node's length.
1739                if end_offset > 0 {
1740                    if let Some(child) = end_node.children().nth(end_offset as usize - 1) {
1741                        if child.same_editing_host(&end_node) {
1742                            end_node = child;
1743                            end_offset = end_node.len();
1744                            continue;
1745                        }
1746                    }
1747                }
1748                // Step 8.2. Otherwise, if end offset is zero and end node's parent is in the same editing host,
1749                // set end offset to end node's index, then set end node to its parent.
1750                if let Some(parent) = end_node.GetParentNode() {
1751                    if end_offset == 0 && parent.same_editing_host(&end_node) {
1752                        end_offset = end_node.index();
1753                        end_node = parent;
1754                        continue;
1755                    }
1756                }
1757                // Step 8.3. Otherwise, if end node is a Text node and its parent's resolved value for "white-space"
1758                // is neither "pre" nor "pre-wrap"
1759                // and end offset is end node's length and the last code unit of end node's data
1760                // is a space (0x0020) and end node precedes a line break:
1761                if let Some(text) = end_node.downcast::<Text>() {
1762                    if text.has_whitespace_and_has_parent_with_whitespace_preserve(
1763                        text.data().len() as u32,
1764                        &[&'\u{0020}'],
1765                    ) && end_node.precedes_a_line_break()
1766                    {
1767                        // Step 8.3.1. Subtract one from end offset.
1768                        end_offset -= 1;
1769                        // Step 8.3.2. Subtract one from length.
1770                        length -= 1;
1771                        // Step 8.3.3. Call deleteData(end offset, 1) on end node.
1772                        if text
1773                            .upcast::<CharacterData>()
1774                            .DeleteData(end_offset, 1)
1775                            .is_err()
1776                        {
1777                            unreachable!("Invalid deletion for character at end offset");
1778                        }
1779                        continue;
1780                    }
1781                }
1782                // Step 8.4. Otherwise, break from this loop.
1783                break;
1784            }
1785        }
1786        // Step 9. Let replacement whitespace be the canonical space sequence of length length.
1787        // non-breaking start is true if start offset is zero and start node follows a line break, and false otherwise.
1788        // non-breaking end is true if end offset is end node's length and end node precedes a line break, and false otherwise.
1789        let replacement_whitespace = Node::canonical_space_sequence(
1790            length,
1791            start_offset == 0 && start_node.follows_a_line_break(),
1792            end_offset == end_node.len() && end_node.precedes_a_line_break(),
1793        );
1794        let mut replacement_whitespace_chars = replacement_whitespace.chars();
1795        // Step 10. While (start node, start offset) is before (end node, end offset):
1796        while bp_position(&start_node, start_offset, &end_node, end_offset) == Some(Ordering::Less)
1797        {
1798            // Step 10.1. If start node has a child with index start offset, set start node to that child, then set start offset to zero.
1799            if let Some(child) = start_node.children().nth(start_offset as usize) {
1800                start_node = child;
1801                start_offset = 0;
1802                continue;
1803            }
1804            // Step 10.2. Otherwise, if start node is not a Text node or if start offset is start node's length,
1805            // set start offset to one plus start node's index, then set start node to its parent.
1806            let start_node_as_text = start_node.downcast::<Text>();
1807            if start_node_as_text.is_none() || start_offset == start_node.len() {
1808                start_offset = 1 + start_node.index();
1809                start_node = start_node
1810                    .GetParentNode()
1811                    .expect("Must always have a parent");
1812                continue;
1813            }
1814            let start_node_as_text =
1815                start_node_as_text.expect("Already verified none in previous statement");
1816            // Step 10.3. Otherwise:
1817            // Step 10.3.1. Remove the first code unit from replacement whitespace, and let element be that code unit.
1818            if let Some(element) = replacement_whitespace_chars.next() {
1819                // Step 10.3.2. If element is not the same as the start offsetth code unit of start node's data:
1820                if start_node_as_text.data().chars().nth(start_offset as usize) != Some(element) {
1821                    let character_data = start_node_as_text.upcast::<CharacterData>();
1822                    // Step 10.3.2.1. Call insertData(start offset, element) on start node.
1823                    if character_data
1824                        .InsertData(start_offset, element.to_string().into())
1825                        .is_err()
1826                    {
1827                        unreachable!("Invalid insertion for character at start offset");
1828                    }
1829                    // Step 10.3.2.2. Call deleteData(start offset + 1, 1) on start node.
1830                    if character_data.DeleteData(start_offset + 1, 1).is_err() {
1831                        unreachable!("Invalid deletion for character at start offset + 1");
1832                    }
1833                }
1834            }
1835            // Step 10.3.3. Add one to start offset.
1836            start_offset += 1;
1837        }
1838    }
1839
1840    /// <https://w3c.github.io/editing/docs/execCommand/#remove-extraneous-line-breaks-before>
1841    fn remove_extraneous_line_breaks_before(&self, cx: &mut js::context::JSContext) {
1842        let parent = self.GetParentNode();
1843        // Step 1. Let ref be the previousSibling of node.
1844        let Some(mut ref_) = self.GetPreviousSibling() else {
1845            // Step 2. If ref is null, abort these steps.
1846            return;
1847        };
1848        // Step 3. While ref has children, set ref to its lastChild.
1849        while let Some(last_child) = ref_.children().last() {
1850            ref_ = last_child;
1851        }
1852        // Step 4. While ref is invisible but not an extraneous line break,
1853        // and ref does not equal node's parent, set ref to the node before it in tree order.
1854        loop {
1855            if ref_.is_invisible() &&
1856                ref_.downcast::<HTMLBRElement>()
1857                    .is_none_or(|br| !br.is_extraneous_line_break())
1858            {
1859                if let Some(parent) = parent.as_ref() {
1860                    if ref_ != *parent {
1861                        ref_ = match ref_.preceding_nodes(parent).nth(0) {
1862                            None => break,
1863                            Some(node) => node,
1864                        };
1865                        continue;
1866                    }
1867                }
1868            }
1869            break;
1870        }
1871        // Step 5. If ref is an editable extraneous line break, remove it from its parent.
1872        if ref_.is_editable() &&
1873            ref_.downcast::<HTMLBRElement>()
1874                .is_some_and(|br| br.is_extraneous_line_break())
1875        {
1876            assert!(ref_.has_parent());
1877            ref_.remove_self(cx);
1878        }
1879    }
1880
1881    /// <https://w3c.github.io/editing/docs/execCommand/#remove-extraneous-line-breaks-at-the-end-of>
1882    fn remove_extraneous_line_breaks_at_the_end_of(&self, cx: &mut js::context::JSContext) {
1883        // Step 1. Let ref be node.
1884        let mut ref_ = DomRoot::from_ref(self);
1885        // Step 2. While ref has children, set ref to its lastChild.
1886        while let Some(last_child) = ref_.children().last() {
1887            ref_ = last_child;
1888        }
1889        // Step 3. While ref is invisible but not an extraneous line break, and ref does not equal node,
1890        // set ref to the node before it in tree order.
1891        loop {
1892            if ref_.is_invisible() &&
1893                *ref_ != *self &&
1894                ref_.downcast::<HTMLBRElement>()
1895                    .is_none_or(|br| !br.is_extraneous_line_break())
1896            {
1897                if let Some(parent_of_ref) = ref_.GetParentNode() {
1898                    ref_ = match ref_.preceding_nodes(&parent_of_ref).nth(0) {
1899                        None => break,
1900                        Some(node) => node,
1901                    };
1902                    continue;
1903                }
1904            }
1905            break;
1906        }
1907        // Step 4. If ref is an editable extraneous line break:
1908        if ref_.is_editable() &&
1909            ref_.downcast::<HTMLBRElement>()
1910                .is_some_and(|br| br.is_extraneous_line_break())
1911        {
1912            // Step 4.1. While ref's parent is editable and invisible, set ref to its parent.
1913            loop {
1914                if let Some(parent) = ref_.GetParentNode() {
1915                    if parent.is_editable() && parent.is_invisible() {
1916                        ref_ = parent;
1917                        continue;
1918                    }
1919                }
1920                break;
1921            }
1922            // Step 4.2. Remove ref from its parent.
1923            assert!(ref_.has_parent());
1924            ref_.remove_self(cx);
1925        }
1926    }
1927
1928    /// <https://w3c.github.io/editing/docs/execCommand/#remove-extraneous-line-breaks-from>
1929    fn remove_extraneous_line_breaks_from(&self, cx: &mut js::context::JSContext) {
1930        // > To remove extraneous line breaks from a node, first remove extraneous line breaks before it,
1931        // > then remove extraneous line breaks at the end of it.
1932        self.remove_extraneous_line_breaks_before(cx);
1933        self.remove_extraneous_line_breaks_at_the_end_of(cx);
1934    }
1935
1936    /// <https://w3c.github.io/editing/docs/execCommand/#preserving-its-descendants>
1937    fn remove_preserving_its_descendants(&self, cx: &mut js::context::JSContext) {
1938        // > To remove a node node while preserving its descendants,
1939        // > split the parent of node's children if it has any.
1940        // > If it has no children, instead remove it from its parent.
1941        if self.children_count() == 0 {
1942            assert!(self.has_parent());
1943            self.remove_self(cx);
1944        } else {
1945            rooted_vec!(let children <- self.children().map(|child| DomRoot::as_traced(&child)));
1946            split_the_parent(cx, children.r());
1947        }
1948    }
1949
1950    /// <https://w3c.github.io/editing/docs/execCommand/#effective-command-value>
1951    fn effective_command_value(&self, command: &CommandName) -> Option<DOMString> {
1952        // Step 1. If neither node nor its parent is an Element, return null.
1953        // Step 2. If node is not an Element, return the effective command value of its parent for command.
1954        let Some(element) = self.downcast::<Element>() else {
1955            return self
1956                .GetParentElement()
1957                .and_then(|parent| parent.upcast::<Node>().effective_command_value(command));
1958        };
1959        match command {
1960            // Step 3. If command is "createLink" or "unlink":
1961            CommandName::CreateLink | CommandName::Unlink => {
1962                // Step 3.1. While node is not null, and is not an a element that has an href attribute, set node to its parent.
1963                let mut current_node = Some(DomRoot::from_ref(self));
1964                while let Some(node) = current_node {
1965                    if let Some(anchor_value) =
1966                        node.downcast::<HTMLAnchorElement>().and_then(|anchor| {
1967                            anchor
1968                                .upcast::<Element>()
1969                                .get_attribute(&local_name!("href"))
1970                        })
1971                    {
1972                        // Step 3.3. Return the value of node's href attribute.
1973                        return Some(DOMString::from(&**anchor_value.value()));
1974                    }
1975                    current_node = node.GetParentNode();
1976                }
1977                // Step 3.2. If node is null, return null.
1978                None
1979            },
1980            // Step 4. If command is "backColor" or "hiliteColor":
1981            CommandName::BackColor | CommandName::HiliteColor => {
1982                // Step 4.1. While the resolved value of "background-color" on node is any fully transparent value,
1983                // and node's parent is an Element, set node to its parent.
1984                // TODO
1985                // Step 4.2. Return the resolved value of "background-color" for node.
1986                // TODO
1987                None
1988            },
1989            // Step 5. If command is "subscript" or "superscript":
1990            CommandName::Subscript | CommandName::Superscript => {
1991                // Step 5.1. Let affected by subscript and affected by superscript be two boolean variables,
1992                // both initially false.
1993                let mut affected_by_subscript = false;
1994                let mut affected_by_superscript = false;
1995                // Step 5.2. While node is an inline node:
1996                let mut current_node = Some(DomRoot::from_ref(self));
1997                while let Some(node) = current_node {
1998                    if !node.is_inline_node() {
1999                        break;
2000                    }
2001                    // Step 5.2.1. If node is a sub, set affected by subscript to true.
2002                    if *element.local_name() == local_name!("sub") {
2003                        affected_by_subscript = true;
2004                    } else if *element.local_name() == local_name!("sup") {
2005                        // Step 5.2.2. Otherwise, if node is a sup, set affected by superscript to true.
2006                        affected_by_superscript = true;
2007                    }
2008                    // Step 5.2.3. Set node to its parent.
2009                    current_node = node.GetParentNode();
2010                }
2011                Some(match (affected_by_subscript, affected_by_superscript) {
2012                    // Step 5.3. If affected by subscript and affected by superscript are both true,
2013                    // return the string "mixed".
2014                    (true, true) => "mixed".into(),
2015                    // Step 5.4. If affected by subscript is true, return "subscript".
2016                    (true, false) => "subscript".into(),
2017                    // Step 5.5. If affected by superscript is true, return "superscript".
2018                    (false, true) => "superscript".into(),
2019                    // Step 5.6. Return null.
2020                    (false, false) => return None,
2021                })
2022            },
2023            // Step 6. If command is "strikethrough",
2024            // and the "text-decoration" property of node or any of its ancestors has resolved value containing "line-through",
2025            // return "line-through". Otherwise, return null.
2026            // TODO
2027            CommandName::Strikethrough => None,
2028            // Step 7. If command is "underline",
2029            // and the "text-decoration" property of node or any of its ancestors has resolved value containing "underline",
2030            // return "underline". Otherwise, return null.
2031            // TODO
2032            CommandName::Underline => None,
2033            // Step 8. Return the resolved value for node of the relevant CSS property for command.
2034            _ => command.resolved_value_for_node(element),
2035        }
2036    }
2037}
2038
2039pub(crate) trait ContentEditableRange {
2040    fn handle_focus_state_for_contenteditable(&self, can_gc: CanGc);
2041}
2042
2043impl ContentEditableRange for HTMLElement {
2044    /// There is no specification for this implementation. Instead, it is
2045    /// reverse-engineered based on the WPT test
2046    /// /selection/contenteditable/initial-selection-on-focus.tentative.html
2047    fn handle_focus_state_for_contenteditable(&self, can_gc: CanGc) {
2048        if !self.is_editing_host() {
2049            return;
2050        }
2051        let document = self.owner_document();
2052        let Some(selection) = document.GetSelection(can_gc) else {
2053            return;
2054        };
2055        let range = self
2056            .upcast::<Element>()
2057            .ensure_contenteditable_selection_range(&document, can_gc);
2058        // If the current range is already associated with this contenteditable
2059        // element, then we shouldn't do anything. This is important when focus
2060        // is lost and regained, but selection was changed beforehand. In that
2061        // case, we should maintain the selection as it were, by not creating
2062        // a new range.
2063        if selection
2064            .active_range()
2065            .is_some_and(|active| active == range)
2066        {
2067            return;
2068        }
2069        let node = self.upcast::<Node>();
2070        let mut selected_node = DomRoot::from_ref(node);
2071        let mut previous_eligible_node = DomRoot::from_ref(node);
2072        let mut previous_node = DomRoot::from_ref(node);
2073        let mut selected_offset = 0;
2074        for child in node.traverse_preorder(ShadowIncluding::Yes) {
2075            if let Some(text) = child.downcast::<Text>() {
2076                // Note that to consider it whitespace, it needs to take more
2077                // into account than simply "it has a non-whitespace" character.
2078                // Therefore, we need to first check if it is not a whitespace
2079                // node and only then can we find what the relevant character is.
2080                if !text.is_whitespace_node() {
2081                    // A node with "white-space: pre" set must select its first
2082                    // character, regardless if that's a whitespace character or not.
2083                    let is_pre_formatted_text_node = child
2084                        .GetParentElement()
2085                        .and_then(|parent| parent.style())
2086                        .is_some_and(|style| {
2087                            style.get_inherited_text().white_space_collapse ==
2088                                WhiteSpaceCollapse::Preserve
2089                        });
2090                    if !is_pre_formatted_text_node {
2091                        // If it isn't pre-formatted, then we should instead select the
2092                        // first non-whitespace character.
2093                        selected_offset = text
2094                            .data()
2095                            .find(|c: char| !c.is_whitespace())
2096                            .unwrap_or_default() as u32;
2097                    }
2098                    selected_node = child;
2099                    break;
2100                }
2101            }
2102            // For <input>, <textarea>, <hr> and <br> elements, we should select the previous
2103            // node, regardless if it was a block node or not
2104            if matches!(
2105                child.type_id(),
2106                NodeTypeId::Element(ElementTypeId::HTMLElement(
2107                    HTMLElementTypeId::HTMLInputElement,
2108                )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
2109                    HTMLElementTypeId::HTMLTextAreaElement,
2110                )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
2111                    HTMLElementTypeId::HTMLHRElement,
2112                )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
2113                    HTMLElementTypeId::HTMLBRElement,
2114                ))
2115            ) {
2116                selected_node = previous_node;
2117                break;
2118            }
2119            // When we encounter a non-contenteditable element, we should select the previous
2120            // eligible node
2121            if child
2122                .downcast::<HTMLElement>()
2123                .is_some_and(|el| el.ContentEditable().str() == "false")
2124            {
2125                selected_node = previous_eligible_node;
2126                break;
2127            }
2128            // We can only select block nodes as eligible nodes for the case of non-conenteditable
2129            // nodes
2130            if child.is_block_node() {
2131                previous_eligible_node = child.clone();
2132            }
2133            previous_node = child;
2134        }
2135        range.set_start(&selected_node, selected_offset);
2136        range.set_end(&selected_node, selected_offset);
2137        selection.AddRange(&range);
2138    }
2139}
2140
2141trait EquivalentPoint {
2142    fn previous_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)>;
2143    fn next_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)>;
2144    fn first_equivalent_point(self) -> (DomRoot<Node>, u32);
2145    fn last_equivalent_point(self) -> (DomRoot<Node>, u32);
2146}
2147
2148impl EquivalentPoint for (DomRoot<Node>, u32) {
2149    /// <https://w3c.github.io/editing/docs/execCommand/#previous-equivalent-point>
2150    fn previous_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)> {
2151        let (node, offset) = self;
2152        // Step 1. If node's length is zero, return null.
2153        let len = node.len();
2154        if len == 0 {
2155            return None;
2156        }
2157        // Step 2. If offset is 0, and node's parent is not null, and node is an inline node,
2158        // return (node's parent, node's index).
2159        if *offset == 0 && node.is_inline_node() {
2160            if let Some(parent) = node.GetParentNode() {
2161                return Some((parent, node.index()));
2162            }
2163        }
2164        // Step 3. If node has a child with index offset − 1, and that child's length is not zero,
2165        // and that child is an inline node, return (that child, that child's length).
2166        if *offset > 0 {
2167            if let Some(child) = node.children().nth(*offset as usize - 1) {
2168                if !child.is_empty() && child.is_inline_node() {
2169                    let len = child.len();
2170                    return Some((child, len));
2171                }
2172            }
2173        }
2174
2175        // Step 4. Return null.
2176        None
2177    }
2178
2179    /// <https://w3c.github.io/editing/docs/execCommand/#next-equivalent-point>
2180    fn next_equivalent_point(&self) -> Option<(DomRoot<Node>, u32)> {
2181        let (node, offset) = self;
2182        // Step 1. If node's length is zero, return null.
2183        let len = node.len();
2184        if len == 0 {
2185            return None;
2186        }
2187
2188        // Step 2.
2189        //
2190        // This step does not exist in the spec
2191
2192        // Step 3. If offset is node's length, and node's parent is not null, and node is an inline node,
2193        // return (node's parent, 1 + node's index).
2194        if *offset == len && node.is_inline_node() {
2195            if let Some(parent) = node.GetParentNode() {
2196                return Some((parent, node.index() + 1));
2197            }
2198        }
2199
2200        // Step 4.
2201        //
2202        // This step does not exist in the spec
2203
2204        // Step 5. If node has a child with index offset, and that child's length is not zero,
2205        // and that child is an inline node, return (that child, 0).
2206        if let Some(child) = node.children().nth(*offset as usize) {
2207            if !child.is_empty() && child.is_inline_node() {
2208                return Some((child, 0));
2209            }
2210        }
2211
2212        // Step 6.
2213        //
2214        // This step does not exist in the spec
2215
2216        // Step 7. Return null.
2217        None
2218    }
2219
2220    /// <https://w3c.github.io/editing/docs/execCommand/#first-equivalent-point>
2221    fn first_equivalent_point(self) -> (DomRoot<Node>, u32) {
2222        let mut previous_equivalent_point = self;
2223        // Step 1. While (node, offset)'s previous equivalent point is not null, set (node, offset) to its previous equivalent point.
2224        loop {
2225            if let Some(next) = previous_equivalent_point.previous_equivalent_point() {
2226                previous_equivalent_point = next;
2227            } else {
2228                // Step 2. Return (node, offset).
2229                return previous_equivalent_point;
2230            }
2231        }
2232    }
2233
2234    /// <https://w3c.github.io/editing/docs/execCommand/#last-equivalent-point>
2235    fn last_equivalent_point(self) -> (DomRoot<Node>, u32) {
2236        let mut next_equivalent_point = self;
2237        // Step 1. While (node, offset)'s next equivalent point is not null, set (node, offset) to its next equivalent point.
2238        loop {
2239            if let Some(next) = next_equivalent_point.next_equivalent_point() {
2240                next_equivalent_point = next;
2241            } else {
2242                // Step 2. Return (node, offset).
2243                return next_equivalent_point;
2244            }
2245        }
2246    }
2247}
2248
2249enum BoolOrOptionalString {
2250    Bool(bool),
2251    OptionalString(Option<DOMString>),
2252}
2253
2254impl From<Option<DOMString>> for BoolOrOptionalString {
2255    fn from(optional_string: Option<DOMString>) -> Self {
2256        Self::OptionalString(optional_string)
2257    }
2258}
2259
2260impl From<bool> for BoolOrOptionalString {
2261    fn from(bool_: bool) -> Self {
2262        Self::Bool(bool_)
2263    }
2264}
2265
2266struct RecordedStateOfNode {
2267    command: CommandName,
2268    value: BoolOrOptionalString,
2269}
2270
2271impl RecordedStateOfNode {
2272    fn for_command_node(command: CommandName, node: &Node) -> Self {
2273        let value = node.effective_command_value(&command).into();
2274        Self { command, value }
2275    }
2276}
2277
2278impl Range {
2279    /// <https://w3c.github.io/editing/docs/execCommand/#effectively-contained>
2280    fn is_effectively_contained_node(&self, node: &Node) -> bool {
2281        // > A node node is effectively contained in a range range if range is not collapsed,
2282        if self.collapsed() {
2283            return false;
2284        }
2285        // > and at least one of the following holds:
2286        // > node is range's start node, it is a Text node, and its length is different from range's start offset.
2287        let start_container = self.start_container();
2288        if *start_container == *node && node.is::<Text>() && node.len() != self.start_offset() {
2289            return true;
2290        }
2291        // > node is range's end node, it is a Text node, and range's end offset is not 0.
2292        let end_container = self.end_container();
2293        if *end_container == *node && node.is::<Text>() && self.end_offset() != 0 {
2294            return true;
2295        }
2296        // > node is contained in range.
2297        if self.contains(node) {
2298            return true;
2299        }
2300        // > node has at least one child; and all its children are effectively contained in range;
2301        node.children_count() > 0 && node.children().all(|child| self.is_effectively_contained_node(&child))
2302        // > and either range's start node is not a descendant of node or is not a Text node or range's start offset is zero;
2303        && (!node.is_ancestor_of(&start_container) || !start_container.is::<Text>() || self.start_offset() == 0)
2304        // > and either range's end node is not a descendant of node or is not a Text node or range's end offset is its end node's length.
2305        && (!node.is_ancestor_of(&end_container) || !end_container.is::<Text>() || self.end_offset() == end_container.len())
2306    }
2307
2308    pub(crate) fn first_formattable_contained_node(&self) -> Option<DomRoot<Node>> {
2309        if self.collapsed() {
2310            return None;
2311        }
2312
2313        self.CommonAncestorContainer()
2314            .traverse_preorder(ShadowIncluding::No)
2315            .find(|child| child.is_formattable() && self.is_effectively_contained_node(child))
2316    }
2317
2318    fn for_each_effectively_contained_child<Callback: FnMut(&Node)>(&self, mut callback: Callback) {
2319        if self.collapsed() {
2320            return;
2321        }
2322
2323        for child in self
2324            .CommonAncestorContainer()
2325            .traverse_preorder(ShadowIncluding::No)
2326        {
2327            if self.is_effectively_contained_node(&child) {
2328                callback(&child);
2329            }
2330        }
2331    }
2332
2333    /// <https://w3c.github.io/editing/docs/execCommand/#record-current-states-and-values>
2334    fn record_current_states_and_values(&self) -> Vec<RecordedStateOfNode> {
2335        // Step 1. Let overrides be a list of (string, string or boolean) ordered pairs, initially empty.
2336        //
2337        // We return the vec in one go for the relevant values
2338
2339        // Step 2. Let node be the first formattable node effectively contained in the active range,
2340        // or null if there is none.
2341        let Some(node) = self.first_formattable_contained_node() else {
2342            // Step 3. If node is null, return overrides.
2343            return vec![];
2344        };
2345        // Step 8. Return overrides.
2346        vec![
2347            // Step 4. Add ("createLink", node's effective command value for "createLink") to overrides.
2348            RecordedStateOfNode::for_command_node(CommandName::CreateLink, &node),
2349            // Step 5. For each command in the list
2350            // "bold", "italic", "strikethrough", "subscript", "superscript", "underline", in order:
2351            // if node's effective command value for command is one of its inline command activated values,
2352            // add (command, true) to overrides, and otherwise add (command, false) to overrides.
2353            // TODO
2354
2355            // Step 6. For each command in the list "fontName", "foreColor", "hiliteColor", in order:
2356            // add (command, command's value) to overrides.
2357            // TODO
2358
2359            // Step 7. Add ("fontSize", node's effective command value for "fontSize") to overrides.
2360            RecordedStateOfNode::for_command_node(CommandName::FontSize, &node),
2361        ]
2362    }
2363
2364    /// <https://w3c.github.io/editing/docs/execCommand/#restore-states-and-values>
2365    fn restore_states_and_values(
2366        &self,
2367        context_object: &Document,
2368        overrides: Vec<RecordedStateOfNode>,
2369    ) {
2370        // Step 1. Let node be the first formattable node effectively contained in the active range,
2371        // or null if there is none.
2372        let Some(_node) = self.first_formattable_contained_node() else {
2373            // Step 3. Otherwise, for each (command, override) pair in overrides, in order:
2374            for override_state in overrides {
2375                // Step 3.1. If override is a boolean, set the state override for command to override.
2376                match override_state.value {
2377                    BoolOrOptionalString::Bool(bool_) => {
2378                        context_object.set_state_override(override_state.command, Some(bool_))
2379                    },
2380                    // Step 3.2. If override is a string, set the value override for command to override.
2381                    BoolOrOptionalString::OptionalString(optional_string) => {
2382                        context_object.set_value_override(override_state.command, optional_string)
2383                    },
2384                }
2385            }
2386            return;
2387        };
2388        // Step 2. If node is not null, then for each (command, override) pair in overrides, in order:
2389        // TODO
2390
2391        // Step 2.1. If override is a boolean, and queryCommandState(command)
2392        // returns something different from override, take the action for command,
2393        // with value equal to the empty string.
2394        // TODO
2395
2396        // Step 2.2. Otherwise, if override is a string, and command is neither "createLink" nor "fontSize",
2397        // and queryCommandValue(command) returns something not equivalent to override,
2398        // take the action for command, with value equal to override.
2399        // TODO
2400
2401        // Step 2.3. Otherwise, if override is a string; and command is "createLink";
2402        // and either there is a value override for "createLink" that is not equal to override,
2403        // or there is no value override for "createLink" and node's effective command value
2404        // for "createLink" is not equal to override: take the action for "createLink", with value equal to override.
2405        // TODO
2406
2407        // Step 2.4. Otherwise, if override is a string; and command is "fontSize";
2408        // and either there is a value override for "fontSize" that is not equal to override,
2409        // or there is no value override for "fontSize" and node's effective command value for "fontSize"
2410        // is not loosely equivalent to override:
2411        // TODO
2412
2413        // Step 2.5. Otherwise, continue this loop from the beginning.
2414        // TODO
2415
2416        // Step 2.6. Set node to the first formattable node effectively contained in the active range, if there is one.
2417        // TODO
2418    }
2419}
2420
2421#[derive(Default, PartialEq)]
2422pub(crate) enum SelectionDeletionBlockMerging {
2423    #[default]
2424    Merge,
2425    Skip,
2426}
2427
2428#[derive(Default, PartialEq)]
2429pub(crate) enum SelectionDeletionStripWrappers {
2430    #[default]
2431    Strip,
2432}
2433
2434#[derive(Default, PartialEq)]
2435pub(crate) enum SelectionDeleteDirection {
2436    #[default]
2437    Forward,
2438    Backward,
2439}
2440
2441pub(crate) trait SelectionExecCommandSupport {
2442    fn delete_the_selection(
2443        &self,
2444        cx: &mut js::context::JSContext,
2445        context_object: &Document,
2446        block_merging: SelectionDeletionBlockMerging,
2447        strip_wrappers: SelectionDeletionStripWrappers,
2448        direction: SelectionDeleteDirection,
2449    );
2450    fn set_the_selection_value(
2451        &self,
2452        cx: &mut js::context::JSContext,
2453        new_value: Option<DOMString>,
2454        command: CommandName,
2455        context_object: &Document,
2456    );
2457}
2458
2459impl SelectionExecCommandSupport for Selection {
2460    /// <https://w3c.github.io/editing/docs/execCommand/#delete-the-selection>
2461    fn delete_the_selection(
2462        &self,
2463        cx: &mut js::context::JSContext,
2464        context_object: &Document,
2465        block_merging: SelectionDeletionBlockMerging,
2466        strip_wrappers: SelectionDeletionStripWrappers,
2467        direction: SelectionDeleteDirection,
2468    ) {
2469        // Step 1. If the active range is null, abort these steps and do nothing.
2470        let Some(active_range) = self.active_range() else {
2471            return;
2472        };
2473
2474        // Step 2. Canonicalize whitespace at the active range's start.
2475        active_range
2476            .start_container()
2477            .canonicalize_whitespace(active_range.start_offset(), true);
2478
2479        // Step 3. Canonicalize whitespace at the active range's end.
2480        active_range
2481            .end_container()
2482            .canonicalize_whitespace(active_range.end_offset(), true);
2483
2484        // Step 4. Let (start node, start offset) be the last equivalent point for the active range's start.
2485        let (mut start_node, mut start_offset) =
2486            (active_range.start_container(), active_range.start_offset()).last_equivalent_point();
2487
2488        // Step 5. Let (end node, end offset) be the first equivalent point for the active range's end.
2489        let (mut end_node, mut end_offset) =
2490            (active_range.end_container(), active_range.end_offset()).first_equivalent_point();
2491
2492        // Step 6. If (end node, end offset) is not after (start node, start offset):
2493        if bp_position(&end_node, end_offset, &start_node, start_offset) != Some(Ordering::Greater)
2494        {
2495            // Step 6.1. If direction is "forward", call collapseToStart() on the context object's selection.
2496            if direction == SelectionDeleteDirection::Forward {
2497                if self.CollapseToStart(CanGc::from_cx(cx)).is_err() {
2498                    unreachable!("Should be able to collapse to start");
2499                }
2500            } else {
2501                // Step 6.2. Otherwise, call collapseToEnd() on the context object's selection.
2502                if self.CollapseToEnd(CanGc::from_cx(cx)).is_err() {
2503                    unreachable!("Should be able to collapse to end");
2504                }
2505            }
2506            // Step 6.3. Abort these steps.
2507            return;
2508        }
2509
2510        // Step 7. If start node is a Text node and start offset is 0, set start offset to the index of start node,
2511        // then set start node to its parent.
2512        if start_node.is::<Text>() && start_offset == 0 {
2513            start_offset = start_node.index();
2514            start_node = start_node
2515                .GetParentNode()
2516                .expect("Must always have a parent");
2517        }
2518
2519        // 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,
2520        // then set end node to its parent.
2521        if end_node.is::<Text>() && end_offset == end_node.len() {
2522            end_offset = end_node.index() + 1;
2523            end_node = end_node.GetParentNode().expect("Must always have a parent");
2524        }
2525
2526        // Step 9. Call collapse(start node, start offset) on the context object's selection.
2527        if self
2528            .Collapse(Some(&start_node), start_offset, CanGc::from_cx(cx))
2529            .is_err()
2530        {
2531            unreachable!("Must always be able to collapse");
2532        }
2533
2534        // Step 10. Call extend(end node, end offset) on the context object's selection.
2535        if self
2536            .Extend(&end_node, end_offset, CanGc::from_cx(cx))
2537            .is_err()
2538        {
2539            unreachable!("Must always be able to extend");
2540        }
2541
2542        // Step 11.
2543        //
2544        // This step does not exist in the spec
2545
2546        // Step 12. Let start block be the active range's start node.
2547        let Some(active_range) = self.active_range() else {
2548            return;
2549        };
2550        let mut start_block = active_range.start_container();
2551
2552        // Step 13. While start block's parent is in the same editing host and start block is an inline node,
2553        // set start block to its parent.
2554        loop {
2555            if start_block.is_inline_node() {
2556                if let Some(parent) = start_block.GetParentNode() {
2557                    if parent.same_editing_host(&start_node) {
2558                        start_block = parent;
2559                        continue;
2560                    }
2561                }
2562            }
2563            break;
2564        }
2565
2566        // Step 14. If start block is neither a block node nor an editing host,
2567        // or "span" is not an allowed child of start block,
2568        // or start block is a td or th, set start block to null.
2569        let start_block = if (!start_block.is_block_node() && !start_block.is_editing_host()) ||
2570            !is_allowed_child(
2571                NodeOrString::String("span".to_owned()),
2572                NodeOrString::Node(start_block.clone()),
2573            ) ||
2574            start_block.is::<HTMLTableCellElement>()
2575        {
2576            None
2577        } else {
2578            Some(start_block)
2579        };
2580
2581        // Step 15. Let end block be the active range's end node.
2582        let mut end_block = active_range.end_container();
2583
2584        // 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.
2585        loop {
2586            if end_block.is_inline_node() {
2587                if let Some(parent) = end_block.GetParentNode() {
2588                    if parent.same_editing_host(&end_block) {
2589                        end_block = parent;
2590                        continue;
2591                    }
2592                }
2593            }
2594            break;
2595        }
2596
2597        // Step 17. If end block is neither a block node nor an editing host, or "span" is not an allowed child of end block,
2598        // or end block is a td or th, set end block to null.
2599        let end_block = if (!end_block.is_block_node() && !end_block.is_editing_host()) ||
2600            !is_allowed_child(
2601                NodeOrString::String("span".to_owned()),
2602                NodeOrString::Node(end_block.clone()),
2603            ) ||
2604            end_block.is::<HTMLTableCellElement>()
2605        {
2606            None
2607        } else {
2608            Some(end_block)
2609        };
2610
2611        // Step 18.
2612        //
2613        // This step does not exist in the spec
2614
2615        // Step 19. Record current states and values, and let overrides be the result.
2616        let overrides = active_range.record_current_states_and_values();
2617
2618        // Step 20.
2619        //
2620        // This step does not exist in the spec
2621
2622        // Step 21. If start node and end node are the same, and start node is an editable Text node:
2623        if start_node == end_node && start_node.is_editable() {
2624            if let Some(start_text) = start_node.downcast::<Text>() {
2625                // Step 21.1. Call deleteData(start offset, end offset − start offset) on start node.
2626                if start_text
2627                    .upcast::<CharacterData>()
2628                    .DeleteData(start_offset, end_offset - start_offset)
2629                    .is_err()
2630                {
2631                    unreachable!("Must always be able to delete");
2632                }
2633                // Step 21.2. Canonicalize whitespace at (start node, start offset), with fix collapsed space false.
2634                start_node.canonicalize_whitespace(start_offset, false);
2635                // Step 21.3. If direction is "forward", call collapseToStart() on the context object's selection.
2636                if direction == SelectionDeleteDirection::Forward {
2637                    if self.CollapseToStart(CanGc::from_cx(cx)).is_err() {
2638                        unreachable!("Should be able to collapse to start");
2639                    }
2640                } else {
2641                    // Step 21.4. Otherwise, call collapseToEnd() on the context object's selection.
2642                    if self.CollapseToEnd(CanGc::from_cx(cx)).is_err() {
2643                        unreachable!("Should be able to collapse to end");
2644                    }
2645                }
2646                // Step 21.5. Restore states and values from overrides.
2647                active_range.restore_states_and_values(context_object, overrides);
2648
2649                // Step 21.6. Abort these steps.
2650                return;
2651            }
2652        }
2653
2654        // Step 22. If start node is an editable Text node, call deleteData() on it, with start offset as
2655        // the first argument and (length of start node − start offset) as the second argument.
2656        if start_node.is_editable() {
2657            if let Some(start_text) = start_node.downcast::<Text>() {
2658                if start_text
2659                    .upcast::<CharacterData>()
2660                    .DeleteData(start_offset, start_node.len() - start_offset)
2661                    .is_err()
2662                {
2663                    unreachable!("Must always be able to delete");
2664                }
2665            }
2666        }
2667
2668        // Step 23. Let node list be a list of nodes, initially empty.
2669        rooted_vec!(let mut node_list);
2670
2671        // Step 24. For each node contained in the active range, append node to node list if the
2672        // last member of node list (if any) is not an ancestor of node; node is editable;
2673        // and node is not a thead, tbody, tfoot, tr, th, or td.
2674        let Ok(contained_children) = active_range.contained_children() else {
2675            unreachable!("Must always have contained children");
2676        };
2677        for node in contained_children.contained_children {
2678            // This type is only used to tell the compiler how to handle the type of `node_list.last()`.
2679            // It is not allowed to add a `& DomRoot<Node>` annotation, as test-tidy disallows that.
2680            // However, if we omit the type, the compiler doesn't know what it is, since we also
2681            // aren't allowed to add a type annotation to `node_list` itself, as that is handled
2682            // by the `rooted_vec` macro. Lastly, we also can't make it `&Node`, since then the compiler
2683            // thinks that the contents of the `RootedVec` is `Node`, whereas it is should be
2684            // `RootedVec<DomRoot<Node>>`. The type alias here doesn't upset test-tidy,
2685            // while also providing the necessary information to the compiler to work.
2686            type DomRootNode = DomRoot<Node>;
2687            if node.is_editable() &&
2688                !(node.is::<HTMLTableSectionElement>() ||
2689                    node.is::<HTMLTableRowElement>() ||
2690                    node.is::<HTMLTableCellElement>()) &&
2691                node_list
2692                    .last()
2693                    .is_none_or(|last: &DomRootNode| !last.is_ancestor_of(&node))
2694            {
2695                node_list.push(node);
2696            }
2697        }
2698
2699        // Step 25. For each node in node list:
2700        for node in node_list.iter() {
2701            // Step 25.1. Let parent be the parent of node.
2702            let parent = node.GetParentNode().expect("Must always have a parent");
2703            // Step 25.2. Remove node from parent.
2704            assert!(node.has_parent());
2705            node.remove_self(cx);
2706            // Step 25.3. If the block node of parent has no visible children, and parent is editable or an editing host,
2707            // call createElement("br") on the context object and append the result as the last child of parent.
2708            if parent
2709                .block_node_of()
2710                .is_some_and(|block_node| block_node.children().all(|child| child.is_invisible())) &&
2711                parent.is_editable_or_editing_host()
2712            {
2713                let br = context_object.create_element(cx, "br");
2714                if parent.AppendChild(cx, br.upcast()).is_err() {
2715                    unreachable!("Must always be able to append");
2716                }
2717            }
2718            // Step 25.4. If strip wrappers is true or parent is not an inclusive ancestor of start node,
2719            // while parent is an editable inline node with length 0, let grandparent be the parent of parent,
2720            // then remove parent from grandparent, then set parent to grandparent.
2721            if strip_wrappers == SelectionDeletionStripWrappers::Strip ||
2722                !parent.is_inclusive_ancestor_of(&start_node)
2723            {
2724                let mut parent = parent;
2725                loop {
2726                    if parent.is_editable() && parent.is_inline_node() && parent.is_empty() {
2727                        let grand_parent =
2728                            parent.GetParentNode().expect("Must always have a parent");
2729                        assert!(parent.has_parent());
2730                        parent.remove_self(cx);
2731                        parent = grand_parent;
2732                        continue;
2733                    }
2734                    break;
2735                }
2736            }
2737        }
2738
2739        // Step 26. If end node is an editable Text node, call deleteData(0, end offset) on it.
2740        if end_node.is_editable() {
2741            if let Some(end_text) = end_node.downcast::<Text>() {
2742                if end_text
2743                    .upcast::<CharacterData>()
2744                    .DeleteData(0, end_offset)
2745                    .is_err()
2746                {
2747                    unreachable!("Must always be able to delete");
2748                }
2749            }
2750        }
2751
2752        // Step 27. Canonicalize whitespace at the active range's start, with fix collapsed space false.
2753        active_range
2754            .start_container()
2755            .canonicalize_whitespace(active_range.start_offset(), false);
2756
2757        // Step 28. Canonicalize whitespace at the active range's end, with fix collapsed space false.
2758        active_range
2759            .end_container()
2760            .canonicalize_whitespace(active_range.end_offset(), false);
2761
2762        // Step 29.
2763        //
2764        // This step does not exist in the spec
2765
2766        // Step 30. If block merging is false, or start block or end block is null, or start block is not
2767        // in the same editing host as end block, or start block and end block are the same:
2768        if block_merging == SelectionDeletionBlockMerging::Skip ||
2769            start_block.as_ref().zip(end_block.as_ref()).is_none_or(
2770                |(start_block, end_block)| {
2771                    start_block == end_block || !start_block.same_editing_host(end_block)
2772                },
2773            )
2774        {
2775            // Step 30.1. If direction is "forward", call collapseToStart() on the context object's selection.
2776            if direction == SelectionDeleteDirection::Forward {
2777                if self.CollapseToStart(CanGc::from_cx(cx)).is_err() {
2778                    unreachable!("Should be able to collapse to start");
2779                }
2780            } else {
2781                // Step 30.2. Otherwise, call collapseToEnd() on the context object's selection.
2782                if self.CollapseToEnd(CanGc::from_cx(cx)).is_err() {
2783                    unreachable!("Should be able to collapse to end");
2784                }
2785            }
2786            // Step 30.3. Restore states and values from overrides.
2787            active_range.restore_states_and_values(context_object, overrides);
2788
2789            // Step 30.4. Abort these steps.
2790            return;
2791        }
2792        let start_block = start_block.expect("Already checked for None in previous statement");
2793        let end_block = end_block.expect("Already checked for None in previous statement");
2794
2795        // Step 31. If start block has one child, which is a collapsed block prop, remove its child from it.
2796        if start_block.children_count() == 1 {
2797            let Some(child) = start_block.children().nth(0) else {
2798                unreachable!("Must always have a single child");
2799            };
2800            if child.is_collapsed_block_prop() {
2801                assert!(child.has_parent());
2802                child.remove_self(cx);
2803            }
2804        }
2805
2806        // Step 32. If start block is an ancestor of end block:
2807        if start_block.is_ancestor_of(&end_block) {
2808            // Step 32.1. Let reference node be end block.
2809            let mut reference_node = end_block.clone();
2810            // Step 32.2. While reference node is not a child of start block, set reference node to its parent.
2811            loop {
2812                if start_block.children().all(|child| child != reference_node) {
2813                    reference_node = reference_node
2814                        .GetParentNode()
2815                        .expect("Must always have a parent, at least start_block");
2816                    continue;
2817                }
2818                break;
2819            }
2820            // Step 32.3. Call collapse() on the context object's selection,
2821            // with first argument start block and second argument the index of reference node.
2822            if self
2823                .Collapse(
2824                    Some(&start_block),
2825                    reference_node.index(),
2826                    CanGc::from_cx(cx),
2827                )
2828                .is_err()
2829            {
2830                unreachable!("Must always be able to collapse");
2831            }
2832            // Step 32.4. If end block has no children:
2833            if end_block.children_count() == 0 {
2834                let mut end_block = end_block;
2835                // 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,
2836                // let parent equal end block, then remove end block from parent, then set end block to parent.
2837                loop {
2838                    if end_block.is_editable() &&
2839                        start_block.children().all(|child| child != end_block)
2840                    {
2841                        if let Some(parent) = end_block.GetParentNode() {
2842                            if parent.children_count() == 1 {
2843                                assert!(end_block.has_parent());
2844                                end_block.remove_self(cx);
2845                                end_block = parent;
2846                                continue;
2847                            }
2848                        }
2849                    }
2850                    break;
2851                }
2852                // Step 32.4.2. If end block is editable and is not an inline node,
2853                // and its previousSibling and nextSibling are both inline nodes,
2854                // call createElement("br") on the context object and insert it into end block's parent immediately after end block.
2855                if end_block.is_editable() &&
2856                    !end_block.is_inline_node() &&
2857                    end_block
2858                        .GetPreviousSibling()
2859                        .is_some_and(|previous| previous.is_inline_node())
2860                {
2861                    if let Some(next_of_end_block) = end_block.GetNextSibling() {
2862                        if next_of_end_block.is_inline_node() {
2863                            let br = context_object.create_element(cx, "br");
2864                            let parent = end_block
2865                                .GetParentNode()
2866                                .expect("Must always have a parent");
2867                            if parent
2868                                .InsertBefore(cx, br.upcast(), Some(&next_of_end_block))
2869                                .is_err()
2870                            {
2871                                unreachable!("Must always be able to insert into parent");
2872                            }
2873                        }
2874                    }
2875                }
2876                // Step 32.4.3. If end block is editable, remove it from its parent.
2877                if end_block.is_editable() {
2878                    assert!(end_block.has_parent());
2879                    end_block.remove_self(cx);
2880                }
2881                // Step 32.4.4. Restore states and values from overrides.
2882                active_range.restore_states_and_values(context_object, overrides);
2883
2884                // Step 32.4.5. Abort these steps.
2885                return;
2886            }
2887            let first_child = end_block
2888                .children()
2889                .nth(0)
2890                .expect("Already checked at least 1 child in previous statement");
2891            // Step 32.5. If end block's firstChild is not an inline node,
2892            // restore states and values from record, then abort these steps.
2893            if !first_child.is_inline_node() {
2894                // TODO: Restore state
2895                return;
2896            }
2897            // Step 32.6. Let children be a list of nodes, initially empty.
2898            rooted_vec!(let mut children);
2899            // Step 32.7. Append the first child of end block to children.
2900            children.push(first_child.as_traced());
2901            // Step 32.8. While children's last member is not a br,
2902            // and children's last member's nextSibling is an inline node,
2903            // append children's last member's nextSibling to children.
2904            loop {
2905                let Some(last) = children.last() else {
2906                    break;
2907                };
2908                if last.is::<HTMLBRElement>() {
2909                    break;
2910                }
2911                let Some(next) = last.GetNextSibling() else {
2912                    break;
2913                };
2914                if next.is_inline_node() {
2915                    children.push(next.as_traced());
2916                    continue;
2917                }
2918                break;
2919            }
2920            // Step 32.9. Record the values of children, and let values be the result.
2921            // TODO
2922
2923            // Step 32.10. While children's first member's parent is not start block,
2924            // split the parent of children.
2925            loop {
2926                if children
2927                    .first()
2928                    .and_then(|child| child.GetParentNode())
2929                    .is_some_and(|parent_of_child| parent_of_child != start_block)
2930                {
2931                    split_the_parent(cx, children.r());
2932                    continue;
2933                }
2934                break;
2935            }
2936            // Step 32.11. If children's first member's previousSibling is an editable br,
2937            // remove that br from its parent.
2938            if let Some(first) = children.first() {
2939                if let Some(previous_of_first) = first.GetPreviousSibling() {
2940                    if previous_of_first.is_editable() && previous_of_first.is::<HTMLBRElement>() {
2941                        assert!(previous_of_first.has_parent());
2942                        previous_of_first.remove_self(cx);
2943                    }
2944                }
2945            }
2946        // Step 33. Otherwise, if start block is a descendant of end block:
2947        } else if end_block.is_ancestor_of(&start_block) {
2948            // Step 33.1. Call collapse() on the context object's selection,
2949            // with first argument start block and second argument start block's length.
2950            if self
2951                .Collapse(Some(&start_block), start_block.len(), CanGc::from_cx(cx))
2952                .is_err()
2953            {
2954                unreachable!("Must always be able to collapse");
2955            }
2956            // Step 33.2. Let reference node be start block.
2957            let mut reference_node = start_block.clone();
2958            // Step 33.3. While reference node is not a child of end block, set reference node to its parent.
2959            loop {
2960                if end_block.children().all(|child| child != reference_node) {
2961                    if let Some(parent) = reference_node.GetParentNode() {
2962                        reference_node = parent;
2963                        continue;
2964                    }
2965                }
2966                break;
2967            }
2968            // Step 33.4. If reference node's nextSibling is an inline node and start block's lastChild is a br,
2969            // remove start block's lastChild from it.
2970            if reference_node
2971                .GetNextSibling()
2972                .is_some_and(|next| next.is_inline_node())
2973            {
2974                if let Some(last) = start_block.children().last() {
2975                    if last.is::<HTMLBRElement>() {
2976                        assert!(last.has_parent());
2977                        last.remove_self(cx);
2978                    }
2979                }
2980            }
2981            // Step 33.5. Let nodes to move be a list of nodes, initially empty.
2982            rooted_vec!(let mut nodes_to_move);
2983            // Step 33.6. If reference node's nextSibling is neither null nor a block node,
2984            // append it to nodes to move.
2985            if let Some(next) = reference_node.GetNextSibling() {
2986                if !next.is_block_node() {
2987                    nodes_to_move.push(next);
2988                }
2989            }
2990            // Step 33.7. While nodes to move is nonempty and its last member isn't a br
2991            // and its last member's nextSibling is neither null nor a block node,
2992            // append its last member's nextSibling to nodes to move.
2993            loop {
2994                if let Some(last) = nodes_to_move.last() {
2995                    if !last.is::<HTMLBRElement>() {
2996                        if let Some(next_of_last) = last.GetNextSibling() {
2997                            if !next_of_last.is_block_node() {
2998                                nodes_to_move.push(next_of_last);
2999                                continue;
3000                            }
3001                        }
3002                    }
3003                }
3004                break;
3005            }
3006            // Step 33.8. Record the values of nodes to move, and let values be the result.
3007            // TODO
3008
3009            // Step 33.9. For each node in nodes to move,
3010            // append node as the last child of start block, preserving ranges.
3011            for node in nodes_to_move.iter() {
3012                // TODO: Preserve ranges
3013                if start_block.AppendChild(cx, node).is_err() {
3014                    unreachable!("Must always be able to append");
3015                }
3016            }
3017        // Step 34. Otherwise:
3018        } else {
3019            // Step 34.1. Call collapse() on the context object's selection,
3020            // with first argument start block and second argument start block's length.
3021            if self
3022                .Collapse(Some(&start_block), start_block.len(), CanGc::from_cx(cx))
3023                .is_err()
3024            {
3025                unreachable!("Must always be able to collapse");
3026            }
3027            // Step 34.2. If end block's firstChild is an inline node and start block's lastChild is a br,
3028            // remove start block's lastChild from it.
3029            if end_block
3030                .children()
3031                .nth(0)
3032                .is_some_and(|next| next.is_inline_node())
3033            {
3034                if let Some(last) = start_block.children().last() {
3035                    if last.is::<HTMLBRElement>() {
3036                        assert!(last.has_parent());
3037                        last.remove_self(cx);
3038                    }
3039                }
3040            }
3041            // Step 34.3. Record the values of end block's children, and let values be the result.
3042            // TODO
3043
3044            // Step 34.4. While end block has children,
3045            // append the first child of end block to start block, preserving ranges.
3046            loop {
3047                if let Some(first_child) = end_block.children().nth(0) {
3048                    // TODO: Preserve ranges
3049                    if start_block.AppendChild(cx, &first_child).is_err() {
3050                        unreachable!("Must always be able to append");
3051                    }
3052                    continue;
3053                }
3054                break;
3055            }
3056            // Step 34.5. While end block has no children,
3057            // let parent be the parent of end block, then remove end block from parent,
3058            // then set end block to parent.
3059            let mut end_block = end_block;
3060            loop {
3061                if end_block.children_count() == 0 {
3062                    if let Some(parent) = end_block.GetParentNode() {
3063                        assert!(end_block.has_parent());
3064                        end_block.remove_self(cx);
3065                        end_block = parent;
3066                        continue;
3067                    }
3068                }
3069                break;
3070            }
3071        }
3072
3073        // Step 35.
3074        //
3075        // This step does not exist in the spec
3076
3077        // Step 36. Let ancestor be start block.
3078        // TODO
3079
3080        // Step 37. While ancestor has an inclusive ancestor ol in the same editing host whose nextSibling is
3081        // also an ol in the same editing host, or an inclusive ancestor ul in the same editing host whose nextSibling
3082        // is also a ul in the same editing host:
3083        // TODO
3084
3085        // Step 38. Restore the values from values.
3086        // TODO
3087
3088        // Step 39. If start block has no children, call createElement("br") on the context object and
3089        // append the result as the last child of start block.
3090        if start_block.children_count() == 0 {
3091            let br = context_object.create_element(cx, "br");
3092            if start_block.AppendChild(cx, br.upcast()).is_err() {
3093                unreachable!("Must always be able to append");
3094            }
3095        }
3096
3097        // Step 40. Remove extraneous line breaks at the end of start block.
3098        start_block.remove_extraneous_line_breaks_at_the_end_of(cx);
3099
3100        // Step 41. Restore states and values from overrides.
3101        active_range.restore_states_and_values(context_object, overrides);
3102    }
3103
3104    /// <https://w3c.github.io/editing/docs/execCommand/#set-the-selection%27s-value>
3105    fn set_the_selection_value(
3106        &self,
3107        cx: &mut js::context::JSContext,
3108        new_value: Option<DOMString>,
3109        command: CommandName,
3110        context_object: &Document,
3111    ) {
3112        let active_range = self
3113            .active_range()
3114            .expect("Must always have an active range");
3115
3116        // Step 1. Let command be the current command.
3117        //
3118        // Passed as argument
3119
3120        // Step 2. If there is no formattable node effectively contained in the active range:
3121        if active_range.first_formattable_contained_node().is_none() {
3122            // 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.
3123            // TODO
3124
3125            // Step 2.2. If command is "subscript", unset the state override for "superscript".
3126            if command == CommandName::Subscript {
3127                context_object.set_state_override(CommandName::Superscript, None);
3128            }
3129            // Step 2.3. If command is "superscript", unset the state override for "subscript".
3130            if command == CommandName::Superscript {
3131                context_object.set_state_override(CommandName::Subscript, None);
3132            }
3133            // Step 2.4. If new value is null, unset the value override (if any).
3134            // Step 2.5. Otherwise, if command is "createLink" or it has a value specified, set the value override to new value.
3135            context_object.set_value_override(command, new_value);
3136            // Step 2.6. Abort these steps.
3137            return;
3138        }
3139        // Step 3. If the active range's start node is an editable Text node,
3140        // and its start offset is neither zero nor its start node's length,
3141        // call splitText() on the active range's start node,
3142        // with argument equal to the active range's start offset.
3143        // Then set the active range's start node to the result, and its start offset to zero.
3144        let start_node = active_range.start_container();
3145        let start_offset = active_range.start_offset();
3146        if start_node.is_editable() && start_offset != 0 && start_offset != start_node.len() {
3147            if let Some(start_text) = start_node.downcast::<Text>() {
3148                let Ok(start_text) = start_text.SplitText(cx, start_offset) else {
3149                    unreachable!("Must always be able to split");
3150                };
3151                active_range.set_start(start_text.upcast(), 0);
3152            }
3153        }
3154        // Step 4. If the active range's end node is an editable Text node,
3155        // and its end offset is neither zero nor its end node's length,
3156        // call splitText() on the active range's end node,
3157        // with argument equal to the active range's end offset.
3158        let end_node = active_range.end_container();
3159        let end_offset = active_range.end_offset();
3160        if end_node.is_editable() && end_offset != 0 && end_offset != end_node.len() {
3161            if let Some(end_text) = end_node.downcast::<Text>() {
3162                if end_text.SplitText(cx, end_offset).is_err() {
3163                    unreachable!("Must always be able to split");
3164                };
3165            }
3166        }
3167        // Step 5. Let element list be all editable Elements effectively contained in the active range.
3168        // Step 6. For each element in element list, clear the value of element.
3169        active_range.for_each_effectively_contained_child(|child| {
3170            if child.is_editable() {
3171                if let Some(element_child) = child.downcast::<HTMLElement>() {
3172                    element_child.clear_the_value(cx, &command);
3173                }
3174            }
3175        });
3176        // Step 7. Let node list be all editable nodes effectively contained in the active range.
3177        // Step 8. For each node in node list:
3178        active_range.for_each_effectively_contained_child(|child| {
3179            if child.is_editable() {
3180                // Step 8.1. Push down values on node.
3181                child.push_down_values(cx, &command, new_value.clone());
3182                // Step 8.2. If node is an allowed child of "span", force the value of node.
3183                if is_allowed_child(
3184                    NodeOrString::Node(DomRoot::from_ref(child)),
3185                    NodeOrString::String("span".to_owned()),
3186                ) {
3187                    child.force_the_value(cx, &command, new_value.as_ref());
3188                }
3189            }
3190        });
3191    }
3192}