1use js::context::JSContext;
6use script_bindings::inheritance::Castable;
7use style::properties::PropertyDeclarationId;
8use style::properties::generated::{LonghandId, ShorthandId};
9use style::values::specified::text::TextDecorationLine;
10use style_traits::ToCss;
11
12use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
13use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
14use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
15use crate::dom::bindings::codegen::Bindings::HTMLFontElementBinding::HTMLFontElementMethods;
16use crate::dom::bindings::str::DOMString;
17use crate::dom::document::Document;
18use crate::dom::element::Element;
19use crate::dom::execcommand::commands::bold::execute_bold_command;
20use crate::dom::execcommand::commands::defaultparagraphseparator::execute_default_paragraph_separator_command;
21use crate::dom::execcommand::commands::delete::execute_delete_command;
22use crate::dom::execcommand::commands::fontname::execute_fontname_command;
23use crate::dom::execcommand::commands::fontsize::{
24 execute_fontsize_command, font_size_loosely_equivalent, value_for_fontsize_command,
25};
26use crate::dom::execcommand::commands::italic::execute_italic_command;
27use crate::dom::execcommand::commands::strikethrough::execute_strikethrough_command;
28use crate::dom::execcommand::commands::stylewithcss::execute_style_with_css_command;
29use crate::dom::execcommand::commands::underline::execute_underline_command;
30use crate::dom::html::htmlelement::HTMLElement;
31use crate::dom::html::htmlfontelement::HTMLFontElement;
32use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
33use crate::dom::selection::Selection;
34use crate::script_runtime::CanGc;
35
36#[derive(Default, Clone, Copy, MallocSizeOf)]
37pub(crate) enum DefaultSingleLineContainerName {
38 #[default]
39 Div,
40 Paragraph,
41}
42
43impl From<DefaultSingleLineContainerName> for DOMString {
44 fn from(default_single_line_container_name: DefaultSingleLineContainerName) -> Self {
45 match default_single_line_container_name {
46 DefaultSingleLineContainerName::Div => DOMString::from("div"),
47 DefaultSingleLineContainerName::Paragraph => DOMString::from("p"),
48 }
49 }
50}
51
52#[derive(Clone, Copy, Eq, PartialEq)]
54#[expect(unused)] pub(crate) enum CssPropertyName {
56 BackgroundColor,
57 FontFamily,
58 FontSize,
59 FontWeight,
60 FontStyle,
61 TextDecoration,
62 TextDecorationLine,
63}
64
65impl CssPropertyName {
66 pub(crate) fn resolved_value_for_node(&self, element: &Element) -> Option<DOMString> {
67 let style = element.style()?;
68
69 Some(
70 match self {
71 CssPropertyName::BackgroundColor => style.clone_background_color().to_css_string(),
72 CssPropertyName::FontFamily => style.clone_font_family().to_css_string(),
73 CssPropertyName::FontSize => {
74 return element
88 .upcast::<Node>()
89 .inclusive_ancestors(ShadowIncluding::No)
90 .find_map(|ancestor| {
91 if let Some(ancestor_font) = ancestor.downcast::<HTMLFontElement>() {
92 Some(ancestor_font.Size())
93 } else {
94 self.value_set_for_style(ancestor.downcast::<Element>()?)
95 }
96 })
97 .or_else(|| {
98 let pixels = style.get_font().font_size.computed_size().px();
99 Some(format!("{}px", pixels).into())
100 });
101 },
102 CssPropertyName::FontWeight => style.clone_font_weight().to_css_string(),
103 CssPropertyName::FontStyle => style.clone_font_style().to_css_string(),
104 CssPropertyName::TextDecoration => unreachable!("Should use longhands instead"),
105 CssPropertyName::TextDecorationLine => {
106 let text_decoration_line = style.get_text().text_decoration_line;
107 if text_decoration_line == TextDecorationLine::NONE {
108 return None;
109 }
110 text_decoration_line.to_css_string()
111 },
112 }
113 .into(),
114 )
115 }
116
117 pub(crate) fn value_set_for_style(&self, element: &Element) -> Option<DOMString> {
121 let style_attribute = element.style_attribute().borrow();
122 let declarations = style_attribute.as_ref()?;
123 let document = element.owner_document();
124 let shared_lock = document.style_shared_lock();
125 let read_lock = shared_lock.read();
126 let style = declarations.read_with(&read_lock);
127
128 let longhand_id = match self {
129 CssPropertyName::BackgroundColor => LonghandId::BackgroundColor,
130 CssPropertyName::FontFamily => LonghandId::FontFamily,
131 CssPropertyName::FontSize => LonghandId::FontSize,
132 CssPropertyName::FontWeight => LonghandId::FontWeight,
133 CssPropertyName::FontStyle => LonghandId::FontStyle,
134 CssPropertyName::TextDecoration => {
135 let mut dest = String::new();
136 style
137 .shorthand_to_css(ShorthandId::TextDecoration, &mut dest)
138 .ok()?;
139 return Some(dest.into());
140 },
141 CssPropertyName::TextDecorationLine => LonghandId::TextDecorationLine,
142 };
143 style
144 .get(PropertyDeclarationId::Longhand(longhand_id))
145 .and_then(|value| {
146 let mut dest = String::new();
147 value.0.to_css(&mut dest).ok()?;
148 Some(dest.into())
149 })
150 }
151
152 fn property_name(&self) -> DOMString {
153 match self {
154 CssPropertyName::BackgroundColor => "background-color",
155 CssPropertyName::FontFamily => "font-family",
156 CssPropertyName::FontSize => "font-size",
157 CssPropertyName::FontWeight => "font-weight",
158 CssPropertyName::FontStyle => "font-style",
159 CssPropertyName::TextDecoration => "text-decoration",
160 CssPropertyName::TextDecorationLine => "text-decoration-line",
161 }
162 .into()
163 }
164
165 pub(crate) fn set_for_element(
166 &self,
167 cx: &mut JSContext,
168 element: &HTMLElement,
169 new_value: DOMString,
170 ) {
171 let style = element.Style(CanGc::from_cx(cx));
172
173 let _ = style.SetProperty(cx, self.property_name(), new_value, "".into());
174 }
175
176 pub(crate) fn remove_from_element(&self, cx: &mut JSContext, element: &HTMLElement) {
177 let _ = element
178 .Style(CanGc::from_cx(cx))
179 .RemoveProperty(cx, self.property_name());
180 }
181}
182
183#[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq)]
184#[expect(unused)] pub(crate) enum CommandName {
186 BackColor,
187 Bold,
188 Copy,
189 CreateLink,
190 Cut,
191 DefaultParagraphSeparator,
192 Delete,
193 FontName,
194 FontSize,
195 ForeColor,
196 FormatBlock,
197 ForwardDelete,
198 HiliteColor,
199 Indent,
200 InsertHorizontalRule,
201 InsertHtml,
202 InsertLineBreak,
203 InsertOrderedList,
204 InsertParagraph,
205 InsertText,
206 InsertUnorderedList,
207 Italic,
208 JustifyCenter,
209 JustifyFull,
210 JustifyLeft,
211 JustifyRight,
212 Outdent,
213 Paste,
214 Redo,
215 SelectAll,
216 Strikethrough,
217 StyleWithCss,
218 Subscript,
219 Superscript,
220 Underline,
221 Undo,
222 Unlink,
223 Usecss,
224}
225
226impl CommandName {
227 pub(crate) fn is_indeterminate(&self) -> bool {
229 false
230 }
231
232 pub(crate) fn current_state(&self, cx: &mut JSContext, document: &Document) -> Option<bool> {
234 Some(match self {
235 CommandName::StyleWithCss => {
236 document.css_styling_flag()
239 },
240 _ => {
241 let inline_command_activated_values = self.inline_command_activated_values();
248 if inline_command_activated_values.is_empty() {
249 return None;
250 }
251 let selection = document.GetSelection(cx)?;
252 let active_range = selection.active_range()?;
253 let mut at_least_one_child_is_formattable = false;
254 let mut all_children_have_matching_command_values = true;
255 active_range.for_each_effectively_contained_child(|node| {
256 if !node.is_formattable() {
257 return;
258 }
259 at_least_one_child_is_formattable = true;
260 all_children_have_matching_command_values &= node
261 .effective_command_value(self)
262 .is_some_and(|effective_value| {
263 inline_command_activated_values.contains(&&*effective_value.str())
264 });
265 });
266 if at_least_one_child_is_formattable {
267 all_children_have_matching_command_values
268 } else {
269 active_range
270 .start_container()
271 .effective_command_value(self)
272 .is_some_and(|effective_value| {
273 inline_command_activated_values.contains(&&*effective_value.str())
274 })
275 }
276 },
277 })
278 }
279
280 pub(crate) fn current_value(
282 &self,
283 cx: &mut JSContext,
284 document: &Document,
285 ) -> Option<DOMString> {
286 Some(match self {
287 CommandName::DefaultParagraphSeparator => {
288 document.default_single_line_container_name().into()
291 },
292 CommandName::FontSize => value_for_fontsize_command(cx, document)?,
293 _ => return None,
294 })
295 }
296
297 pub(crate) fn are_equivalent_values(
299 &self,
300 first: Option<&DOMString>,
301 second: Option<&DOMString>,
302 ) -> bool {
303 match (first, second) {
304 (None, None) => true,
306 (Some(first_str), Some(second_str)) => {
307 match self {
309 CommandName::Bold => {
310 first_str == second_str ||
314 matches!(
315 (first_str.str().as_ref(), second_str.str().as_ref()),
316 ("bold", "700") |
317 ("700", "bold") |
318 ("normal", "400") |
319 ("400", "normal")
320 )
321 },
322 _ => first_str == second_str,
324 }
325 },
326 _ => false,
327 }
328 }
329
330 pub(crate) fn are_loosely_equivalent_values(
332 &self,
333 first: Option<&DOMString>,
334 second: Option<&DOMString>,
335 ) -> bool {
336 if self.are_equivalent_values(first, second) {
338 return true;
339 }
340 if let (CommandName::FontSize, Some(first), Some(second)) = (self, first, second) {
345 font_size_loosely_equivalent(first, second)
346 } else {
347 false
348 }
349 }
350
351 pub(crate) fn relevant_css_property(&self) -> Option<CssPropertyName> {
353 Some(match self {
356 CommandName::Bold => CssPropertyName::FontWeight,
357 CommandName::FontName => CssPropertyName::FontFamily,
358 CommandName::FontSize => CssPropertyName::FontSize,
359 CommandName::Italic => CssPropertyName::FontStyle,
360 _ => return None,
362 })
363 }
364
365 pub(crate) fn resolved_value_for_node(&self, element: &Element) -> Option<DOMString> {
366 let property = self.relevant_css_property()?;
367 property.resolved_value_for_node(element)
368 }
369
370 pub(crate) fn is_enabled_in_plaintext_only_state(&self) -> bool {
371 matches!(
372 self,
373 CommandName::Copy |
374 CommandName::Cut |
375 CommandName::DefaultParagraphSeparator |
376 CommandName::FormatBlock |
377 CommandName::ForwardDelete |
378 CommandName::InsertHtml |
379 CommandName::InsertLineBreak |
380 CommandName::InsertParagraph |
381 CommandName::InsertText |
382 CommandName::Paste |
383 CommandName::Redo |
384 CommandName::StyleWithCss |
385 CommandName::Undo |
386 CommandName::Usecss |
387 CommandName::Delete
388 )
389 }
390
391 pub(crate) fn execute(
393 &self,
394 cx: &mut JSContext,
395 document: &Document,
396 selection: &Selection,
397 value: DOMString,
398 ) -> bool {
399 match self {
400 CommandName::Bold => execute_bold_command(cx, document, selection),
401 CommandName::DefaultParagraphSeparator => {
402 execute_default_paragraph_separator_command(document, value)
403 },
404 CommandName::Delete => execute_delete_command(cx, document, selection),
405 CommandName::FontName => execute_fontname_command(cx, document, selection, value),
406 CommandName::FontSize => execute_fontsize_command(cx, document, selection, value),
407 CommandName::Italic => execute_italic_command(cx, document, selection),
408 CommandName::Strikethrough => execute_strikethrough_command(cx, document, selection),
409 CommandName::StyleWithCss => execute_style_with_css_command(document, value),
410 CommandName::Underline => execute_underline_command(cx, document, selection),
411 _ => false,
412 }
413 }
414
415 pub(crate) fn inline_command_activated_values(&self) -> Vec<&str> {
417 match self {
418 CommandName::Bold => vec!["bold", "600", "700", "800", "900"],
420 CommandName::Italic => vec!["italic", "oblique"],
422 CommandName::Strikethrough => vec!["line-through"],
424 CommandName::Subscript => vec!["subscript"],
426 CommandName::Superscript => vec!["superscript"],
428 CommandName::Underline => vec!["underline"],
430 _ => vec![],
431 }
432 }
433}