script/dom/execcommand/commands/
indent.rs1use std::collections::HashSet;
6
7use js::context::JSContext;
8use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
9use script_bindings::inheritance::Castable;
10
11use crate::dom::NodeTraits;
12use crate::dom::bindings::root::DomRoot;
13use crate::dom::document::Document;
14use crate::dom::element::{AdjacentPosition, Element};
15use crate::dom::execcommand::contenteditable::node::{
16 NodeOrString, is_allowed_child, move_preserving_ranges, wrap_node_list,
17};
18use crate::dom::html::htmllielement::HTMLLIElement;
19use crate::dom::html::htmlolistelement::HTMLOListElement;
20use crate::dom::html::htmlulistelement::HTMLUListElement;
21use crate::dom::node::Node;
22use crate::dom::selection::Selection;
23use crate::dom::text::Text;
24
25pub(crate) fn indent(cx: &mut JSContext, document: &Document, node_list: Vec<DomRoot<Node>>) {
27 if node_list.is_empty() {
29 return;
30 }
31
32 let first_node = node_list
34 .first()
35 .expect("Must have a first node by now.")
36 .clone();
37
38 if let Some(parent) = first_node.GetParentElement() &&
40 (parent.is::<HTMLOListElement>() || parent.is::<HTMLUListElement>())
41 {
42 let tag = parent.local_name();
44
45 wrap_node_list(
49 cx,
50 node_list,
51 |sibling| {
52 sibling
53 .downcast::<Element>()
54 .is_some_and(|sibling| sibling.local_name() == tag)
55 },
56 |cx| {
57 Some(DomRoot::upcast(
58 first_node.owner_doc().create_element(cx, tag),
59 ))
60 },
61 );
62
63 return;
65 }
66
67 let new_parent = wrap_node_list(
72 cx,
73 node_list,
74 |sibling| {
75 sibling
76 .downcast::<Element>()
77 .is_some_and(|sibling| sibling.is_simple_indentation_element())
78 },
79 |cx| {
80 Some(DomRoot::upcast(
81 first_node.owner_doc().create_element(cx, "blockquote"),
82 ))
83 },
84 );
85
86 if let Some(new_parent) = new_parent {
88 new_parent.fix_disallowed_ancestors(cx, document);
89 }
90}
91
92pub(crate) fn normalize_sublists(cx: &mut JSContext, item: DomRoot<Node>) {
94 let item_element = item
95 .downcast::<Element>()
96 .expect("item should be an element");
97
98 if !item.is::<HTMLLIElement>() ||
100 !item.is_editable() ||
101 !item
102 .GetParentElement()
103 .is_some_and(|parent| parent.upcast::<Node>().is_editable())
104 {
105 return;
106 }
107
108 let mut new_item: Option<DomRoot<Element>> = None;
110
111 while item
113 .child_elements()
114 .any(|child| child.is::<HTMLOListElement>() || child.is::<HTMLUListElement>())
115 {
116 let child = item.GetLastChild().expect("Must have a last child here.");
118
119 if child.is::<HTMLOListElement>() ||
121 child.is::<HTMLUListElement>() ||
122 (new_item.is_none() &&
123 child
124 .downcast::<Text>()
125 .is_some_and(|text| text.data().bytes().all(|byte| byte == b' ')))
126 {
127 new_item = None;
129
130 move_preserving_ranges(cx, &child, |cx| {
132 item_element
133 .insert_adjacent(cx, AdjacentPosition::AfterEnd, &child)
134 .map(|elem| elem.expect("Should have inserted"))
135 });
136
137 continue;
138 }
139 if new_item.is_none() {
144 new_item = Some(item.owner_document().create_element(cx, "li"));
145 item_element
146 .insert_adjacent(cx, AdjacentPosition::AfterEnd, &child)
147 .expect("Insertion should always work here.");
148 }
149
150 move_preserving_ranges(cx, &child, |cx| {
152 new_item
153 .as_ref()
154 .expect("Must have new item here")
155 .downcast::<Element>()
156 .expect("New item must be able to support children")
157 .insert_adjacent(cx, AdjacentPosition::AfterBegin, &child)
158 .map(|elem| elem.expect("Should have inserted"))
159 });
160 }
161}
162
163pub(crate) fn execute_indent_command(
165 cx: &mut JSContext,
166 document: &Document,
167 selection: &Selection,
168) -> bool {
169 let mut active_range = selection
170 .active_range(cx)
171 .expect("Must always have an active range.");
172 let items: HashSet<DomRoot<Node>> = active_range
174 .start_container()
175 .ancestors()
176 .chain(active_range.end_container().ancestors())
177 .filter(|ancestor| ancestor.is::<HTMLLIElement>())
178 .collect();
179
180 for item in items {
182 normalize_sublists(cx, item);
183 }
184
185 active_range = selection
187 .active_range(cx)
188 .expect("Must always have an active range.");
189
190 let new_range = active_range.block_extend(cx, document);
192
193 let mut node_list: Vec<DomRoot<Node>> = vec![];
195
196 for node in new_range.contained_nodes(cx.no_gc()) {
201 if node.is_editable() &&
202 (is_allowed_child(
203 NodeOrString::Node(node.clone()),
204 NodeOrString::String("div".to_owned()),
205 ) || is_allowed_child(
206 NodeOrString::Node(node.clone()),
207 NodeOrString::String("ol".to_owned()),
208 )) &&
209 node_list
210 .last()
211 .is_none_or(|last| !last.is_ancestor_of(&node))
212 {
213 node_list.push(node.as_rooted());
214 }
215 }
216
217 if let Some(first_visible_member) = node_list.iter().find(|node| node.is_visible(cx.no_gc())) &&
219 first_visible_member.is::<HTMLLIElement>() &&
220 let Some(parent) = first_visible_member.GetParentNode() &&
221 (parent.is::<HTMLOListElement>() || parent.is::<HTMLUListElement>())
222 {
223 let mut sibling = first_visible_member.GetPreviousSibling();
225
226 while let Some(ref some_sibling) = sibling &&
228 some_sibling.is_invisible(cx.no_gc())
229 {
230 sibling = some_sibling.GetPreviousSibling();
231 }
232
233 if let Some(sibling) = sibling &&
235 sibling.is::<HTMLLIElement>()
236 {
237 normalize_sublists(cx, sibling);
238 }
239 }
240
241 let mut node_list_iter = node_list.iter().peekable();
243 while node_list_iter.peek().is_some() {
244 let mut sublist: Vec<DomRoot<Node>> = vec![];
246
247 sublist.push(
249 node_list_iter
250 .next()
251 .expect("Must always have a next item")
252 .clone(),
253 );
254
255 while node_list_iter.peek().is_some_and(|node| {
258 sublist
259 .last()
260 .expect("Must always have last element here.")
261 .GetNextSibling()
262 .is_some_and(|next_sibling| &&next_sibling == node)
263 }) {
264 sublist.push(
265 node_list_iter
266 .next()
267 .expect("Must always have a next item")
268 .clone(),
269 );
270 }
271
272 indent(cx, document, sublist);
274 }
275
276 true
279}