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("superscript".into());
60 }
61 if matches!(*self.local_name(), local_name!("sub")) {
63 return Some("subscript".into());
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("line-through".into());
76 }
77 if matches!(*self.local_name(), local_name!("s") | local_name!("strike")) {
79 return Some("line-through".into());
80 }
81 },
82 CommandName::Underline => {
83 if let Some(value) = CssPropertyName::TextDecorationLine.value_set_for_style(self) {
85 return value.contains("underline").then_some("underline".into());
88 }
89 if *self.local_name() == local_name!("u") {
91 return Some("underline".into());
92 }
93 },
94 _ => {},
95 };
96 let property = command.relevant_css_property()?;
99 if let Some(value) = property.value_set_for_style(self) {
102 return Some(value);
103 }
104 if self.is::<HTMLFontElement>() &&
107 let Some(font_size) = self
108 .with_attribute(&ns!(), &local_name!("size"), |attribute| {
109 if let AttrValue::UInt(_, value) = *attribute.value() {
110 Some(value)
111 } else {
112 None
113 }
114 })
115 .flatten()
116 {
117 return Some(font_size_to_css_font(&font_size).into());
118 }
119
120 let element_name = self.local_name();
123 match property {
124 CssPropertyName::FontWeight
125 if element_name == &local_name!("b") || element_name == &local_name!("strong") =>
126 {
127 Some("bold".into())
128 },
129 CssPropertyName::FontStyle
130 if element_name == &local_name!("i") || element_name == &local_name!("em") =>
131 {
132 Some("italic".into())
133 },
134 _ => None,
136 }
137 }
138
139 pub(crate) fn is_modifiable_element(&self) -> bool {
141 let attrs = self.attrs().borrow();
142 let mut attrs = attrs.iter();
143 let type_id = self.upcast::<Node>().type_id();
144
145 if matches!(
148 type_id,
149 NodeTypeId::Element(ElementTypeId::HTMLElement(
150 HTMLElementTypeId::HTMLSpanElement,
151 ))
152 ) || matches!(
153 *self.local_name(),
154 local_name!("b") |
155 local_name!("em") |
156 local_name!("i") |
157 local_name!("s") |
158 local_name!("strike") |
159 local_name!("strong") |
160 local_name!("sub") |
161 local_name!("sup") |
162 local_name!("u")
163 ) {
164 return attrs.all(|attr| attr.local_name() == &local_name!("style"));
165 }
166
167 if matches!(
169 type_id,
170 NodeTypeId::Element(ElementTypeId::HTMLElement(
171 HTMLElementTypeId::HTMLFontElement,
172 ))
173 ) {
174 return attrs.all(|attr| {
175 matches!(
176 *attr.local_name(),
177 local_name!("style") |
178 local_name!("color") |
179 local_name!("face") |
180 local_name!("size")
181 )
182 });
183 }
184
185 if matches!(
187 type_id,
188 NodeTypeId::Element(ElementTypeId::HTMLElement(
189 HTMLElementTypeId::HTMLAnchorElement,
190 ))
191 ) {
192 return attrs.all(|attr| {
193 matches!(
194 *attr.local_name(),
195 local_name!("style") | local_name!("href")
196 )
197 });
198 }
199
200 false
201 }
202
203 pub(crate) fn has_empty_style_attribute(&self) -> bool {
204 let style_attribute = self.style_attribute().borrow();
205 style_attribute.as_ref().is_some_and(|declarations| {
206 let document = self.owner_document();
207 let shared_lock = document.style_shared_author_lock();
208 let read_lock = shared_lock.read();
209 let style = declarations.read_with(&read_lock);
210
211 style.is_empty()
212 })
213 }
214
215 pub(crate) fn is_non_list_single_line_container(&self) -> bool {
217 matches!(
220 *self.local_name(),
221 local_name!("address") |
222 local_name!("div") |
223 local_name!("h1") |
224 local_name!("h2") |
225 local_name!("h3") |
226 local_name!("h4") |
227 local_name!("h5") |
228 local_name!("h6") |
229 local_name!("listing") |
230 local_name!("p") |
231 local_name!("pre") |
232 local_name!("xmp")
233 )
234 }
235
236 pub(crate) fn is_simple_modifiable_element(&self) -> bool {
238 let attrs = self.attrs().borrow();
239 let attr_count = attrs.len();
240 let type_id = self.upcast::<Node>().type_id();
241
242 if matches!(
243 type_id,
244 NodeTypeId::Element(ElementTypeId::HTMLElement(
245 HTMLElementTypeId::HTMLAnchorElement,
246 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
247 HTMLElementTypeId::HTMLFontElement,
248 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
249 HTMLElementTypeId::HTMLSpanElement,
250 ))
251 ) || matches!(
252 *self.local_name(),
253 local_name!("b") |
254 local_name!("em") |
255 local_name!("i") |
256 local_name!("s") |
257 local_name!("strike") |
258 local_name!("strong") |
259 local_name!("sub") |
260 local_name!("sup") |
261 local_name!("u")
262 ) {
263 if attr_count == 0 {
265 return true;
266 }
267
268 if attr_count == 1 &&
272 attrs.first().expect("Size is 1").local_name() == &local_name!("style") &&
273 self.has_empty_style_attribute()
274 {
275 return true;
276 }
277 }
278
279 if attr_count != 1 {
280 return false;
281 }
282
283 let first_attr = attrs.first().expect("Size is 1");
284 let only_attribute = first_attr.local_name();
285
286 if matches!(
288 type_id,
289 NodeTypeId::Element(ElementTypeId::HTMLElement(
290 HTMLElementTypeId::HTMLAnchorElement,
291 ))
292 ) {
293 return only_attribute == &local_name!("href");
294 }
295
296 if matches!(
298 type_id,
299 NodeTypeId::Element(ElementTypeId::HTMLElement(
300 HTMLElementTypeId::HTMLFontElement,
301 ))
302 ) {
303 return only_attribute == &local_name!("color") ||
304 only_attribute == &local_name!("face") ||
305 only_attribute == &local_name!("size");
306 }
307
308 if only_attribute != &local_name!("style") {
309 return false;
310 }
311 let style_attribute = self.style_attribute().borrow();
312 let Some(declarations) = style_attribute.as_ref() else {
313 return false;
314 };
315 let document = self.owner_document();
316 let shared_lock = document.style_shared_author_lock();
317 let read_lock = shared_lock.read();
318 let style = declarations.read_with(&read_lock);
319
320 if matches!(*self.local_name(), local_name!("b") | local_name!("strong")) {
324 return style.len() == 1 &&
325 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontWeight));
326 }
327
328 if matches!(*self.local_name(), local_name!("i") | local_name!("em")) {
332 return style.len() == 1 &&
333 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontStyle));
334 }
335
336 let a_font_or_span = matches!(
337 type_id,
338 NodeTypeId::Element(ElementTypeId::HTMLElement(
339 HTMLElementTypeId::HTMLAnchorElement,
340 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
341 HTMLElementTypeId::HTMLFontElement,
342 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
343 HTMLElementTypeId::HTMLSpanElement,
344 ))
345 );
346 let s_strike_or_u = matches!(
347 *self.local_name(),
348 local_name!("s") | local_name!("strike") | local_name!("u")
349 );
350 if a_font_or_span || s_strike_or_u {
351 if style.len() == ShorthandId::TextDecoration.longhands().count() &&
354 style
355 .shorthand_to_css(ShorthandId::TextDecoration, &mut String::new())
356 .is_ok()
357 {
358 if let Some((text_decoration, _)) = style.get(PropertyDeclarationId::Longhand(
359 LonghandId::TextDecorationLine,
360 )) {
361 return matches!(
366 text_decoration,
367 PropertyDeclaration::TextDecorationLine(
368 TextDecorationLine::LINE_THROUGH |
369 TextDecorationLine::UNDERLINE |
370 TextDecorationLine::OVERLINE |
371 TextDecorationLine::NONE
372 )
373 );
374 }
375 } else if a_font_or_span {
376 return style.len() == 1;
380 }
381 }
382
383 false
384 }
385
386 pub(crate) fn set_the_tag_name(&self, cx: &mut JSContext, new_name: &str) -> DomRoot<Node> {
388 if self.local_name() == &LocalName::from(new_name) {
390 return DomRoot::upcast(DomRoot::from_ref(self));
391 }
392 let node = self.upcast::<Node>();
394 let Some(parent) = node.GetParentNode() else {
395 return DomRoot::upcast(DomRoot::from_ref(self));
396 };
397 let document = node.owner_document();
399 let replacement = document.create_element(cx, new_name);
400 let replacement_node = replacement.upcast::<Node>();
401 if parent
403 .InsertBefore(cx, replacement_node, Some(node))
404 .is_err()
405 {
406 unreachable!("Must always be able to insert");
407 }
408 self.copy_all_attributes_to_other_element(cx, &replacement);
410 for child in node.children() {
412 move_preserving_ranges(cx, &child, |cx| replacement_node.AppendChild(cx, &child));
413 }
414 node.remove_self(cx);
416 DomRoot::upcast(replacement)
418 }
419}