script/dom/execcommand/commands/
insertparagraph.rs1use html5ever::local_name;
6use js::context::JSContext;
7use script_bindings::codegen::GenericBindings::SelectionBinding::SelectionMethods;
8use script_bindings::inheritance::Castable;
9
10use crate::dom::Node;
11use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
12use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
13use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
14use crate::dom::bindings::codegen::Bindings::TextBinding::TextMethods;
15use crate::dom::bindings::root::DomRoot;
16use crate::dom::comment::Comment;
17use crate::dom::document::Document;
18use crate::dom::element::Element;
19use crate::dom::execcommand::contenteditable::node::{
20 NodeOrString, is_allowed_child, node_matches_local_name, split_the_parent, wrap_node_list,
21};
22use crate::dom::html::htmlbrelement::HTMLBRElement;
23use crate::dom::iterators::ShadowIncluding;
24use crate::dom::selection::Selection;
25use crate::dom::text::Text;
26
27pub(crate) fn execute_insert_paragraph_command(
29 cx: &mut JSContext,
30 document: &Document,
31 selection: &Selection,
32) -> bool {
33 selection.delete_the_selection(
35 cx,
36 document,
37 Default::default(),
38 Default::default(),
39 Default::default(),
40 );
41 let (mut node, mut offset) = selection.start_boundary(cx);
43 if !node.is_editable_or_editing_host() {
46 return true;
47 }
48 if offset != 0 &&
51 offset != node.len() &&
52 let Some(text_node) = node.downcast::<Text>() &&
53 text_node.SplitText(cx, offset).is_err()
54 {
55 unreachable!("Must always be able to split");
56 }
57 if node.is::<Text>() && offset == node.len() {
60 offset = 1 + node.index();
61 node = node.GetParentNode().expect("Must always have a parent");
62 }
63 if node.is::<Text>() || node.is::<Comment>() {
66 offset = node.index();
67 node = node.GetParentNode().expect("Must always have a parent");
68 }
69 let _ = selection.Collapse(cx, Some(&node), offset);
71 let mut container = node.clone();
73 while !container.is_single_line_container() &&
77 let Some(parent) = container.GetParentNode() &&
78 parent.is_editable() &&
79 parent.same_editing_host(&node)
80 {
81 container = parent;
82 }
83 if container.is_editable() &&
86 container.is_single_line_container() &&
87 container.same_editing_host(&node) &&
88 node_matches_local_name!(container, local_name!("p") | local_name!("div"))
89 {
90 let mut outer_container = container.clone();
92 while !node_matches_local_name!(
95 outer_container,
96 local_name!("dd") | local_name!("dt") | local_name!("li")
97 ) && let Some(parent) = outer_container.GetParentNode() &&
98 parent.is_editable()
99 {
100 outer_container = parent;
101 }
102 if node_matches_local_name!(
104 outer_container,
105 local_name!("dd") | local_name!("dt") | local_name!("li")
106 ) {
107 container = outer_container;
108 }
109 }
110 if !container.is_editable() ||
112 !container.same_editing_host(&node) ||
113 !container.is_single_line_container()
114 {
115 let tag = document.default_single_line_container_name();
117 let new_range = selection.expect_active_range(cx).block_extend(cx, document);
119 let mut node_list = if let Some(eligible_node) = new_range
121 .contained_children()
122 .ok()
123 .and_then(|contained_children| {
124 contained_children
125 .contained_children
126 .into_iter()
127 .find(|node| {
128 is_allowed_child(
129 NodeOrString::from_node(node, cx.no_gc()),
130 NodeOrString::String("p".to_owned()),
131 )
132 })
133 }) {
134 vec![eligible_node]
135 } else {
136 if !is_allowed_child(
140 NodeOrString::String(tag.str().to_owned()),
141 NodeOrString::from_node(
142 &selection.expect_active_range(cx).start_container(),
143 cx.no_gc(),
144 ),
145 ) {
146 return true;
147 }
148 let container = document.create_element(cx, tag.str());
150 let container = container.upcast::<Node>();
151 if selection
153 .expect_active_range(cx)
154 .InsertNode(cx, container)
155 .is_err()
156 {
157 unreachable!("Must always be able to insert");
158 }
159 let br = document.create_element(cx, "br");
162 if container.AppendChild(cx, br.upcast()).is_err() {
163 unreachable!("Must always be able to append");
164 }
165 let _ = selection.Collapse(cx, Some(container), 0);
167 return true;
169 };
170 while let Some(next_of_last) = node_list
173 .iter()
174 .last()
175 .and_then(|node| node.GetNextSibling())
176 .filter(|next_of_last| {
177 is_allowed_child(
178 NodeOrString::from_node(next_of_last, cx.no_gc()),
179 NodeOrString::String("p".to_owned()),
180 )
181 })
182 {
183 node_list.push(next_of_last);
184 }
185 container = wrap_node_list(
189 cx,
190 node_list,
191 |_| false,
192 |cx| Some(DomRoot::upcast(document.create_element(cx, tag.str()))),
193 )
194 .expect("Must always be able to wrap");
195 }
196 if node_matches_local_name!(
198 container,
199 local_name!("address") | local_name!("listing") | local_name!("pre")
200 ) {
201 let br = document.create_element(cx, "br");
203 if selection
205 .expect_active_range(cx)
206 .InsertNode(cx, br.upcast())
207 .is_err()
208 {
209 unreachable!("Must always be able to insert");
210 }
211 let _ = selection.Collapse(cx, Some(&node), offset + 1);
213 if container
217 .children()
218 .last()
219 .is_some_and(|child| *child == *br.upcast())
220 {
221 let br = document.create_element(cx, "br");
222 if selection
223 .expect_active_range(cx)
224 .InsertNode(cx, br.upcast())
225 .is_err()
226 {
227 unreachable!("Must always be able to insert");
228 }
229 }
230 return true;
232 }
233 if node_matches_local_name!(
236 container,
237 local_name!("li") | local_name!("dt") | local_name!("dd")
238 ) && (container.children_count() == 0 ||
239 (container.children_count() == 1 &&
240 container
241 .children()
242 .next()
243 .expect("has one child")
244 .is::<HTMLBRElement>()))
245 {
246 split_the_parent(cx, &[&container]);
248 if container.children_count() == 0 {
251 let br = document.create_element(cx, "br");
252 if container.AppendChild(cx, br.upcast()).is_err() {
253 unreachable!("Must always be able to append");
254 }
255 }
256 if node_matches_local_name!(container, local_name!("dd") | local_name!("dt")) &&
260 container.is_no_allowed_child_in_same_editing_host(cx.no_gc())
261 {
262 container = container
263 .downcast::<Element>()
264 .expect("Must always be an element")
265 .set_the_tag_name(cx, document.default_single_line_container_name().str());
266 }
267 container.fix_disallowed_ancestors(cx, document);
269 return true;
271 }
272 let new_line_range = document.CreateRange(cx);
275 let (start_container, start_offset) = selection.start_boundary(cx);
276 let _ = new_line_range.SetStart(&start_container, start_offset);
277 let _ = new_line_range.SetEnd(&container, container.len());
278 while new_line_range.start_offset() == 0 &&
282 !new_line_range
283 .start_container()
284 .is_prohibited_paragraph_child()
285 {
286 let start = new_line_range.start_container();
287 let _ = new_line_range.SetStart(
288 &start.GetParentNode().expect("Must always have a parent"),
289 start.index(),
290 );
291 }
292 while new_line_range.start_offset() == new_line_range.start_container().len() &&
296 !new_line_range
297 .start_container()
298 .is_prohibited_paragraph_child()
299 {
300 let start = new_line_range.start_container();
301 let _ = new_line_range.SetStart(
302 &start.GetParentNode().expect("Must always have a parent"),
303 1 + start.index(),
304 );
305 }
306 let end_of_line = new_line_range
308 .contained_children()
309 .is_ok_and(|contained_children| {
310 let contained_children = contained_children.contained_children;
311 contained_children.is_empty() ||
312 (contained_children.len() == 1 && contained_children[0].is::<HTMLBRElement>())
313 });
314 let container_as_element = container
317 .downcast::<Element>()
318 .expect("Must always be an element");
319 let container_name = container_as_element.local_name();
320 let new_container_name = if end_of_line &&
321 matches!(
322 *container_name,
323 local_name!("h1") |
324 local_name!("h2") |
325 local_name!("h3") |
326 local_name!("h4") |
327 local_name!("h5") |
328 local_name!("h6")
329 ) {
330 document
331 .default_single_line_container_name()
332 .str()
333 .to_owned()
334 } else
335 if end_of_line && container_name == &local_name!("dt") {
337 "dd".to_owned()
338 } else
339 if end_of_line && container_name == &local_name!("dd") {
341 "dt".to_owned()
342 } else {
343 container_name.to_string()
345 };
346 let new_container = document.create_element(cx, &new_container_name);
348 container_as_element.copy_all_attributes_to_other_element(cx, &new_container);
350 new_container.remove_attribute_by_name(cx, &local_name!("id"));
352 let new_container_node = DomRoot::upcast(new_container);
354 if container
355 .GetParentNode()
356 .expect("Must always have a parent")
357 .InsertBefore(
358 cx,
359 &new_container_node,
360 container.GetNextSibling().as_deref(),
361 )
362 .is_err()
363 {
364 unreachable!("Must always be able to insert");
365 }
366 let contained_nodes: Vec<DomRoot<Node>> = new_line_range
368 .contained_nodes(cx.no_gc())
369 .map(|node| node.as_rooted())
370 .collect();
371 let Ok(frag) = new_line_range.ExtractContents(cx) else {
373 unreachable!("Must always be able to extract");
374 };
375 let frag_as_node = frag.upcast::<Node>();
376 for descendant in frag_as_node.traverse_preorder(ShadowIncluding::No) {
379 if !contained_nodes.contains(&descendant) &&
380 let Some(descendant) = descendant.downcast::<Element>()
381 {
382 descendant.remove_attribute_by_name(cx, &local_name!("id"));
383 }
384 }
385 if new_container_node.AppendChild(cx, frag_as_node).is_err() {
387 unreachable!("Must always be able to append");
388 }
389 loop {
392 let Some(last_child) = container.children().last() else {
393 break;
394 };
395 if !last_child.is_prohibited_paragraph_child() {
396 break;
397 }
398 container = last_child;
399 }
400 let mut new_container_node = new_container_node;
403 loop {
404 let Some(last_child) = new_container_node.children().last() else {
405 break;
406 };
407 if !last_child.is_prohibited_paragraph_child() {
408 break;
409 }
410 new_container_node = last_child;
411 }
412 if container
416 .children()
417 .all(|child| child.is_invisible(cx.no_gc()))
418 {
419 let br = document.create_element(cx, "br");
420 if container.AppendChild(cx, br.upcast()).is_err() {
421 unreachable!("Must always be able to append");
422 }
423 }
424 if new_container_node
428 .children()
429 .all(|child| child.is_invisible(cx.no_gc()))
430 {
431 let br = document.create_element(cx, "br");
432 if new_container_node.AppendChild(cx, br.upcast()).is_err() {
433 unreachable!("Must always be able to append");
434 }
435 }
436 let _ = selection.Collapse(cx, Some(&new_container_node), 0);
438 true
440}