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::htmlfontelement::HTMLFontElement;
22use crate::dom::node::node::{Node, NodeTraits};
23
24impl Element {
25 pub(crate) fn resolved_display_value(&self) -> Option<DisplayOutside> {
26 self.style().map(|style| style.get_box().display.outside())
27 }
28
29 pub(crate) fn specified_command_value(&self, command: &CommandName) -> Option<DOMString> {
31 match command {
32 CommandName::BackColor | CommandName::HiliteColor => {
34 },
36 CommandName::CreateLink | CommandName::Unlink => {
38 },
40 CommandName::Subscript | CommandName::Superscript => {
42 },
44 CommandName::Strikethrough => {
45 if let Some(value) = CssPropertyName::TextDecorationLine.value_set_for_style(self) {
47 return Some("line-through".into()).filter(|_| value.contains("line-through"));
50 }
51 if matches!(*self.local_name(), local_name!("s") | local_name!("strike")) {
53 return Some("line-through".into());
54 }
55 },
56 CommandName::Underline => {
57 if let Some(value) = CssPropertyName::TextDecorationLine.value_set_for_style(self) {
59 return Some("underline".into()).filter(|_| value.contains("underline"));
62 }
63 if *self.local_name() == local_name!("u") {
65 return Some("underline".into());
66 }
67 },
68 _ => {},
69 };
70 let property = command.relevant_css_property()?;
73 if let Some(value) = property.value_set_for_style(self) {
76 return Some(value);
77 }
78 if self.is::<HTMLFontElement>() {
81 if let Some(font_size) = self.get_attribute(&local_name!("size")) {
82 if let AttrValue::UInt(_, value) = *font_size.value() {
83 return Some(font_size_to_css_font(&value).into());
84 }
85 }
86 }
87
88 let element_name = self.local_name();
91 match property {
92 CssPropertyName::FontWeight
93 if element_name == &local_name!("b") || element_name == &local_name!("strong") =>
94 {
95 Some("bold".into())
96 },
97 CssPropertyName::FontStyle
98 if element_name == &local_name!("i") || element_name == &local_name!("em") =>
99 {
100 Some("italic".into())
101 },
102 _ => None,
104 }
105 }
106
107 pub(crate) fn is_modifiable_element(&self) -> bool {
109 let attrs = self.attrs();
110 let mut attrs = attrs.iter();
111 let type_id = self.upcast::<Node>().type_id();
112
113 if matches!(
116 type_id,
117 NodeTypeId::Element(ElementTypeId::HTMLElement(
118 HTMLElementTypeId::HTMLSpanElement,
119 ))
120 ) || matches!(
121 *self.local_name(),
122 local_name!("b") |
123 local_name!("em") |
124 local_name!("i") |
125 local_name!("s") |
126 local_name!("strike") |
127 local_name!("strong") |
128 local_name!("sub") |
129 local_name!("sup") |
130 local_name!("u")
131 ) {
132 return attrs.all(|attr| attr.local_name() == &local_name!("style"));
133 }
134
135 if matches!(
137 type_id,
138 NodeTypeId::Element(ElementTypeId::HTMLElement(
139 HTMLElementTypeId::HTMLFontElement,
140 ))
141 ) {
142 return attrs.all(|attr| {
143 matches!(
144 *attr.local_name(),
145 local_name!("style") |
146 local_name!("color") |
147 local_name!("face") |
148 local_name!("size")
149 )
150 });
151 }
152
153 if matches!(
155 type_id,
156 NodeTypeId::Element(ElementTypeId::HTMLElement(
157 HTMLElementTypeId::HTMLAnchorElement,
158 ))
159 ) {
160 return attrs.all(|attr| {
161 matches!(
162 *attr.local_name(),
163 local_name!("style") | local_name!("href")
164 )
165 });
166 }
167
168 false
169 }
170
171 pub(crate) fn has_empty_style_attribute(&self) -> bool {
172 let style_attribute = self.style_attribute().borrow();
173 style_attribute.as_ref().is_some_and(|declarations| {
174 let document = self.owner_document();
175 let shared_lock = document.style_shared_lock();
176 let read_lock = shared_lock.read();
177 let style = declarations.read_with(&read_lock);
178
179 style.is_empty()
180 })
181 }
182
183 pub(crate) fn is_simple_modifiable_element(&self) -> bool {
185 let attrs = self.attrs();
186 let attr_count = attrs.len();
187 let type_id = self.upcast::<Node>().type_id();
188
189 if matches!(
190 type_id,
191 NodeTypeId::Element(ElementTypeId::HTMLElement(
192 HTMLElementTypeId::HTMLAnchorElement,
193 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
194 HTMLElementTypeId::HTMLFontElement,
195 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
196 HTMLElementTypeId::HTMLSpanElement,
197 ))
198 ) || matches!(
199 *self.local_name(),
200 local_name!("b") |
201 local_name!("em") |
202 local_name!("i") |
203 local_name!("s") |
204 local_name!("strike") |
205 local_name!("strong") |
206 local_name!("sub") |
207 local_name!("sup") |
208 local_name!("u")
209 ) {
210 if attr_count == 0 {
212 return true;
213 }
214
215 if attr_count == 1 &&
219 attrs.first().expect("Size is 1").local_name() == &local_name!("style") &&
220 self.has_empty_style_attribute()
221 {
222 return true;
223 }
224 }
225
226 if attr_count != 1 {
227 return false;
228 }
229
230 let only_attribute = attrs.first().expect("Size is 1").local_name();
231
232 if matches!(
234 type_id,
235 NodeTypeId::Element(ElementTypeId::HTMLElement(
236 HTMLElementTypeId::HTMLAnchorElement,
237 ))
238 ) {
239 return only_attribute == &local_name!("href");
240 }
241
242 if matches!(
244 type_id,
245 NodeTypeId::Element(ElementTypeId::HTMLElement(
246 HTMLElementTypeId::HTMLFontElement,
247 ))
248 ) {
249 return only_attribute == &local_name!("color") ||
250 only_attribute == &local_name!("face") ||
251 only_attribute == &local_name!("size");
252 }
253
254 if only_attribute != &local_name!("style") {
255 return false;
256 }
257 let style_attribute = self.style_attribute().borrow();
258 let Some(declarations) = style_attribute.as_ref() else {
259 return false;
260 };
261 let document = self.owner_document();
262 let shared_lock = document.style_shared_lock();
263 let read_lock = shared_lock.read();
264 let style = declarations.read_with(&read_lock);
265
266 if matches!(*self.local_name(), local_name!("b") | local_name!("strong")) {
270 return style.len() == 1 &&
271 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontWeight));
272 }
273
274 if matches!(*self.local_name(), local_name!("i") | local_name!("em")) {
278 return style.len() == 1 &&
279 style.contains(PropertyDeclarationId::Longhand(LonghandId::FontStyle));
280 }
281
282 let a_font_or_span = matches!(
283 type_id,
284 NodeTypeId::Element(ElementTypeId::HTMLElement(
285 HTMLElementTypeId::HTMLAnchorElement,
286 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
287 HTMLElementTypeId::HTMLFontElement,
288 )) | NodeTypeId::Element(ElementTypeId::HTMLElement(
289 HTMLElementTypeId::HTMLSpanElement,
290 ))
291 );
292 let s_strike_or_u = matches!(
293 *self.local_name(),
294 local_name!("s") | local_name!("strike") | local_name!("u")
295 );
296 if a_font_or_span || s_strike_or_u {
297 if style.len() == 3 &&
300 style
301 .shorthand_to_css(ShorthandId::TextDecoration, &mut String::new())
302 .is_ok()
303 {
304 if let Some((text_decoration, _)) = style.get(PropertyDeclarationId::Longhand(
305 LonghandId::TextDecorationLine,
306 )) {
307 return matches!(
312 text_decoration,
313 PropertyDeclaration::TextDecorationLine(
314 TextDecorationLine::LINE_THROUGH |
315 TextDecorationLine::UNDERLINE |
316 TextDecorationLine::OVERLINE |
317 TextDecorationLine::NONE
318 )
319 );
320 }
321 } else if a_font_or_span {
322 return style.len() == 1;
326 }
327 }
328
329 false
330 }
331
332 pub(crate) fn set_the_tag_name(&self, cx: &mut JSContext, new_name: &str) -> DomRoot<Element> {
334 if self.local_name() == &LocalName::from(new_name) {
336 return DomRoot::from_ref(self);
337 }
338 let node = self.upcast::<Node>();
340 let Some(parent) = node.GetParentNode() else {
341 return DomRoot::from_ref(self);
342 };
343 let document = node.owner_document();
345 let replacement = document.create_element(cx, new_name);
346 let replacement_node = replacement.upcast::<Node>();
347 if parent
349 .InsertBefore(cx, replacement_node, Some(node))
350 .is_err()
351 {
352 unreachable!("Must always be able to insert");
353 }
354 self.copy_all_attributes_to_other_element(cx, &replacement);
356 for child in node.children() {
358 move_preserving_ranges(cx, &child, |cx| replacement_node.AppendChild(cx, &child));
359 }
360 node.remove_self(cx);
362 replacement
364 }
365}