script/dom/execcommand/contenteditable/
element.rs1use html5ever::{LocalName, local_name, ns};
6use js::context::JSContext;
7use script_bindings::inheritance::Castable;
8use style::attr::AttrValue;
9use style::properties::{LonghandId, PropertyDeclaration, PropertyDeclarationId, ShorthandId};
10use style::values::specified::TextDecorationLine;
11use style::values::specified::box_::DisplayOutside;
12
13use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
14use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
15use crate::dom::bindings::root::DomRoot;
16use crate::dom::bindings::str::DOMString;
17use crate::dom::element::Element;
18use crate::dom::execcommand::basecommand::{CommandName, CssPropertyName};
19use crate::dom::execcommand::commands::fontsize::font_size_to_css_font;
20use crate::dom::execcommand::contenteditable::node::move_preserving_ranges;
21use crate::dom::html::htmlanchorelement::HTMLAnchorElement;
22use crate::dom::html::htmlfontelement::HTMLFontElement;
23use crate::dom::node::node::{Node, NodeTraits};
24
25impl Element {
26 pub(crate) fn resolved_display_value(&self) -> Option<DisplayOutside> {
27 self.style().map(|style| style.get_box().display.outside())
28 }
29
30 pub(crate) fn specified_command_value(&self, command: &CommandName) -> Option<DOMString> {
32 match command {
33 CommandName::BackColor | CommandName::HiliteColor
35 if self
36 .resolved_display_value()
37 .is_none_or(|display| display != DisplayOutside::Inline) =>
38 {
39 return None;
40 },
41 CommandName::CreateLink | CommandName::Unlink => {
43 if let Some(anchor) = self.downcast::<HTMLAnchorElement>() {
46 return anchor
47 .upcast::<Element>()
48 .get_attribute_string_value(&local_name!("href"))
49 .map(|value| value.into());
50 }
51
52 return None;
54 },
55 CommandName::Subscript | CommandName::Superscript => {
57 if matches!(*self.local_name(), local_name!("sup")) {
59 return Some(DOMString::from_static("superscript"));
60 }
61 if matches!(*self.local_name(), local_name!("sub")) {
63 return Some(DOMString::from_static("subscript"));
64 }
65 return None;
67 },
68 CommandName::Strikethrough => {
69 if let Some(value) = CssPropertyName::TextDecorationLine.value_set_for_style(self) {
71 return value
74 .contains("line-through")
75 .then_some(DOMString::from_static("line-through"));
76 }
77 if matches!(*self.local_name(), local_name!("s") | local_name!("strike")) {
79 return Some(DOMString::from_static("line-through"));
80 }
81 },
82 CommandName::Underline => {
83 if let Some(value) = CssPropertyName::TextDecorationLine.value_set_for_style(self) {
85 return value
88 .contains("underline")
89 .then_some(DOMString::from_static("underline"));
90 }
91 if *self.local_name() == local_name!("u") {
93 return Some(DOMString::from_static("underline"));
94 }
95 },
96 _ => {},
97 };
98 let property = command.relevant_css_property()?;
101 if let Some(value) = property.value_set_for_style(self) {
104 return Some(value);
105 }
106 if self.is::<HTMLFontElement>() &&
109 let Some(font_size) = self
110 .with_attribute(&ns!(), &local_name!("size"), |attribute| {
111 if let AttrValue::UInt(_, value) = *attribute.value() {
112 Some(value)
113 } else {
114 None
115 }
116 })
117 .flatten()
118 {
119 return Some(font_size_to_css_font(&font_size).into());
120 }
121
122 let element_name = self.local_name();
125 match property {
126 CssPropertyName::FontWeight
127 if element_name == &local_name!("b") || element_name == &local_name!("strong") =>
128 {
129 Some(DOMString::from_static("bold"))
130 },
131 CssPropertyName::FontStyle
132 if element_name == &local_name!("i") || element_name == &local_name!("em") =>
133 {
134 Some(DOMString::from_static("italic"))
135 },
136 _ => None,
138 }
139 }
140
141 pub(crate) fn is_modifiable_element(&self) -> bool {
143 let attrs = self.attrs().borrow();
144 let mut attrs = attrs.iter();
145 let type_id = self.upcast::<Node>().type_id();
146
147 if matches!(
150 type_id,
151 NodeTypeId::Element(ElementTypeId::HTMLElement(
152 HTMLElementTypeId::HTMLSpanElement,
153 ))
154 ) || matches!(
155 *self.local_name(),
156 local_name!("b") |
157 local_name!("em") |
158 local_name!("i") |
159 local_name!("s") |
160 local_name!("strike") |
161 local_name!("strong") |
162 local_name!("sub") |
163 local_name!("sup") |
164 local_name!("u")
165 ) {
166 return attrs.all(|attr| attr.local_name() == &local_name!("style"));
167 }
168
169 if matches!(
171 type_id,
172 NodeTypeId::Element(ElementTypeId::HTMLElement(
173 HTMLElementTypeId::HTMLFontElement,
174 ))
175 ) {
176 return attrs.all(|attr| {
177 matches!(
178 *attr.local_name(),
179 local_name!("style") |
180 local_name!("color") |
181 local_name!("face") |
182 local_name!("size")
183 )
184 });
185 }
186
187 if matches!(
189 type_id,
190 NodeTypeId::Element(ElementTypeId::HTMLElement(
191 HTMLElementTypeId::HTMLAnchorElement,
192 ))
193 ) {
194 return attrs.all(|attr| {
195 matches!(
196 *attr.local_name(),
197 local_name!("style") | local_name!("href")
198 )
199 });
200 }
201
202 false
203 }
204
205 pub(crate) fn is_indentation_element(&self) -> bool {
207 if !matches!(
209 *self.local_name(),
210 local_name!("blockquote") | local_name!("div")
211 ) {
212 return false;
213 }
214
215 let style_attribute = self.style_attribute().borrow();
217 let Some(declarations) = style_attribute.as_ref() else {
218 return false;
219 };
220 let document = self.owner_document();
221 let shared_lock = document.style_shared_author_lock();
222 let read_lock = shared_lock.read();
223 let style = declarations.read_with(&read_lock);
224
225 ShorthandId::Margin
226 .longhands()
227 .any(|longhand| style.contains(PropertyDeclarationId::Longhand(longhand)))
228 }
229
230 pub(crate) fn has_empty_style_attribute(&self) -> bool {
231 let style_attribute = self.style_attribute().borrow();
232 style_attribute.as_ref().is_some_and(|declarations| {
233 let document = self.owner_document();
234 let shared_lock = document.style_shared_author_lock();
235 let read_lock = shared_lock.read();
236 let style = declarations.read_with(&read_lock);
237
238 style.is_empty()
239 })
240 }
241
242 pub(crate) fn is_non_list_single_line_container(&self) -> bool {
244 matches!(
247 *self.local_name(),
248 local_name!("address") |
249 local_name!("div") |
250 local_name!("h1") |
251 local_name!("h2") |
252 local_name!("h3") |
253 local_name!("h4") |
254 local_name!("h5") |
255 local_name!("h6") |
256 local_name!("listing") |
257 local_name!("p") |
258 local_name!("pre") |
259 local_name!("xmp")
260 )
261 }
262
263 pub(crate) fn is_simple_modifiable_element(&self) -> bool {
265 let attrs = self.attrs().borrow();
266 let attr_count = attrs.len();
267 let type_id = self.upcast::<Node>().type_id();
268
269 if matches!(
270 type_id,
271 NodeTypeId::Element(ElementTypeId::HTMLElement(
272 HTMLElementTypeId::HTMLAnchorElement,
273 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
274 HTMLElementTypeId::HTMLFontElement,
275 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
276 HTMLElementTypeId::HTMLSpanElement,
277 ))
278 ) || matches!(
279 *self.local_name(),
280 local_name!("b") |
281 local_name!("em") |
282 local_name!("i") |
283 local_name!("s") |
284 local_name!("strike") |
285 local_name!("strong") |
286 local_name!("sub") |
287 local_name!("sup") |
288 local_name!("u")
289 ) {
290 if attr_count == 0 {
292 return true;
293 }
294
295 if attr_count == 1 &&
299 attrs.first().expect("Size is 1").local_name() == &local_name!("style") &&
300 self.has_empty_style_attribute()
301 {
302 return true;
303 }
304 }
305
306 if attr_count != 1 {
307 return false;
308 }
309
310 let first_attr = attrs.first().expect("Size is 1");
311 let only_attribute = first_attr.local_name();
312
313 if matches!(
315 type_id,
316 NodeTypeId::Element(ElementTypeId::HTMLElement(
317 HTMLElementTypeId::HTMLAnchorElement,
318 ))
319 ) {
320 return only_attribute == &local_name!("href");
321 }
322
323 if matches!(
325 type_id,
326 NodeTypeId::Element(ElementTypeId::HTMLElement(
327 HTMLElementTypeId::HTMLFontElement,
328 ))
329 ) {
330 return only_attribute == &local_name!("color") ||
331 only_attribute == &local_name!("face") ||
332 only_attribute == &local_name!("size");
333 }
334
335 if only_attribute != &local_name!("style") {
336 return false;
337 }
338 let style_attribute = self.style_attribute().borrow();
339 let Some(declarations) = style_attribute.as_ref() else {
340 return false;
341 };
342 let document = self.owner_document();
343 let shared_lock = document.style_shared_author_lock();
344 let read_lock = shared_lock.read();
345 let style = declarations.read_with(&read_lock);
346
347 if matches!(*self.local_name(), local_name!("b") | local_name!("strong")) {
351 return style.len() == 1 &&
352 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontWeight));
353 }
354
355 if matches!(*self.local_name(), local_name!("i") | local_name!("em")) {
359 return style.len() == 1 &&
360 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontStyle));
361 }
362
363 let a_font_or_span = matches!(
364 type_id,
365 NodeTypeId::Element(ElementTypeId::HTMLElement(
366 HTMLElementTypeId::HTMLAnchorElement,
367 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
368 HTMLElementTypeId::HTMLFontElement,
369 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
370 HTMLElementTypeId::HTMLSpanElement,
371 ))
372 );
373 let s_strike_or_u = matches!(
374 *self.local_name(),
375 local_name!("s") | local_name!("strike") | local_name!("u")
376 );
377 if a_font_or_span || s_strike_or_u {
378 if style.len() == ShorthandId::TextDecoration.longhands().count() &&
381 style
382 .shorthand_to_css(ShorthandId::TextDecoration, &mut String::new())
383 .is_ok()
384 {
385 if let Some((text_decoration, _)) = style.get(PropertyDeclarationId::Longhand(
386 LonghandId::TextDecorationLine,
387 )) {
388 return matches!(
393 text_decoration,
394 PropertyDeclaration::TextDecorationLine(
395 TextDecorationLine::LINE_THROUGH |
396 TextDecorationLine::UNDERLINE |
397 TextDecorationLine::OVERLINE |
398 TextDecorationLine::NONE
399 )
400 );
401 }
402 } else if a_font_or_span {
403 return style.len() == 1;
407 }
408 }
409
410 false
411 }
412
413 pub(crate) fn is_simple_indentation_element(&self) -> bool {
414 if !self.is_indentation_element() {
416 return false;
417 }
418
419 let attrs = self.attrs().borrow();
421 let mut attrs = attrs.iter();
422 attrs.all(|attr| {
423 if matches!(*attr.local_name(), local_name!("style")) {
425 let style_attribute = self.style_attribute().borrow();
426 let Some(declarations) = style_attribute.as_ref() else {
427 return false;
428 };
429 let document = self.owner_document();
430 let shared_lock = document.style_shared_author_lock();
431 let read_lock = shared_lock.read();
432 let style = declarations.read_with(&read_lock);
433
434 let properties: Vec<_> = ShorthandId::Margin
435 .longhands()
436 .chain(ShorthandId::Border.longhands())
437 .chain(ShorthandId::Padding.longhands())
438 .collect();
439 return style.declarations().iter().all(|declaration| {
440 declaration
441 .id()
442 .as_longhand()
443 .is_some_and(|longhand| properties.contains(&longhand))
444 });
445 }
446
447 if matches!(*attr.local_name(), local_name!("dir")) {
449 return true;
450 }
451
452 false
453 })
454 }
455
456 pub(crate) fn set_the_tag_name(&self, cx: &mut JSContext, new_name: &str) -> DomRoot<Node> {
458 if self.local_name() == &LocalName::from(new_name) {
460 return DomRoot::upcast(DomRoot::from_ref(self));
461 }
462 let node = self.upcast::<Node>();
464 let Some(parent) = node.GetParentNode() else {
465 return DomRoot::upcast(DomRoot::from_ref(self));
466 };
467 let document = node.owner_document();
469 let replacement = document.create_element(cx, new_name);
470 let replacement_node = replacement.upcast::<Node>();
471 if parent
473 .InsertBefore(cx, replacement_node, Some(node))
474 .is_err()
475 {
476 unreachable!("Must always be able to insert");
477 }
478 self.copy_all_attributes_to_other_element(cx, &replacement);
480 for child in node.children() {
482 move_preserving_ranges(cx, &child, |cx| replacement_node.AppendChild(cx, &child));
483 }
484 node.remove_self(cx);
486 DomRoot::upcast(replacement)
488 }
489}