script/dom/execcommand/commands/
outdent.rs1use std::collections::HashSet;
6
7use html5ever::local_name;
8use js::context::JSContext;
9use script_bindings::codegen::GenericBindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
10use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
11use script_bindings::inheritance::Castable;
12use script_bindings::str::DOMString;
13
14use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
15use crate::dom::bindings::root::{DomRoot, DomSlice};
16use crate::dom::document::Document;
17use crate::dom::element::Element;
18use crate::dom::execcommand::commands::indent::indent;
19use crate::dom::execcommand::contenteditable::node::{
20 record_the_values, restore_the_values, split_the_parent,
21};
22use crate::dom::html::htmlbrelement::HTMLBRElement;
23use crate::dom::html::htmlelement::HTMLElement;
24use crate::dom::html::htmllielement::HTMLLIElement;
25use crate::dom::html::htmlolistelement::HTMLOListElement;
26use crate::dom::html::htmlulistelement::HTMLUListElement;
27use crate::dom::iterators::ShadowIncluding;
28use crate::dom::node::Node;
29use crate::dom::selection::Selection;
30
31pub(crate) fn outdent(cx: &mut JSContext, context_object: &Document, node: &Node) {
33 if !node.is_editable() {
35 return;
36 }
37
38 if node
41 .downcast::<Element>()
42 .is_some_and(|element| element.is_simple_indentation_element())
43 {
44 node.remove_preserving_its_descendants(cx);
45 return;
46 }
47
48 if let Some(node_as_element) = node.downcast::<Element>() &&
50 node_as_element.is_indentation_element()
51 {
52 node_as_element.remove_attribute_by_name(cx, &local_name!("dir"));
54
55 if let Some(node_as_html_element) = node_as_element.downcast::<HTMLElement>() {
57 let _ = node_as_html_element
58 .Style(cx)
59 .RemoveProperty(cx, DOMString::from_static("margin"));
60 let _ = node_as_html_element
61 .Style(cx)
62 .RemoveProperty(cx, DOMString::from_static("padding"));
63 let _ = node_as_html_element
64 .Style(cx)
65 .RemoveProperty(cx, DOMString::from_static("border"));
66 }
67
68 node_as_element.set_the_tag_name(cx, "div");
70
71 return;
73 }
74
75 let mut current_ancestor = node.GetParentNode();
77
78 let mut ancestor_list: Vec<DomRoot<Node>> = vec![];
80
81 while let Some(ancestor) = current_ancestor.clone() &&
85 ancestor.is_editable() &&
86 ancestor
87 .downcast::<Element>()
88 .is_some_and(|element| !element.is_simple_indentation_element()) &&
89 !ancestor.is::<HTMLOListElement>() &&
90 !ancestor.is::<HTMLUListElement>()
91 {
92 current_ancestor = ancestor.GetParentNode();
93 ancestor_list.push(ancestor);
94 }
95
96 if !current_ancestor.as_ref().is_some_and(|ancestor| {
98 ancestor.is_editable() &&
99 ancestor
100 .downcast::<Element>()
101 .is_some_and(|element| element.is_simple_indentation_element())
102 }) {
103 current_ancestor = node.GetParentNode();
105
106 ancestor_list.clear();
108
109 while let Some(ancestor) = current_ancestor.clone() &&
113 ancestor.is_editable() &&
114 ancestor
115 .downcast::<Element>()
116 .is_some_and(|element| !element.is_indentation_element()) &&
117 !ancestor.is::<HTMLOListElement>() &&
118 !ancestor.is::<HTMLUListElement>()
119 {
120 current_ancestor = ancestor.GetParentNode();
121 ancestor_list.push(ancestor);
122 }
123 }
124
125 if let Some(node_as_element) = node.downcast::<Element>() &&
127 (node.is::<HTMLOListElement>() || node.is::<HTMLUListElement>()) &&
128 !current_ancestor.as_ref().is_some_and(|ancestor| {
129 ancestor.is_editable() &&
130 ancestor
131 .downcast::<Element>()
132 .is_some_and(|element| element.is_indentation_element())
133 })
134 {
135 node_as_element.remove_attribute_by_name(cx, &local_name!("reversed"));
137 node_as_element.remove_attribute_by_name(cx, &local_name!("start"));
138 node_as_element.remove_attribute_by_name(cx, &local_name!("type"));
139
140 let children = node.children();
142
143 if !node_as_element.attrs().borrow().is_empty() &&
145 !node.GetParentElement().is_some_and(|parent| {
146 parent.is::<HTMLOListElement>() || parent.is::<HTMLUListElement>()
147 })
148 {
149 node_as_element.set_the_tag_name(cx, "div");
150 }
151 else {
153 let values = record_the_values(node.children().collect());
155
156 node.remove_preserving_its_descendants(cx);
158
159 restore_the_values(cx, values);
161 }
162
163 for child in children {
165 child.fix_disallowed_ancestors(cx, context_object);
166 }
167
168 return;
170 }
171
172 if !current_ancestor.as_ref().is_some_and(|ancestor| {
174 ancestor.is_editable() &&
175 ancestor
176 .downcast::<Element>()
177 .is_some_and(|element| element.is_indentation_element())
178 }) {
179 return;
180 }
181
182 ancestor_list.push(
184 current_ancestor
185 .clone()
186 .expect("Should have an ancestor here."),
187 );
188
189 let original_ancestor = current_ancestor;
191
192 while !ancestor_list.is_empty() {
194 let current_ancestor_ref = ancestor_list.pop();
197
198 let target = current_ancestor_ref
201 .expect("Must always have an ancestor here.")
202 .children()
203 .find(|child| **child == *node || Some(child) == ancestor_list.last())
204 .expect("Must be able to find a target here.");
205
206 if target.is_inline_node() &&
209 !target.is::<HTMLBRElement>() &&
210 let Some(next_br_sibling) = target.GetNextSibling() &&
211 next_br_sibling.is::<HTMLBRElement>()
212 {
213 next_br_sibling.remove_self(cx);
214 }
215
216 let preceding_siblings = target.preceding_siblings().collect();
219 let following_siblings = target.following_siblings().collect();
220
221 indent(cx, context_object, preceding_siblings);
223
224 indent(cx, context_object, following_siblings);
226 }
227
228 outdent(
230 cx,
231 context_object,
232 &original_ancestor.expect("Should have an ancestor here."),
233 );
234}
235
236pub(crate) fn execute_outdent_command(
238 cx: &mut JSContext,
239 document: &Document,
240 selection: &Selection,
241) -> bool {
242 let mut active_range = selection
243 .active_range(cx)
244 .expect("Must always have an active range.");
245 let items: HashSet<DomRoot<Node>> = active_range
247 .start_container()
248 .ancestors()
249 .chain(active_range.end_container().ancestors())
250 .filter(|ancestor| ancestor.is::<HTMLLIElement>())
251 .collect();
252
253 for item in items {
255 item.normalize_sublists(cx);
256 }
257
258 active_range = selection
260 .active_range(cx)
261 .expect("Must always have an active range.");
262
263 let new_range = active_range.block_extend(cx, document);
265
266 rooted_vec!(let mut node_list);
268
269 for node in new_range.contained_nodes(cx.no_gc()) {
275 type DomRootNode = DomRoot<Node>;
284 if !node_list
285 .last()
286 .is_some_and(|last: &DomRootNode| last.is_ancestor_of(&node)) &&
287 node.is_editable() &&
288 (!node
289 .traverse_preorder(ShadowIncluding::No)
290 .any(|node| node.is_editable()) ||
291 node.is::<HTMLOListElement>() ||
292 node.is::<HTMLUListElement>() ||
293 (node.is::<HTMLLIElement>() &&
294 node.GetParentElement().is_some_and(|parent| {
295 parent.is::<HTMLOListElement>() || parent.is::<HTMLUListElement>()
296 })))
297 {
298 node_list.push(node.as_rooted());
299 }
300 }
301
302 let mut node_list_iter = node_list.iter().peekable();
304 while let Some(first_node) = node_list_iter.next() {
305 if (first_node.is::<HTMLOListElement>() || first_node.is::<HTMLUListElement>()) ||
310 !first_node.GetParentElement().is_some_and(|parent| {
311 parent.is::<HTMLOListElement>() || parent.is::<HTMLUListElement>()
312 })
313 {
314 outdent(cx, document, first_node);
315 continue;
317 }
318
319 rooted_vec!(let mut sublist);
325
326 sublist.push(first_node.as_traced());
328
329 while let Some(node) = node_list_iter.next_if(|node| {
333 sublist
334 .last()
335 .expect("Must always have last element here.")
336 .GetNextSibling()
337 .is_some_and(|next_sibling| next_sibling == **node) &&
338 !node.is::<HTMLOListElement>() &&
339 !node.is::<HTMLUListElement>()
340 }) {
341 sublist.push(node.as_traced());
342 }
343
344 let values = record_the_values(sublist.iter().map(|node| node.as_rooted()).collect());
346
347 split_the_parent(cx, sublist.r());
349
350 for node in sublist.iter() {
352 node.fix_disallowed_ancestors(cx, document);
353 }
354
355 restore_the_values(cx, values);
357 }
358
359 true
361}