Skip to main content

script/dom/execcommand/commands/
inserthorizontalrule.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use js::context::JSContext;
6use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
7use script_bindings::codegen::GenericBindings::RangeBinding::RangeMethods;
8use script_bindings::codegen::GenericBindings::SelectionBinding::SelectionMethods;
9use script_bindings::inheritance::Castable;
10
11use crate::dom::bindings::root::DomRoot;
12use crate::dom::document::Document;
13use crate::dom::execcommand::contenteditable::selection::SelectionDeletionBlockMerging;
14use crate::dom::selection::Selection;
15use crate::dom::text::Text;
16
17/// <https://w3c.github.io/editing/docs/execCommand/#the-inserthorizontalrule-command>
18pub(crate) fn execute_insert_horizontal_rule_command(
19    cx: &mut JSContext,
20    document: &Document,
21    selection: &Selection,
22) -> bool {
23    // Step 1. Let start node, start offset, end node, and end offset be the active range's start and end nodes and offsets.
24    let mut active_range = selection
25        .active_range()
26        .expect("Must always have an active range");
27    let mut start_node = active_range.start_container();
28    let mut start_offset = active_range.start_offset();
29    let mut end_node = active_range.end_container();
30    let mut end_offset = active_range.end_offset();
31
32    // Step 2. While start offset is 0 and start node's parent is not null, set start offset to start node's index,
33    //         then set start node to its parent.
34    while let Some(parent) = start_node.GetParentNode() &&
35        start_offset == 0
36    {
37        start_offset = start_node.index();
38        start_node = parent;
39    }
40
41    // Step 3. While end offset is end node's length, and end node's parent is not null, set end offset to one plus end
42    //         node's index, then set end node to its parent.
43    while let Some(parent) = end_node.GetParentNode() &&
44        end_offset == end_node.len()
45    {
46        end_offset = 1 + end_node.index();
47        end_node = parent;
48    }
49
50    // Step 4. Call collapse(start node, start offset) on the context object's selection.
51    if selection
52        .Collapse(cx, Some(&start_node), start_offset)
53        .is_err()
54    {
55        unreachable!("Should always be able to collapse the selection.");
56    }
57
58    // Step 5. Call extend(end node, end offset) on the context object's selection.
59    if selection.Extend(cx, &end_node, end_offset).is_err() {
60        unreachable!("Should always be able to extend the selection.");
61    }
62
63    // Step 6. Delete the selection, with block merging false.
64    selection.delete_the_selection(
65        cx,
66        document,
67        SelectionDeletionBlockMerging::Skip,
68        Default::default(),
69        Default::default(),
70    );
71
72    active_range = selection
73        .active_range()
74        .expect("Must always have an active range");
75
76    // Step 7. If the active range's start node is neither editable nor an editing host, return true.
77    if !active_range.start_container().is_editable_or_editing_host() {
78        return true;
79    }
80
81    // Step 8. If the active range's start node is a Text node and its start offset is zero, call collapse() on the
82    //         context object's selection, with first argument the active range's start node's parent and second
83    //         argument the active range's start node's index.
84    if active_range.start_container().is::<Text>() && active_range.start_offset() == 0 {
85        if selection
86            .Collapse(
87                cx,
88                active_range.start_container().GetParentNode().as_deref(),
89                active_range.start_container().index(),
90            )
91            .is_err()
92        {
93            unreachable!("Should always be able to collapse the selection.");
94        }
95        active_range = selection
96            .active_range()
97            .expect("Must always have an active range");
98    }
99
100    // Step 9. If the active range's start node is a Text node and its start offset is the length of its start node,
101    //         call collapse() on the context object's selection, with first argument the active range's start node's
102    //         parent, and the second argument one plus the active range's start node's index.
103    if active_range.start_container().is::<Text>() &&
104        active_range.start_offset() == active_range.start_container().len()
105    {
106        if selection
107            .Collapse(
108                cx,
109                active_range.start_container().GetParentNode().as_deref(),
110                1 + active_range.start_container().index(),
111            )
112            .is_err()
113        {
114            unreachable!("Should always be able to collapse the selection.");
115        }
116        active_range = selection
117            .active_range()
118            .expect("Must always have an active range");
119    }
120
121    // Step 10. Let hr be the result of calling createElement("hr") on the context object.
122    let hr = document.create_element(cx, "hr");
123
124    // Step 11. Run insertNode(hr) on the active range.
125    let hr_node = DomRoot::upcast(hr);
126    if active_range.InsertNode(cx, &hr_node).is_err() {
127        unreachable!("The image should always be insertable.");
128    }
129
130    // Step 12. Fix disallowed ancestors of hr.
131    hr_node.fix_disallowed_ancestors(cx, document);
132
133    // Step 13. Run collapse() on the context object's selection, with first argument hr's parent and the second argument equal to one plus hr's index.
134    if selection
135        .Collapse(cx, hr_node.GetParentNode().as_deref(), 1 + hr_node.index())
136        .is_err()
137    {
138        unreachable!("Should always be able to collapse the selection.");
139    }
140
141    // Step 14.
142    true
143}