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