Skip to main content

script/dom/execcommand/
basecommand.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 js::context::JSContext;
6use script_bindings::inheritance::Castable;
7use style::attr::parse_legacy_color;
8use style::color::ColorFlags;
9use style::properties::PropertyDeclarationId;
10use style::properties::generated::{LonghandId, ShorthandId};
11use style::values::specified::text::TextDecorationLine;
12use style_traits::ToCss;
13
14use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
15use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
16use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
17use crate::dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMethods;
18use crate::dom::bindings::str::DOMString;
19use crate::dom::document::Document;
20use crate::dom::element::Element;
21use crate::dom::execcommand::commands::backcolor::execute_backcolor_command;
22use crate::dom::execcommand::commands::bold::execute_bold_command;
23use crate::dom::execcommand::commands::createlink::execute_createlink_command;
24use crate::dom::execcommand::commands::defaultparagraphseparator::execute_default_paragraph_separator_command;
25use crate::dom::execcommand::commands::delete::execute_delete_command;
26use crate::dom::execcommand::commands::fontname::execute_fontname_command;
27use crate::dom::execcommand::commands::fontsize::{
28    execute_fontsize_command, font_size_loosely_equivalent, value_for_fontsize_command,
29};
30use crate::dom::execcommand::commands::forecolor::execute_forecolor_command;
31use crate::dom::execcommand::commands::hilitecolor::execute_hilitecolor_command;
32use crate::dom::execcommand::commands::insertparagraph::execute_insert_paragraph_command;
33use crate::dom::execcommand::commands::italic::execute_italic_command;
34use crate::dom::execcommand::commands::removeformat::execute_removeformat_command;
35use crate::dom::execcommand::commands::strikethrough::execute_strikethrough_command;
36use crate::dom::execcommand::commands::stylewithcss::execute_style_with_css_command;
37use crate::dom::execcommand::commands::subscript::execute_subscript_command;
38use crate::dom::execcommand::commands::superscript::execute_superscript_command;
39use crate::dom::execcommand::commands::underline::execute_underline_command;
40use crate::dom::execcommand::commands::unlink::execute_unlink_command;
41use crate::dom::html::htmlelement::HTMLElement;
42use crate::dom::html::htmlfontelement::HTMLFontElement;
43use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
44use crate::dom::selection::Selection;
45use crate::script_runtime::CanGc;
46
47#[derive(Default, Clone, Copy, MallocSizeOf)]
48pub(crate) enum DefaultSingleLineContainerName {
49    #[default]
50    Div,
51    Paragraph,
52}
53
54impl DefaultSingleLineContainerName {
55    pub(crate) fn str(&self) -> &str {
56        match self {
57            DefaultSingleLineContainerName::Div => "div",
58            DefaultSingleLineContainerName::Paragraph => "p",
59        }
60    }
61}
62
63impl From<DefaultSingleLineContainerName> for DOMString {
64    fn from(default_single_line_container_name: DefaultSingleLineContainerName) -> Self {
65        match default_single_line_container_name {
66            DefaultSingleLineContainerName::Div => DOMString::from("div"),
67            DefaultSingleLineContainerName::Paragraph => DOMString::from("p"),
68        }
69    }
70}
71
72pub(crate) enum BoolOrOptionalString {
73    Bool(bool),
74    OptionalString(Option<DOMString>),
75}
76
77impl From<Option<DOMString>> for BoolOrOptionalString {
78    fn from(optional_string: Option<DOMString>) -> Self {
79        Self::OptionalString(optional_string)
80    }
81}
82
83impl From<bool> for BoolOrOptionalString {
84    fn from(bool_: bool) -> Self {
85        Self::Bool(bool_)
86    }
87}
88
89pub(crate) struct RecordedStateOfCommand {
90    pub(crate) command: CommandName,
91    pub(crate) value: BoolOrOptionalString,
92}
93
94impl RecordedStateOfCommand {
95    pub(crate) fn for_command_node(command: CommandName, node: &Node) -> Self {
96        let value = node.effective_command_value(&command).into();
97        Self { command, value }
98    }
99
100    pub(crate) fn for_command_node_with_inline_activated_values(
101        command: CommandName,
102        node: &Node,
103    ) -> Self {
104        let effective_command_value = node.effective_command_value(&command);
105        let value = effective_command_value
106            .is_some_and(|effective_command_value| {
107                command
108                    .inline_command_activated_values()
109                    .contains(&effective_command_value.str().as_ref())
110            })
111            .into();
112        Self { command, value }
113    }
114
115    pub(crate) fn for_command_node_with_value(
116        cx: &mut JSContext,
117        command: CommandName,
118        document: &Document,
119    ) -> Self {
120        let value = command.current_value(cx, document).into();
121        Self { command, value }
122    }
123
124    fn for_command_state_override(command: CommandName, document: &Document) -> Option<Self> {
125        let value = document.state_override(&command)?.into();
126        Some(Self { command, value })
127    }
128
129    fn for_command_value_override(command: CommandName, document: &Document) -> Option<Self> {
130        let value_override = document.value_override(&command)?;
131        let value = Some(value_override).into();
132        Some(Self { command, value })
133    }
134}
135
136/// <https://w3c.github.io/editing/docs/execCommand/#relevant-css-property>
137#[derive(Clone, Copy, Eq, PartialEq)]
138pub(crate) enum CssPropertyName {
139    BackgroundColor,
140    Color,
141    FontFamily,
142    FontSize,
143    FontWeight,
144    FontStyle,
145    TextDecoration,
146    TextDecorationLine,
147}
148
149impl CssPropertyName {
150    pub(crate) fn resolved_value_for_node(&self, element: &Element) -> Option<DOMString> {
151        let style = element.style()?;
152
153        Some(
154            match self {
155                CssPropertyName::BackgroundColor => {
156                    let background_color = style.clone_background_color();
157                    if let Some(absolute_color) = background_color.as_absolute() {
158                        // Used as an early-exit when figuring out on which element to resolve
159                        // the style in `effective_command_value`
160                        if absolute_color.is_transparent() {
161                            return None;
162                        }
163                        // Requires legacy SRGB syntax, which is what all tests expect.
164                        // E.g. it should use `rgba()` instead of `rgb()`, even if the alpha
165                        // is zero.
166                        let mut absolute_color = *absolute_color;
167                        absolute_color.flags.insert(ColorFlags::IS_LEGACY_SRGB);
168                        return Some(absolute_color.to_css_string().into());
169                    }
170                    background_color.to_css_string()
171                },
172                CssPropertyName::Color => {
173                    // Detached font elements (e.g. does created with `document.createElement`
174                    // and not yet present in DOM) dont have a computed style for `color`.
175                    // Since we create detached parent elements and compute "effective command
176                    // value" for these elements, we need to special case this. Otherwise, we
177                    // would add both a `color` attribute and `color` style declaration
178                    // to a parent font element.
179                    if let Some(ancestor_font) = element.downcast::<HTMLFontElement>() {
180                        let color = ancestor_font.Color();
181                        if !color.is_empty() {
182                            return Some(color);
183                        }
184                    }
185                    style.clone_color().to_css_string()
186                },
187                CssPropertyName::FontFamily => {
188                    // Detached font elements (e.g. does created with `document.createElement`
189                    // and not yet present in DOM) dont have a computed style for `fontFamily`.
190                    // Since we create detached parent elements and compute "effective command
191                    // value" for these elements, we need to special case this. Otherwise, we
192                    // would add both a `face` attribute and `font-family` style declaration
193                    // to a parent font element.
194                    if let Some(ancestor_font) = element.downcast::<HTMLFontElement>() {
195                        let face = ancestor_font.Face();
196                        if !face.is_empty() {
197                            return Some(face);
198                        }
199                    }
200                    style.clone_font_family().to_css_string()
201                },
202                CssPropertyName::FontSize => {
203                    // Font size is special, in that it can't use the resolved styles to compute
204                    // values. That's because it is influenced by other factors as well, and it
205                    // should also take into account size attributes of font elements.
206                    //
207                    // Therefore, we do a manual traversal up the chain to mimic what style
208                    // resolution would have done. This also allows us to later check for
209                    // loose equivalence for font elements, since we would return the size as an
210                    // integer, without a size indicator (e.g. `px`).
211                    //
212                    // However, if no such relevant declaration exists, then we should fallback
213                    // to pixels after all. For the effective command value, this essentially means
214                    // we will overwrite it. For the value of the "fontsize" command, we would then
215                    // need to convert it using [`legacy_font_size_for`].
216                    return element
217                        .upcast::<Node>()
218                        .inclusive_ancestors(ShadowIncluding::No)
219                        .find_map(|ancestor| {
220                            if let Some(ancestor_font) = ancestor.downcast::<HTMLFontElement>() {
221                                Some(ancestor_font.Size())
222                            } else {
223                                self.value_set_for_style(ancestor.downcast::<Element>()?)
224                            }
225                        })
226                        .or_else(|| {
227                            let pixels = style.get_font().font_size.computed_size().px();
228                            Some(format!("{}px", pixels).into())
229                        });
230                },
231                CssPropertyName::FontWeight => style.clone_font_weight().to_css_string(),
232                CssPropertyName::FontStyle => style.clone_font_style().to_css_string(),
233                CssPropertyName::TextDecoration => unreachable!("Should use longhands instead"),
234                CssPropertyName::TextDecorationLine => {
235                    let text_decoration_line = style.get_text().text_decoration_line;
236                    if text_decoration_line == TextDecorationLine::NONE {
237                        return None;
238                    }
239                    text_decoration_line.to_css_string()
240                },
241            }
242            .into(),
243        )
244    }
245
246    /// Retrieves a respective css longhand value from the style declarations of an
247    /// element. Note that this is different than the computed values, since this is
248    /// only relevant when the author specified rules on the specific element.
249    pub(crate) fn value_set_for_style(&self, element: &Element) -> Option<DOMString> {
250        let style_attribute = element.style_attribute().borrow();
251        let declarations = style_attribute.as_ref()?;
252        let document = element.owner_document();
253        let shared_lock = document.style_shared_author_lock();
254        let read_lock = shared_lock.read();
255        let style = declarations.read_with(&read_lock);
256
257        let longhand_id = match self {
258            CssPropertyName::BackgroundColor => LonghandId::BackgroundColor,
259            CssPropertyName::Color => LonghandId::Color,
260            CssPropertyName::FontFamily => LonghandId::FontFamily,
261            CssPropertyName::FontSize => LonghandId::FontSize,
262            CssPropertyName::FontWeight => LonghandId::FontWeight,
263            CssPropertyName::FontStyle => LonghandId::FontStyle,
264            CssPropertyName::TextDecoration => {
265                let mut dest = String::new();
266                style
267                    .shorthand_to_css(ShorthandId::TextDecoration, &mut dest)
268                    .ok()?;
269                return Some(dest.into());
270            },
271            CssPropertyName::TextDecorationLine => LonghandId::TextDecorationLine,
272        };
273        style
274            .get(PropertyDeclarationId::Longhand(longhand_id))
275            .and_then(|value| {
276                let mut dest = String::new();
277                value.0.to_css(&mut dest).ok()?;
278                Some(dest.into())
279            })
280    }
281
282    fn property_name(&self) -> DOMString {
283        match self {
284            CssPropertyName::BackgroundColor => "background-color",
285            CssPropertyName::Color => "color",
286            CssPropertyName::FontFamily => "font-family",
287            CssPropertyName::FontSize => "font-size",
288            CssPropertyName::FontWeight => "font-weight",
289            CssPropertyName::FontStyle => "font-style",
290            CssPropertyName::TextDecoration => "text-decoration",
291            CssPropertyName::TextDecorationLine => "text-decoration-line",
292        }
293        .into()
294    }
295
296    pub(crate) fn set_for_element(
297        &self,
298        cx: &mut JSContext,
299        element: &HTMLElement,
300        new_value: DOMString,
301    ) {
302        let style = element.Style(CanGc::from_cx(cx));
303
304        let _ = style.SetProperty(cx, self.property_name(), new_value, "".into());
305    }
306
307    pub(crate) fn remove_from_element(&self, cx: &mut JSContext, element: &HTMLElement) {
308        let _ = element
309            .Style(CanGc::from_cx(cx))
310            .RemoveProperty(cx, self.property_name());
311    }
312}
313
314#[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq)]
315#[expect(unused)] // TODO(25005): implement all commands
316pub(crate) enum CommandName {
317    BackColor,
318    Bold,
319    Copy,
320    CreateLink,
321    Cut,
322    DefaultParagraphSeparator,
323    Delete,
324    FontName,
325    FontSize,
326    ForeColor,
327    FormatBlock,
328    ForwardDelete,
329    HiliteColor,
330    Indent,
331    InsertHorizontalRule,
332    InsertHtml,
333    InsertImage,
334    InsertLineBreak,
335    InsertOrderedList,
336    InsertParagraph,
337    InsertText,
338    InsertUnorderedList,
339    Italic,
340    JustifyCenter,
341    JustifyFull,
342    JustifyLeft,
343    JustifyRight,
344    Outdent,
345    Paste,
346    Redo,
347    RemoveFormat,
348    SelectAll,
349    Strikethrough,
350    StyleWithCss,
351    Subscript,
352    Superscript,
353    Underline,
354    Undo,
355    Unlink,
356    Usecss,
357}
358
359impl CommandName {
360    /// <https://w3c.github.io/editing/docs/execCommand/#indeterminate>
361    pub(crate) fn is_indeterminate(&self, cx: &mut JSContext, document: &Document) -> bool {
362        if !self.is_standard_inline_value_command() {
363            return false;
364        }
365        // https://w3c.github.io/editing/docs/execCommand/#standard-inline-value-command
366        // > it is indeterminate if among formattable nodes that are effectively contained in the active range,
367        // > there are two that have distinct effective command values.
368        let Some(selection) = document.GetSelection(cx) else {
369            return false;
370        };
371        let Some(active_range) = selection.active_range() else {
372            return false;
373        };
374        let mut at_least_two_different_effective_values = false;
375        let mut previous_effective_value: Option<DOMString> = None;
376        active_range.for_each_effectively_contained_child(|node| {
377            if at_least_two_different_effective_values || !node.is_formattable() {
378                return;
379            }
380            if let Some(effective_command_value) = node.effective_command_value(self) {
381                // https://w3c.github.io/editing/docs/execCommand/#the-subscript-command
382                // https://w3c.github.io/editing/docs/execCommand/#the-superscript-command
383                // > or if there is some formattable node effectively contained in
384                // > the active range with effective command value "mixed".
385                if matches!(self, CommandName::Subscript | CommandName::Superscript) &&
386                    effective_command_value == "mixed"
387                {
388                    at_least_two_different_effective_values = true;
389                }
390                if let Some(previous_effective_value) = &previous_effective_value {
391                    if &effective_command_value != previous_effective_value {
392                        at_least_two_different_effective_values = true;
393                    }
394                } else {
395                    previous_effective_value = Some(effective_command_value);
396                }
397            }
398        });
399        at_least_two_different_effective_values
400    }
401
402    /// <https://w3c.github.io/editing/docs/execCommand/#state>
403    pub(crate) fn current_state(&self, cx: &mut JSContext, document: &Document) -> Option<bool> {
404        Some(match self {
405            CommandName::StyleWithCss => {
406                // https://w3c.github.io/editing/docs/execCommand/#the-stylewithcss-command
407                // > True if the CSS styling flag is true, otherwise false.
408                document.css_styling_flag()
409            },
410            _ => {
411                // https://w3c.github.io/editing/docs/execCommand/#inline-formatting-command-definitions
412                // > If a command has inline command activated values defined, its state is true if either
413                // > no formattable node is effectively contained in the active range,
414                // > and the active range's start node's effective command value is one of the given values;
415                // > or if there is at least one formattable node effectively contained in the active range,
416                // > and all of them have an effective command value equal to one of the given values.
417                let inline_command_activated_values = self.inline_command_activated_values();
418                if inline_command_activated_values.is_empty() {
419                    return None;
420                }
421                let selection = document.GetSelection(cx)?;
422                let active_range = selection.active_range()?;
423                let mut at_least_one_child_is_formattable = false;
424                let mut all_children_have_matching_command_values = true;
425                active_range.for_each_effectively_contained_child(|node| {
426                    if !node.is_formattable() {
427                        return;
428                    }
429                    at_least_one_child_is_formattable = true;
430                    all_children_have_matching_command_values &= node
431                        .effective_command_value(self)
432                        .is_some_and(|effective_value| {
433                            inline_command_activated_values.contains(&&*effective_value.str())
434                        });
435                });
436                if at_least_one_child_is_formattable {
437                    all_children_have_matching_command_values
438                } else {
439                    active_range
440                        .start_container()
441                        .effective_command_value(self)
442                        .is_some_and(|effective_value| {
443                            inline_command_activated_values.contains(&&*effective_value.str())
444                        })
445                }
446            },
447        })
448    }
449
450    /// <https://w3c.github.io/editing/docs/execCommand/#value>
451    pub(crate) fn current_value(
452        &self,
453        cx: &mut JSContext,
454        document: &Document,
455    ) -> Option<DOMString> {
456        Some(match self {
457            CommandName::DefaultParagraphSeparator => {
458                // https://w3c.github.io/editing/docs/execCommand/#the-defaultparagraphseparator-command
459                // > Return the context object's default single-line container name.
460                document.default_single_line_container_name().into()
461            },
462            CommandName::FontSize => value_for_fontsize_command(cx, document)?,
463            _ if self.is_standard_inline_value_command() => {
464                // https://w3c.github.io/editing/docs/execCommand/#standard-inline-value-command
465                // > Its value is the effective command value of the first formattable node that
466                // > is effectively contained in the active range; or if there is no such node,
467                // > the effective command value of the active range's start node;
468                // > or if that is null, the empty string.
469                let selection = document.GetSelection(cx)?;
470                let active_range = selection.active_range()?;
471
472                active_range
473                    .first_formattable_contained_node()
474                    .unwrap_or_else(|| active_range.start_container())
475                    .effective_command_value(self)
476                    .unwrap_or_default()
477            },
478            _ => return None,
479        })
480    }
481
482    /// <https://w3c.github.io/editing/docs/execCommand/#equivalent-values>
483    pub(crate) fn are_equivalent_values(
484        &self,
485        first: Option<&DOMString>,
486        second: Option<&DOMString>,
487    ) -> bool {
488        match (first, second) {
489            // > Two quantities are equivalent values for a command if either both are null,
490            (None, None) => true,
491            (Some(first_str), Some(second_str)) => {
492                // > or both are strings and the command defines equivalent values and they match the definition.
493                match self {
494                    CommandName::Bold => {
495                        // https://w3c.github.io/editing/docs/execCommand/#the-bold-command
496                        // > Either the two strings are equal, or one is "bold" and the other is "700",
497                        // > or one is "normal" and the other is "400".
498                        first_str == second_str ||
499                            matches!(
500                                (first_str.str().as_ref(), second_str.str().as_ref()),
501                                ("bold", "700") |
502                                    ("700", "bold") |
503                                    ("normal", "400") |
504                                    ("400", "normal")
505                            )
506                    },
507                    CommandName::BackColor | CommandName::ForeColor | CommandName::HiliteColor => {
508                        // https://w3c.github.io/editing/docs/execCommand/#the-backcolor-command
509                        // https://w3c.github.io/editing/docs/execCommand/#the-forecolor-command
510                        // https://w3c.github.io/editing/docs/execCommand/#the-hilitecolor-command
511                        // > Either both strings are valid CSS colors and have the same red, green, blue, and alpha components,
512                        // > or neither string is a valid CSS color.
513                        match (
514                            parse_legacy_color(&first_str.str()),
515                            parse_legacy_color(&second_str.str()),
516                        ) {
517                            (Ok(first_legacy_color), Ok(second_legacy_color)) => {
518                                first_legacy_color == second_legacy_color
519                            },
520                            (Err(_), Err(_)) => true,
521                            _ => false,
522                        }
523                    },
524                    // > or both are strings and they're equal and the command does not define any equivalent values,
525                    _ => first_str == second_str,
526                }
527            },
528            _ => false,
529        }
530    }
531
532    /// <https://w3c.github.io/editing/docs/execCommand/#loosely-equivalent-values>
533    pub(crate) fn are_loosely_equivalent_values(
534        &self,
535        first: Option<&DOMString>,
536        second: Option<&DOMString>,
537    ) -> bool {
538        // > Two quantities are loosely equivalent values for a command if either they are equivalent values for the command,
539        if self.are_equivalent_values(first, second) {
540            return true;
541        }
542        // > or if the command is the fontSize command;
543        // > one of the quantities is one of "x-small", "small", "medium", "large", "x-large", "xx-large", or "xxx-large";
544        // > and the other quantity is the resolved value of "font-size" on a font element whose size attribute
545        // > has the corresponding value set ("1" through "7" respectively).
546        if let (CommandName::FontSize, Some(first), Some(second)) = (self, first, second) {
547            font_size_loosely_equivalent(first, second)
548        } else {
549            false
550        }
551    }
552
553    /// <https://w3c.github.io/editing/docs/execCommand/#record-current-overrides>
554    fn record_current_overrides(document: &Document) -> Vec<RecordedStateOfCommand> {
555        // Step 1. Let overrides be a list of (string, string or boolean) ordered pairs, initially empty.
556        let mut overrides = vec![];
557        // Step 2. If there is a value override for "createLink",
558        // add ("createLink", value override for "createLink") to overrides.
559        if let Some(value_override) =
560            RecordedStateOfCommand::for_command_value_override(CommandName::CreateLink, document)
561        {
562            overrides.push(value_override);
563        }
564        // Step 3. For each command in the list "bold", "italic", "strikethrough",
565        // "subscript", "superscript", "underline", in order:
566        // if there is a state override for command, add (command, command's state override) to overrides.
567        for command in [
568            CommandName::Bold,
569            CommandName::Italic,
570            CommandName::Strikethrough,
571            CommandName::Subscript,
572            CommandName::Superscript,
573            CommandName::Underline,
574        ] {
575            if let Some(state_override) =
576                RecordedStateOfCommand::for_command_state_override(command, document)
577            {
578                overrides.push(state_override);
579            }
580        }
581        // Step 4. For each command in the list "fontName", "fontSize", "foreColor", "hiliteColor",
582        // in order: if there is a value override for command,
583        // add (command, command's value override) to overrides.
584        for command in [
585            CommandName::FontName,
586            CommandName::FontSize,
587            CommandName::ForeColor,
588            CommandName::HiliteColor,
589        ] {
590            if let Some(value_override) =
591                RecordedStateOfCommand::for_command_value_override(command, document)
592            {
593                overrides.push(value_override);
594            }
595        }
596        // Step 5. Return overrides.
597        overrides
598    }
599
600    /// <https://w3c.github.io/editing/docs/execCommand/#relevant-css-property>
601    pub(crate) fn relevant_css_property(&self) -> Option<CssPropertyName> {
602        // > This is defined for certain inline formatting commands, and is used in algorithms specific to those commands.
603        // > It is an implementation detail, and is not exposed to authors.
604        Some(match self {
605            CommandName::BackColor => CssPropertyName::BackgroundColor,
606            CommandName::Bold => CssPropertyName::FontWeight,
607            CommandName::FontName => CssPropertyName::FontFamily,
608            CommandName::FontSize => CssPropertyName::FontSize,
609            CommandName::ForeColor => CssPropertyName::Color,
610            CommandName::HiliteColor => CssPropertyName::BackgroundColor,
611            CommandName::Italic => CssPropertyName::FontStyle,
612            // > If a command does not have a relevant CSS property specified, it defaults to null.
613            _ => return None,
614        })
615    }
616
617    pub(crate) fn resolved_value_for_node(&self, element: &Element) -> Option<DOMString> {
618        let property = self.relevant_css_property()?;
619        property.resolved_value_for_node(element)
620    }
621
622    /// <https://w3c.github.io/editing/docs/execCommand/#standard-inline-value-command>
623    pub(crate) fn is_standard_inline_value_command(&self) -> bool {
624        matches!(
625            self,
626            CommandName::BackColor |
627                CommandName::FontName |
628                CommandName::ForeColor |
629                CommandName::HiliteColor
630        )
631    }
632
633    pub(crate) fn is_enabled_in_plaintext_only_state(&self) -> bool {
634        matches!(
635            self,
636            CommandName::Copy |
637                CommandName::Cut |
638                CommandName::DefaultParagraphSeparator |
639                CommandName::FormatBlock |
640                CommandName::ForwardDelete |
641                CommandName::InsertHtml |
642                CommandName::InsertLineBreak |
643                CommandName::InsertParagraph |
644                CommandName::InsertText |
645                CommandName::Paste |
646                CommandName::Redo |
647                CommandName::StyleWithCss |
648                CommandName::Undo |
649                CommandName::Usecss |
650                CommandName::Delete
651        )
652    }
653
654    /// <https://w3c.github.io/editing/docs/execCommand/#preserves-overrides>
655    fn preserves_overrides(&self) -> bool {
656        matches!(
657            self,
658            CommandName::Delete |
659                CommandName::FormatBlock |
660                CommandName::ForwardDelete |
661                CommandName::Indent |
662                CommandName::InsertHorizontalRule |
663                CommandName::InsertHtml |
664                CommandName::InsertImage |
665                CommandName::InsertLineBreak |
666                CommandName::InsertOrderedList |
667                CommandName::InsertParagraph |
668                CommandName::InsertUnorderedList |
669                CommandName::JustifyCenter |
670                CommandName::JustifyFull |
671                CommandName::JustifyLeft |
672                CommandName::JustifyRight |
673                CommandName::Outdent
674        )
675    }
676
677    /// <https://w3c.github.io/editing/docs/execCommand/#action>
678    pub(crate) fn execute(
679        &self,
680        cx: &mut JSContext,
681        document: &Document,
682        selection: &Selection,
683        value: DOMString,
684    ) -> bool {
685        // https://w3c.github.io/editing/docs/execCommand/#preserves-overrides
686        // > If a command preserves overrides, then before taking its action,
687        // > the user agent must record current overrides.
688        let overrides = if self.preserves_overrides() {
689            Self::record_current_overrides(document)
690        } else {
691            vec![]
692        };
693        let result = match self {
694            CommandName::BackColor => execute_backcolor_command(cx, document, selection, value),
695            CommandName::Bold => execute_bold_command(cx, document, selection),
696            CommandName::CreateLink => execute_createlink_command(cx, document, selection, value),
697            CommandName::DefaultParagraphSeparator => {
698                execute_default_paragraph_separator_command(document, value)
699            },
700            CommandName::Delete => execute_delete_command(cx, document, selection),
701            CommandName::FontName => execute_fontname_command(cx, document, selection, value),
702            CommandName::FontSize => execute_fontsize_command(cx, document, selection, value),
703            CommandName::ForeColor => execute_forecolor_command(cx, document, selection, value),
704            CommandName::HiliteColor => execute_hilitecolor_command(cx, document, selection, value),
705            CommandName::InsertParagraph => {
706                execute_insert_paragraph_command(cx, document, selection)
707            },
708            CommandName::Italic => execute_italic_command(cx, document, selection),
709            CommandName::RemoveFormat => execute_removeformat_command(cx, document, selection),
710            CommandName::Strikethrough => execute_strikethrough_command(cx, document, selection),
711            CommandName::StyleWithCss => execute_style_with_css_command(document, value),
712            CommandName::Subscript => execute_subscript_command(cx, document, selection),
713            CommandName::Superscript => execute_superscript_command(cx, document, selection),
714            CommandName::Underline => execute_underline_command(cx, document, selection),
715            CommandName::Unlink => execute_unlink_command(cx, selection),
716            _ => false,
717        };
718
719        // https://w3c.github.io/editing/docs/execCommand/#preserves-overrides
720        // > After taking the action, if the active range is collapsed,
721        // > it must restore states and values from the recorded list.
722        if let Some(active_range) = selection
723            .active_range()
724            .filter(|active_range| active_range.collapsed())
725        {
726            active_range.restore_states_and_values(cx, selection, document, overrides);
727        }
728
729        result
730    }
731
732    /// <https://w3c.github.io/editing/docs/execCommand/#inline-command-activated-values>
733    pub(crate) fn inline_command_activated_values(&self) -> Vec<&str> {
734        match self {
735            // https://w3c.github.io/editing/docs/execCommand/#the-bold-command
736            CommandName::Bold => vec!["bold", "600", "700", "800", "900"],
737            // https://w3c.github.io/editing/docs/execCommand/#the-italic-command
738            CommandName::Italic => vec!["italic", "oblique"],
739            // https://w3c.github.io/editing/docs/execCommand/#the-strikethrough-command
740            CommandName::Strikethrough => vec!["line-through"],
741            // https://w3c.github.io/editing/docs/execCommand/#the-subscript-command
742            CommandName::Subscript => vec!["subscript"],
743            // https://w3c.github.io/editing/docs/execCommand/#the-superscript-command
744            CommandName::Superscript => vec!["superscript"],
745            // https://w3c.github.io/editing/docs/execCommand/#the-underline-command
746            CommandName::Underline => vec!["underline"],
747            _ => vec![],
748        }
749    }
750}