Skip to main content

script/dom/
characterdata.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
5//! DOM bindings for `CharacterData`.
6use std::cell::LazyCell;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use script_bindings::cell::{DomRefCell, Ref};
11use script_bindings::codegen::InheritTypes::{CharacterDataTypeId, NodeTypeId, TextTypeId};
12
13use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
14use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
15use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
16use crate::dom::bindings::codegen::UnionTypes::NodeOrString;
17use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::root::{DomRoot, LayoutDom};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::cdatasection::CDATASection;
22use crate::dom::comment::Comment;
23use crate::dom::document::Document;
24use crate::dom::element::Element;
25use crate::dom::mutationobserver::{Mutation, MutationObserver};
26use crate::dom::node::{ChildrenMutation, Node, NodeDamage};
27use crate::dom::processinginstruction::ProcessingInstruction;
28use crate::dom::text::Text;
29use crate::dom::virtualmethods::vtable_for;
30
31// https://dom.spec.whatwg.org/#characterdata
32#[dom_struct]
33pub(crate) struct CharacterData {
34    node: Node,
35    data: DomRefCell<String>,
36}
37
38impl CharacterData {
39    pub(crate) fn new_inherited(data: DOMString, document: &Document) -> CharacterData {
40        CharacterData {
41            node: Node::new_inherited(document),
42            data: DomRefCell::new(String::from(data.str())),
43        }
44    }
45
46    pub(crate) fn clone_with_data(
47        &self,
48        cx: &mut js::context::JSContext,
49        data: DOMString,
50        document: &Document,
51    ) -> DomRoot<Node> {
52        match self.upcast::<Node>().type_id() {
53            NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
54                DomRoot::upcast(Comment::new(cx, data, document, None))
55            },
56            NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
57                let pi = self.downcast::<ProcessingInstruction>().unwrap();
58                DomRoot::upcast(ProcessingInstruction::new(cx, pi.Target(), data, document))
59            },
60            NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::CDATASection)) => {
61                DomRoot::upcast(CDATASection::new(cx, data, document))
62            },
63            NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => {
64                DomRoot::upcast(Text::new(cx, data, document))
65            },
66            _ => unreachable!(),
67        }
68    }
69
70    #[inline]
71    pub(crate) fn data(&self) -> Ref<'_, String> {
72        self.data.borrow()
73    }
74
75    #[inline]
76    pub(crate) fn append_data(&self, cx: &mut JSContext, data: &str) {
77        self.queue_mutation_record();
78        self.data.borrow_mut().push_str(data);
79        self.content_changed(cx);
80    }
81
82    fn content_changed(&self, cx: &mut JSContext) {
83        let node = self.upcast::<Node>();
84        node.dirty(NodeDamage::Other);
85
86        // If this is a Text node, we might need to re-parse (say, if our parent
87        // is a <style> element.) We don't need to if this is a Comment or
88        // ProcessingInstruction.
89        if self.is::<Text>() &&
90            let Some(parent_node) = node.GetParentNode()
91        {
92            let mutation = ChildrenMutation::ChangeText;
93            vtable_for(&parent_node).children_changed(cx, &mutation);
94        }
95    }
96
97    // Queue a MutationObserver record before changing the content.
98    fn queue_mutation_record(&self) {
99        let mutation = LazyCell::new(|| Mutation::CharacterData {
100            old_value: self.data.borrow().clone(),
101        });
102        MutationObserver::queue_a_mutation_record(self.upcast::<Node>(), mutation);
103    }
104}
105
106impl CharacterDataMethods<crate::DomTypeHolder> for CharacterData {
107    /// <https://dom.spec.whatwg.org/#dom-characterdata-data>
108    fn Data(&self) -> DOMString {
109        DOMString::from(self.data.borrow().clone())
110    }
111
112    /// <https://dom.spec.whatwg.org/#dom-characterdata-data>
113    fn SetData(&self, cx: &mut JSContext, data: DOMString) {
114        self.queue_mutation_record();
115        let old_length = self.Length();
116        let new_length = data.str().encode_utf16().count() as u32;
117        *self.data.borrow_mut() = String::from(data.str());
118        self.content_changed(cx);
119        let node = self.upcast::<Node>();
120        node.ranges()
121            .replace_code_units(node, 0, old_length, new_length);
122    }
123
124    /// <https://dom.spec.whatwg.org/#dom-characterdata-length>
125    fn Length(&self) -> u32 {
126        self.data.borrow().encode_utf16().count() as u32
127    }
128
129    /// <https://dom.spec.whatwg.org/#dom-characterdata-substringdata>
130    fn SubstringData(&self, offset: u32, count: u32) -> Fallible<DOMString> {
131        let data = self.data.borrow();
132        // Step 1.
133        let mut substring = String::new();
134        let remaining = match split_at_utf16_code_unit_offset(&data, offset) {
135            Ok((_, astral, s)) => {
136                // As if we had split the UTF-16 surrogate pair in half
137                // and then transcoded that to UTF-8 lossily,
138                // since our DOMString is currently strict UTF-8.
139                if astral.is_some() {
140                    substring += "\u{FFFD}";
141                }
142                s
143            },
144            // Step 2.
145            Err(()) => return Err(Error::IndexSize(None)),
146        };
147        match split_at_utf16_code_unit_offset(remaining, count) {
148            // Steps 3.
149            Err(()) => substring += remaining,
150            // Steps 4.
151            Ok((s, astral, _)) => {
152                substring += s;
153                // As if we had split the UTF-16 surrogate pair in half
154                // and then transcoded that to UTF-8 lossily,
155                // since our DOMString is currently strict UTF-8.
156                if astral.is_some() {
157                    substring += "\u{FFFD}";
158                }
159            },
160        };
161        Ok(DOMString::from(substring))
162    }
163
164    /// <https://dom.spec.whatwg.org/#dom-characterdata-appenddata>
165    fn AppendData(&self, cx: &mut JSContext, data: DOMString) {
166        // > The appendData(data) method steps are to replace data of this with this’s length, 0, and data.
167        //
168        // FIXME(ajeffrey): Efficient append on DOMStrings?
169        self.append_data(cx, &data.str());
170    }
171
172    /// <https://dom.spec.whatwg.org/#dom-characterdata-insertdata>
173    fn InsertData(&self, cx: &mut JSContext, offset: u32, arg: DOMString) -> ErrorResult {
174        // > The insertData(offset, data) method steps are to replace data of this with offset, 0, and data.
175        self.ReplaceData(cx, offset, 0, arg)
176    }
177
178    /// <https://dom.spec.whatwg.org/#dom-characterdata-deletedata>
179    fn DeleteData(&self, cx: &mut JSContext, offset: u32, count: u32) -> ErrorResult {
180        // > The deleteData(offset, count) method steps are to replace data of this with offset, count, and the empty string.
181        self.ReplaceData(cx, offset, count, DOMString::new())
182    }
183
184    /// <https://dom.spec.whatwg.org/#dom-characterdata-replacedata>
185    fn ReplaceData(
186        &self,
187        cx: &mut JSContext,
188        offset: u32,
189        count: u32,
190        arg: DOMString,
191    ) -> ErrorResult {
192        let mut new_data;
193        {
194            let data = self.data.borrow();
195            let prefix;
196            let replacement_before;
197            let remaining;
198            match split_at_utf16_code_unit_offset(&data, offset) {
199                Ok((p, astral, r)) => {
200                    prefix = p;
201                    // As if we had split the UTF-16 surrogate pair in half
202                    // and then transcoded that to UTF-8 lossily,
203                    // since our DOMString is currently strict UTF-8.
204                    replacement_before = if astral.is_some() { "\u{FFFD}" } else { "" };
205                    remaining = r;
206                },
207                // Step 2.
208                Err(()) => return Err(Error::IndexSize(None)),
209            };
210            let replacement_after;
211            let suffix;
212            match split_at_utf16_code_unit_offset(remaining, count) {
213                // Steps 3.
214                Err(()) => {
215                    replacement_after = "";
216                    suffix = "";
217                },
218                Ok((_, astral, s)) => {
219                    // As if we had split the UTF-16 surrogate pair in half
220                    // and then transcoded that to UTF-8 lossily,
221                    // since our DOMString is currently strict UTF-8.
222                    replacement_after = if astral.is_some() { "\u{FFFD}" } else { "" };
223                    suffix = s;
224                },
225            };
226            // Step 4: Mutation observers.
227            self.queue_mutation_record();
228
229            // Step 5 to 7.
230            new_data = String::with_capacity(
231                prefix.len() +
232                    replacement_before.len() +
233                    arg.len() +
234                    replacement_after.len() +
235                    suffix.len(),
236            );
237            new_data.push_str(prefix);
238            new_data.push_str(replacement_before);
239            new_data.push_str(&arg.str());
240            new_data.push_str(replacement_after);
241            new_data.push_str(suffix);
242        }
243        *self.data.borrow_mut() = new_data;
244        self.content_changed(cx);
245        // Steps 8-11.
246        let node = self.upcast::<Node>();
247        node.ranges().replace_code_units(
248            node,
249            offset,
250            count,
251            arg.str().encode_utf16().count() as u32,
252        );
253        Ok(())
254    }
255
256    /// <https://dom.spec.whatwg.org/#dom-childnode-before>
257    fn Before(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
258        self.upcast::<Node>().before(cx, nodes)
259    }
260
261    /// <https://dom.spec.whatwg.org/#dom-childnode-after>
262    fn After(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
263        self.upcast::<Node>().after(cx, nodes)
264    }
265
266    /// <https://dom.spec.whatwg.org/#dom-childnode-replacewith>
267    fn ReplaceWith(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
268        self.upcast::<Node>().replace_with(cx, nodes)
269    }
270
271    /// <https://dom.spec.whatwg.org/#dom-childnode-remove>
272    fn Remove(&self, cx: &mut JSContext) {
273        self.upcast::<Node>().remove_self(cx);
274    }
275
276    /// <https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling>
277    fn GetPreviousElementSibling(&self) -> Option<DomRoot<Element>> {
278        self.upcast::<Node>()
279            .preceding_siblings()
280            .find_map(DomRoot::downcast)
281    }
282
283    /// <https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling>
284    fn GetNextElementSibling(&self) -> Option<DomRoot<Element>> {
285        self.upcast::<Node>()
286            .following_siblings()
287            .find_map(DomRoot::downcast)
288    }
289}
290
291impl<'dom> LayoutDom<'dom, CharacterData> {
292    #[expect(unsafe_code)]
293    #[inline]
294    pub(crate) fn data_for_layout(self) -> &'dom str {
295        unsafe { self.unsafe_get().data.borrow_for_layout() }
296    }
297}
298
299/// Split the given string at the given position measured in UTF-16 code units from the start.
300///
301/// * `Err(())` indicates that `offset` if after the end of the string
302/// * `Ok((before, None, after))` indicates that `offset` is between Unicode code points.
303///   The two string slices are such that:
304///   `before == s.to_utf16()[..offset].to_utf8()` and
305///   `after == s.to_utf16()[offset..].to_utf8()`
306/// * `Ok((before, Some(ch), after))` indicates that `offset` is "in the middle"
307///   of a single Unicode code point that would be represented in UTF-16 by a surrogate pair
308///   of two 16-bit code units.
309///   `ch` is that code point.
310///   The two string slices are such that:
311///   `before == s.to_utf16()[..offset - 1].to_utf8()` and
312///   `after == s.to_utf16()[offset + 1..].to_utf8()`
313fn split_at_utf16_code_unit_offset(s: &str, offset: u32) -> Result<(&str, Option<char>, &str), ()> {
314    let mut code_units = 0;
315    for (i, c) in s.char_indices() {
316        if code_units == offset {
317            let (a, b) = s.split_at(i);
318            return Ok((a, None, b));
319        }
320        code_units += 1;
321        if c > '\u{FFFF}' {
322            if code_units == offset {
323                debug_assert_eq!(c.len_utf8(), 4);
324                warn!("Splitting a surrogate pair in CharacterData API.");
325                return Ok((&s[..i], Some(c), &s[i + c.len_utf8()..]));
326            }
327            code_units += 1;
328        }
329    }
330    if code_units == offset {
331        Ok((s, None, ""))
332    } else {
333        Err(())
334    }
335}