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