script/dom/execcommand/contenteditable/
element.rs1use html5ever::{LocalName, local_name};
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(&local_name!("href"))
49 .map(|attr| DOMString::from(&**attr.value()));
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.get_attribute(&local_name!("size")) &&
106 let AttrValue::UInt(_, value) = *font_size.value()
107 {
108 return Some(font_size_to_css_font(&value).into());
109 }
110
111 let element_name = self.local_name();
114 match property {
115 CssPropertyName::FontWeight
116 if element_name == &local_name!("b") || element_name == &local_name!("strong") =>
117 {
118 Some("bold".into())
119 },
120 CssPropertyName::FontStyle
121 if element_name == &local_name!("i") || element_name == &local_name!("em") =>
122 {
123 Some("italic".into())
124 },
125 _ => None,
127 }
128 }
129
130 pub(crate) fn is_modifiable_element(&self) -> bool {
132 let attrs = self.attrs().borrow();
133 let mut attrs = attrs.iter();
134 let type_id = self.upcast::<Node>().type_id();
135
136 if matches!(
139 type_id,
140 NodeTypeId::Element(ElementTypeId::HTMLElement(
141 HTMLElementTypeId::HTMLSpanElement,
142 ))
143 ) || matches!(
144 *self.local_name(),
145 local_name!("b") |
146 local_name!("em") |
147 local_name!("i") |
148 local_name!("s") |
149 local_name!("strike") |
150 local_name!("strong") |
151 local_name!("sub") |
152 local_name!("sup") |
153 local_name!("u")
154 ) {
155 return attrs.all(|attr| attr.local_name() == &local_name!("style"));
156 }
157
158 if matches!(
160 type_id,
161 NodeTypeId::Element(ElementTypeId::HTMLElement(
162 HTMLElementTypeId::HTMLFontElement,
163 ))
164 ) {
165 return attrs.all(|attr| {
166 matches!(
167 *attr.local_name(),
168 local_name!("style") |
169 local_name!("color") |
170 local_name!("face") |
171 local_name!("size")
172 )
173 });
174 }
175
176 if matches!(
178 type_id,
179 NodeTypeId::Element(ElementTypeId::HTMLElement(
180 HTMLElementTypeId::HTMLAnchorElement,
181 ))
182 ) {
183 return attrs.all(|attr| {
184 matches!(
185 *attr.local_name(),
186 local_name!("style") | local_name!("href")
187 )
188 });
189 }
190
191 false
192 }
193
194 pub(crate) fn has_empty_style_attribute(&self) -> bool {
195 let style_attribute = self.style_attribute().borrow();
196 style_attribute.as_ref().is_some_and(|declarations| {
197 let document = self.owner_document();
198 let shared_lock = document.style_shared_author_lock();
199 let read_lock = shared_lock.read();
200 let style = declarations.read_with(&read_lock);
201
202 style.is_empty()
203 })
204 }
205
206 pub(crate) fn is_non_list_single_line_container(&self) -> bool {
208 matches!(
211 *self.local_name(),
212 local_name!("address") |
213 local_name!("div") |
214 local_name!("h1") |
215 local_name!("h2") |
216 local_name!("h3") |
217 local_name!("h4") |
218 local_name!("h5") |
219 local_name!("h6") |
220 local_name!("listing") |
221 local_name!("p") |
222 local_name!("pre") |
223 local_name!("xmp")
224 )
225 }
226
227 pub(crate) fn is_simple_modifiable_element(&self) -> bool {
229 let attrs = self.attrs().borrow();
230 let attr_count = attrs.len();
231 let type_id = self.upcast::<Node>().type_id();
232
233 if matches!(
234 type_id,
235 NodeTypeId::Element(ElementTypeId::HTMLElement(
236 HTMLElementTypeId::HTMLAnchorElement,
237 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
238 HTMLElementTypeId::HTMLFontElement,
239 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
240 HTMLElementTypeId::HTMLSpanElement,
241 ))
242 ) || matches!(
243 *self.local_name(),
244 local_name!("b") |
245 local_name!("em") |
246 local_name!("i") |
247 local_name!("s") |
248 local_name!("strike") |
249 local_name!("strong") |
250 local_name!("sub") |
251 local_name!("sup") |
252 local_name!("u")
253 ) {
254 if attr_count == 0 {
256 return true;
257 }
258
259 if attr_count == 1 &&
263 attrs.first().expect("Size is 1").local_name() == &local_name!("style") &&
264 self.has_empty_style_attribute()
265 {
266 return true;
267 }
268 }
269
270 if attr_count != 1 {
271 return false;
272 }
273
274 let first_attr = attrs.first().expect("Size is 1");
275 let only_attribute = first_attr.local_name();
276
277 if matches!(
279 type_id,
280 NodeTypeId::Element(ElementTypeId::HTMLElement(
281 HTMLElementTypeId::HTMLAnchorElement,
282 ))
283 ) {
284 return only_attribute == &local_name!("href");
285 }
286
287 if matches!(
289 type_id,
290 NodeTypeId::Element(ElementTypeId::HTMLElement(
291 HTMLElementTypeId::HTMLFontElement,
292 ))
293 ) {
294 return only_attribute == &local_name!("color") ||
295 only_attribute == &local_name!("face") ||
296 only_attribute == &local_name!("size");
297 }
298
299 if only_attribute != &local_name!("style") {
300 return false;
301 }
302 let style_attribute = self.style_attribute().borrow();
303 let Some(declarations) = style_attribute.as_ref() else {
304 return false;
305 };
306 let document = self.owner_document();
307 let shared_lock = document.style_shared_author_lock();
308 let read_lock = shared_lock.read();
309 let style = declarations.read_with(&read_lock);
310
311 if matches!(*self.local_name(), local_name!("b") | local_name!("strong")) {
315 return style.len() == 1 &&
316 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontWeight));
317 }
318
319 if matches!(*self.local_name(), local_name!("i") | local_name!("em")) {
323 return style.len() == 1 &&
324 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontStyle));
325 }
326
327 let a_font_or_span = matches!(
328 type_id,
329 NodeTypeId::Element(ElementTypeId::HTMLElement(
330 HTMLElementTypeId::HTMLAnchorElement,
331 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
332 HTMLElementTypeId::HTMLFontElement,
333 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
334 HTMLElementTypeId::HTMLSpanElement,
335 ))
336 );
337 let s_strike_or_u = matches!(
338 *self.local_name(),
339 local_name!("s") | local_name!("strike") | local_name!("u")
340 );
341 if a_font_or_span || s_strike_or_u {
342 if style.len() == 3 &&
345 style
346 .shorthand_to_css(ShorthandId::TextDecoration, &mut String::new())
347 .is_ok()
348 {
349 if let Some((text_decoration, _)) = style.get(PropertyDeclarationId::Longhand(
350 LonghandId::TextDecorationLine,
351 )) {
352 return matches!(
357 text_decoration,
358 PropertyDeclaration::TextDecorationLine(
359 TextDecorationLine::LINE_THROUGH |
360 TextDecorationLine::UNDERLINE |
361 TextDecorationLine::OVERLINE |
362 TextDecorationLine::NONE
363 )
364 );
365 }
366 } else if a_font_or_span {
367 return style.len() == 1;
371 }
372 }
373
374 false
375 }
376
377 pub(crate) fn set_the_tag_name(&self, cx: &mut JSContext, new_name: &str) -> DomRoot<Node> {
379 if self.local_name() == &LocalName::from(new_name) {
381 return DomRoot::upcast(DomRoot::from_ref(self));
382 }
383 let node = self.upcast::<Node>();
385 let Some(parent) = node.GetParentNode() else {
386 return DomRoot::upcast(DomRoot::from_ref(self));
387 };
388 let document = node.owner_document();
390 let replacement = document.create_element(cx, new_name);
391 let replacement_node = replacement.upcast::<Node>();
392 if parent
394 .InsertBefore(cx, replacement_node, Some(node))
395 .is_err()
396 {
397 unreachable!("Must always be able to insert");
398 }
399 self.copy_all_attributes_to_other_element(cx, &replacement);
401 for child in node.children() {
403 move_preserving_ranges(cx, &child, |cx| replacement_node.AppendChild(cx, &child));
404 }
405 node.remove_self(cx);
407 DomRoot::upcast(replacement)
409 }
410}