Skip to main content

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