script/dom/execcommand/commands/
insertparagraph.rs1use html5ever::local_name;
6use js::context::JSContext;
7use script_bindings::inheritance::Castable;
8
9use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
10use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
11use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
12use crate::dom::bindings::codegen::Bindings::TextBinding::TextMethods;
13use crate::dom::bindings::root::DomRoot;
14use crate::dom::comment::Comment;
15use crate::dom::document::Document;
16use crate::dom::element::Element;
17use crate::dom::execcommand::contenteditable::node::{
18 NodeOrString, is_allowed_child, node_matches_local_name, split_the_parent, wrap_node_list,
19};
20use crate::dom::html::htmlbrelement::HTMLBRElement;
21use crate::dom::selection::Selection;
22use crate::dom::text::Text;
23use crate::dom::{Node, ShadowIncluding};
24
25pub(crate) fn execute_insert_paragraph_command(
27 cx: &mut JSContext,
28 document: &Document,
29 selection: &Selection,
30) -> bool {
31 selection.delete_the_selection(
33 cx,
34 document,
35 Default::default(),
36 Default::default(),
37 Default::default(),
38 );
39 let active_range = selection
41 .active_range()
42 .expect("Must always have an active range");
43 let mut node = active_range.start_container();
44 let mut offset = active_range.start_offset();
45 if !node.is_editable_or_editing_host() {
48 return true;
49 }
50 if offset != 0 &&
53 offset != node.len() &&
54 let Some(text_node) = node.downcast::<Text>() &&
55 text_node.SplitText(cx, offset).is_err()
56 {
57 unreachable!("Must always be able to split");
58 }
59 if node.is::<Text>() && offset == node.len() {
62 offset = 1 + node.index();
63 node = node.GetParentNode().expect("Must always have a parent");
64 }
65 if node.is::<Text>() || node.is::<Comment>() {
68 offset = node.index();
69 node = node.GetParentNode().expect("Must always have a parent");
70 }
71 selection.collapse_current_range(&node, offset);
73 let mut container = node.clone();
75 while !container.is_single_line_container() &&
79 let Some(parent) = container.GetParentNode() &&
80 parent.is_editable() &&
81 parent.same_editing_host(&node)
82 {
83 container = parent;
84 }
85 if container.is_editable() &&
88 container.is_single_line_container() &&
89 container.same_editing_host(&node) &&
90 node_matches_local_name!(container, local_name!("p") | local_name!("div"))
91 {
92 let mut outer_container = container.clone();
94 while !node_matches_local_name!(
97 outer_container,
98 local_name!("dd") | local_name!("dt") | local_name!("li")
99 ) && let Some(parent) = outer_container.GetParentNode() &&
100 parent.is_editable()
101 {
102 outer_container = parent;
103 }
104 if node_matches_local_name!(
106 outer_container,
107 local_name!("dd") | local_name!("dt") | local_name!("li")
108 ) {
109 container = outer_container;
110 }
111 }
112 if !container.is_editable() ||
114 !container.same_editing_host(&node) ||
115 !container.is_single_line_container()
116 {
117 let tag = document.default_single_line_container_name();
119 let new_range = active_range.block_extend(cx, document);
121 let mut node_list = if let Some(eligible_node) = new_range
123 .contained_children()
124 .ok()
125 .and_then(|contained_children| {
126 contained_children
127 .contained_children
128 .into_iter()
129 .find(|node| {
130 is_allowed_child(
131 NodeOrString::Node(node.clone()),
132 NodeOrString::String("p".to_owned()),
133 )
134 })
135 }) {
136 vec![eligible_node]
137 } else {
138 if !is_allowed_child(
142 NodeOrString::String(tag.str().to_owned()),
143 NodeOrString::Node(active_range.start_container()),
144 ) {
145 return true;
146 }
147 let container = document.create_element(cx, tag.str());
149 let container = container.upcast::<Node>();
150 if active_range.InsertNode(cx, container).is_err() {
152 unreachable!("Must always be able to insert");
153 }
154 let br = document.create_element(cx, "br");
157 if container.AppendChild(cx, br.upcast()).is_err() {
158 unreachable!("Must always be able to append");
159 }
160 selection.collapse_current_range(container, 0);
162 return true;
164 };
165 while let Some(next_of_last) = node_list
168 .iter()
169 .last()
170 .and_then(|node| node.GetNextSibling())
171 .filter(|next_of_last| {
172 is_allowed_child(
173 NodeOrString::Node(DomRoot::from_ref(next_of_last)),
174 NodeOrString::String("p".to_owned()),
175 )
176 })
177 {
178 node_list.push(next_of_last);
179 }
180 container = wrap_node_list(
184 cx,
185 node_list,
186 |_| false,
187 |cx| Some(DomRoot::upcast(document.create_element(cx, tag.str()))),
188 )
189 .expect("Must always be able to wrap");
190 }
191 if node_matches_local_name!(
193 container,
194 local_name!("address") | local_name!("listing") | local_name!("pre")
195 ) {
196 let br = document.create_element(cx, "br");
198 if active_range.InsertNode(cx, br.upcast()).is_err() {
200 unreachable!("Must always be able to insert");
201 }
202 selection.collapse_current_range(&node, offset + 1);
204 if container
208 .children()
209 .last()
210 .is_some_and(|child| *child == *br.upcast())
211 {
212 let br = document.create_element(cx, "br");
213 if active_range.InsertNode(cx, br.upcast()).is_err() {
214 unreachable!("Must always be able to insert");
215 }
216 }
217 return true;
219 }
220 if node_matches_local_name!(
223 container,
224 local_name!("li") | local_name!("dt") | local_name!("dd")
225 ) && (container.children_count() == 0 ||
226 (container.children_count() == 1 &&
227 container
228 .children()
229 .next()
230 .expect("has one child")
231 .is::<HTMLBRElement>()))
232 {
233 split_the_parent(cx, &[&container]);
235 if container.children_count() == 0 {
238 let br = document.create_element(cx, "br");
239 if container.AppendChild(cx, br.upcast()).is_err() {
240 unreachable!("Must always be able to append");
241 }
242 }
243 if node_matches_local_name!(container, local_name!("dd") | local_name!("dt")) &&
247 container.is_no_allowed_child_in_same_editing_host()
248 {
249 container = container
250 .downcast::<Element>()
251 .expect("Must always be an element")
252 .set_the_tag_name(cx, document.default_single_line_container_name().str());
253 }
254 container.fix_disallowed_ancestors(cx, document);
256 return true;
258 }
259 let new_line_range = document.CreateRange(cx);
262 new_line_range.set_start(&active_range.start_container(), active_range.start_offset());
263 new_line_range.set_end(&container, container.len());
264 while new_line_range.start_offset() == 0 &&
268 !new_line_range
269 .start_container()
270 .is_prohibited_paragraph_child()
271 {
272 let start = new_line_range.start_container();
273 new_line_range.set_start(
274 &start.GetParentNode().expect("Must always have a parent"),
275 start.index(),
276 );
277 }
278 while new_line_range.start_offset() == new_line_range.start_container().len() &&
282 !new_line_range
283 .start_container()
284 .is_prohibited_paragraph_child()
285 {
286 let start = new_line_range.start_container();
287 new_line_range.set_start(
288 &start.GetParentNode().expect("Must always have a parent"),
289 1 + start.index(),
290 );
291 }
292 let end_of_line = new_line_range
294 .contained_children()
295 .is_ok_and(|contained_children| {
296 let contained_children = contained_children.contained_children;
297 contained_children.is_empty() ||
298 (contained_children.len() == 1 && contained_children[0].is::<HTMLBRElement>())
299 });
300 let container_as_element = container
303 .downcast::<Element>()
304 .expect("Must always be an element");
305 let container_name = container_as_element.local_name();
306 let new_container_name = if end_of_line &&
307 matches!(
308 *container_name,
309 local_name!("h1") |
310 local_name!("h2") |
311 local_name!("h3") |
312 local_name!("h4") |
313 local_name!("h5") |
314 local_name!("h6")
315 ) {
316 document
317 .default_single_line_container_name()
318 .str()
319 .to_owned()
320 } else
321 if end_of_line && container_name == &local_name!("dt") {
323 "dd".to_owned()
324 } else
325 if end_of_line && container_name == &local_name!("dd") {
327 "dt".to_owned()
328 } else {
329 container_name.to_string()
331 };
332 let new_container = document.create_element(cx, &new_container_name);
334 container_as_element.copy_all_attributes_to_other_element(cx, &new_container);
336 new_container.remove_attribute_by_name(cx, &local_name!("id"));
338 let new_container_node = DomRoot::upcast(new_container);
340 if container
341 .GetParentNode()
342 .expect("Must always have a parent")
343 .InsertBefore(
344 cx,
345 &new_container_node,
346 container.GetNextSibling().as_deref(),
347 )
348 .is_err()
349 {
350 unreachable!("Must always be able to insert");
351 }
352 let Ok(contained_nodes) = new_line_range.contained_children() else {
354 unreachable!("Must always have contained children");
355 };
356 let Ok(frag) = new_line_range.ExtractContents(cx) else {
358 unreachable!("Must always be able to extract");
359 };
360 let frag_as_node = frag.upcast::<Node>();
361 for descendant in frag_as_node.traverse_preorder(ShadowIncluding::No) {
364 if !contained_nodes.contained_children.contains(&descendant) &&
365 let Some(descendant) = descendant.downcast::<Element>()
366 {
367 descendant.remove_attribute_by_name(cx, &local_name!("id"));
368 }
369 }
370 if new_container_node.AppendChild(cx, frag_as_node).is_err() {
372 unreachable!("Must always be able to append");
373 }
374 loop {
377 let Some(last_child) = container.children().last() else {
378 break;
379 };
380 if !last_child.is_prohibited_paragraph_child() {
381 break;
382 }
383 container = last_child;
384 }
385 let mut new_container_node = new_container_node;
388 loop {
389 let Some(last_child) = new_container_node.children().last() else {
390 break;
391 };
392 if !last_child.is_prohibited_paragraph_child() {
393 break;
394 }
395 new_container_node = last_child;
396 }
397 if container.children().all(|child| child.is_invisible()) {
401 let br = document.create_element(cx, "br");
402 if container.AppendChild(cx, br.upcast()).is_err() {
403 unreachable!("Must always be able to append");
404 }
405 }
406 if new_container_node
410 .children()
411 .all(|child| child.is_invisible())
412 {
413 let br = document.create_element(cx, "br");
414 if new_container_node.AppendChild(cx, br.upcast()).is_err() {
415 unreachable!("Must always be able to append");
416 }
417 }
418 selection.collapse_current_range(&new_container_node, 0);
420 true
422}