Skip to main content

script/dom/execcommand/commands/
insertimage.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::SelectionBinding::SelectionMethods;
8use script_bindings::inheritance::Castable;
9use style::attr::AttrValue;
10
11use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
12use crate::dom::bindings::root::DomRoot;
13use crate::dom::bindings::str::DOMString;
14use crate::dom::document::Document;
15use crate::dom::execcommand::contenteditable::selection::SelectionDeletionStripWrappers;
16use crate::dom::html::htmlbrelement::HTMLBRElement;
17use crate::dom::selection::Selection;
18
19/// <https://w3c.github.io/editing/docs/execCommand/#the-insertimage-command>
20pub(crate) fn execute_insert_image_command(
21    cx: &mut JSContext,
22    document: &Document,
23    selection: &Selection,
24    value: DOMString,
25) -> bool {
26    // Step 1. If value is the empty string, return false.
27    if value.is_empty() {
28        return false;
29    }
30
31    // Step 2. Delete the selection, with strip wrappers false.
32    selection.delete_the_selection(
33        cx,
34        document,
35        Default::default(),
36        SelectionDeletionStripWrappers::NoStrip,
37        Default::default(),
38    );
39
40    // Step 3. Let range be the active range.
41    let range = selection
42        .active_range()
43        .expect("Must always have an active range");
44
45    // Step 4. If the active range's start node is neither editable nor an editing host, return true.
46    let start_node = range.start_container();
47    if !start_node.is_editable_or_editing_host() {
48        return true;
49    }
50
51    // Step 5. If range's start node is a block node whose sole child is a br, and its start offset is 0, remove its start node's child from it.
52    if start_node.is_block_node() && start_node.children_count() == 1 {
53        let sole_child = start_node
54            .children()
55            .next()
56            .expect("range's start node has one child.");
57        if sole_child.is::<HTMLBRElement>() {
58            sole_child.remove_self(cx);
59        }
60    }
61
62    // Step 6. Let img be the result of calling createElement("img") on the context object.
63    let img = document.create_element(cx, "img");
64
65    // Step 7. Run setAttribute("src", value) on img.
66    img.set_attribute(
67        cx,
68        &html5ever::local_name!("src"),
69        AttrValue::String(value.str().to_owned()),
70    );
71
72    // Step 8. Run insertNode(img) on range.
73    let img_node = DomRoot::upcast(img);
74    if range.InsertNode(cx, &img_node).is_err() {
75        unreachable!("The image should always be insertable.");
76    }
77
78    // Step 9. Let selection be the result of calling getSelection() on the context object.
79    // Step 10. Run collapse() on selection, with first argument equal to the parent of img and the second argument equal to one plus the index of img.
80    if selection
81        .Collapse(
82            cx,
83            img_node.GetParentNode().as_deref(),
84            1 + img_node.index(),
85        )
86        .is_err()
87    {
88        unreachable!("The selection should always be collapsible.");
89    }
90
91    // Step 11. Return true.
92    true
93}