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()) = String::from(data.str());
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
147                .get_or_insert_with(|| Utf16CodeUnits::length_of(AssumeUnder4GB, &data.str()).0)
148        };
149
150        let node: &Node = self.upcast();
151        let document = node.owner_doc_unrooted(cx.no_gc());
152        if let Some(selection) = document.selection() {
153            selection.replace_data_steps(node, 0, old_length, &mut lazy_length);
154        }
155        document.live_range_replace_data_steps(cx.no_gc(), node, 0, old_length, &mut lazy_length);
156    }
157
158    /// <https://dom.spec.whatwg.org/#dom-characterdata-length>
159    fn Length(&self) -> u32 {
160        // TODO: ensure that DOMString’s are under 4 GiB?
161        Utf16CodeUnits::length_of(AssumeUnder4GB, &self.data.borrow()).0
162    }
163
164    /// <https://dom.spec.whatwg.org/#dom-characterdata-substringdata>
165    fn SubstringData(&self, offset: u32, count: u32) -> Fallible<DOMString> {
166        let data = self.data.borrow();
167        // Step 1.
168        let mut substring = String::new();
169        let remaining = match split_at_utf16_code_unit_offset(&data, offset) {
170            Ok((_, astral, s)) => {
171                // As if we had split the UTF-16 surrogate pair in half
172                // and then transcoded that to UTF-8 lossily,
173                // since our DOMString is currently strict UTF-8.
174                if astral.is_some() {
175                    substring += "\u{FFFD}";
176                }
177                s
178            },
179            // Step 2.
180            Err(()) => return Err(Error::IndexSize(None)),
181        };
182        match split_at_utf16_code_unit_offset(remaining, count) {
183            // Steps 3.
184            Err(()) => substring += remaining,
185            // Steps 4.
186            Ok((s, astral, _)) => {
187                substring += s;
188                // As if we had split the UTF-16 surrogate pair in half
189                // and then transcoded that to UTF-8 lossily,
190                // since our DOMString is currently strict UTF-8.
191                if astral.is_some() {
192                    substring += "\u{FFFD}";
193                }
194            },
195        };
196        Ok(DOMString::from(substring))
197    }
198
199    /// <https://dom.spec.whatwg.org/#dom-characterdata-appenddata>
200    fn AppendData(&self, cx: &mut JSContext, data: DOMString) {
201        // > The appendData(data) method steps are to replace data of this with this’s length, 0, and data.
202        //
203        // FIXME(ajeffrey): Efficient append on DOMStrings?
204        self.append_data(cx, &data.str());
205    }
206
207    /// <https://dom.spec.whatwg.org/#dom-characterdata-insertdata>
208    fn InsertData(&self, cx: &mut JSContext, offset: u32, arg: DOMString) -> ErrorResult {
209        // > The insertData(offset, data) method steps are to replace data of this with offset, 0, and data.
210        self.ReplaceData(cx, offset, 0, arg)
211    }
212
213    /// <https://dom.spec.whatwg.org/#dom-characterdata-deletedata>
214    fn DeleteData(&self, cx: &mut JSContext, offset: u32, count: u32) -> ErrorResult {
215        // > The deleteData(offset, count) method steps are to replace data of this with offset, count, and the empty string.
216        self.ReplaceData(cx, offset, count, DOMString::new())
217    }
218
219    /// <https://dom.spec.whatwg.org/#dom-characterdata-replacedata>
220    fn ReplaceData(
221        &self,
222        cx: &mut JSContext,
223        offset: u32,
224        count: u32,
225        arg: DOMString,
226    ) -> ErrorResult {
227        let mut new_data;
228        {
229            let data = self.data.borrow();
230            let prefix;
231            let replacement_before;
232            let remaining;
233            match split_at_utf16_code_unit_offset(&data, offset) {
234                Ok((p, astral, r)) => {
235                    prefix = p;
236                    // As if we had split the UTF-16 surrogate pair in half
237                    // and then transcoded that to UTF-8 lossily,
238                    // since our DOMString is currently strict UTF-8.
239                    replacement_before = if astral.is_some() { "\u{FFFD}" } else { "" };
240                    remaining = r;
241                },
242                // Step 2.
243                Err(()) => return Err(Error::IndexSize(None)),
244            };
245            let replacement_after;
246            let suffix;
247            match split_at_utf16_code_unit_offset(remaining, count) {
248                // Steps 3.
249                Err(()) => {
250                    replacement_after = "";
251                    suffix = "";
252                },
253                Ok((_, astral, s)) => {
254                    // As if we had split the UTF-16 surrogate pair in half
255                    // and then transcoded that to UTF-8 lossily,
256                    // since our DOMString is currently strict UTF-8.
257                    replacement_after = if astral.is_some() { "\u{FFFD}" } else { "" };
258                    suffix = s;
259                },
260            };
261            // Step 4: Mutation observers.
262            self.queue_mutation_record(cx);
263
264            // Step 5 to 7.
265            new_data = String::with_capacity(
266                prefix.len() +
267                    replacement_before.len() +
268                    usize::from(arg.len_utf8()) +
269                    replacement_after.len() +
270                    suffix.len(),
271            );
272            new_data.push_str(prefix);
273            new_data.push_str(replacement_before);
274            new_data.push_str(&arg.str());
275            new_data.push_str(replacement_after);
276            new_data.push_str(suffix);
277        }
278        *self.data.safe_borrow_mut(cx.no_gc()) = new_data;
279        self.content_changed(cx);
280
281        let node = self.upcast::<Node>();
282
283        let mut utf16_length = None;
284        // TODO: ensure that DOMString’s are under 4 GiB?
285        let mut lazy_length = move || {
286            *utf16_length
287                .get_or_insert_with(|| Utf16CodeUnits::length_of(AssumeUnder4GB, &arg.str()).0)
288        };
289
290        let document = node.owner_doc_unrooted(cx.no_gc());
291        if let Some(selection) = document.selection() {
292            selection.replace_data_steps(node, offset, count, &mut lazy_length);
293        }
294        document.live_range_replace_data_steps(cx.no_gc(), node, offset, count, &mut lazy_length);
295
296        // Step 12: If node is a ProcessingInstruction node and piAttributesAlreadyUpdated
297        // is false, then update attributes from data given node.
298        // TODO: Implement this.
299
300        // Step 13: If node’s parent is non-null, then run the children changed steps for
301        // node’s parent.
302        // TODO: This is handled above, but it should be handled here.
303
304        Ok(())
305    }
306
307    /// <https://dom.spec.whatwg.org/#dom-childnode-before>
308    fn Before(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
309        self.upcast::<Node>().before(cx, nodes)
310    }
311
312    /// <https://dom.spec.whatwg.org/#dom-childnode-after>
313    fn After(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
314        self.upcast::<Node>().after(cx, nodes)
315    }
316
317    /// <https://dom.spec.whatwg.org/#dom-childnode-replacewith>
318    fn ReplaceWith(&self, cx: &mut JSContext, nodes: Vec<NodeOrString>) -> ErrorResult {
319        self.upcast::<Node>().replace_with(cx, nodes)
320    }
321
322    /// <https://dom.spec.whatwg.org/#dom-childnode-remove>
323    fn Remove(&self, cx: &mut JSContext) {
324        self.upcast::<Node>().remove_self(cx);
325    }
326
327    /// <https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling>
328    fn GetPreviousElementSibling(&self) -> Option<DomRoot<Element>> {
329        self.upcast::<Node>()
330            .preceding_siblings()
331            .find_map(DomRoot::downcast)
332    }
333
334    /// <https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling>
335    fn GetNextElementSibling(&self) -> Option<DomRoot<Element>> {
336        self.upcast::<Node>()
337            .following_siblings()
338            .find_map(DomRoot::downcast)
339    }
340}
341
342impl<'dom> LayoutDom<'dom, CharacterData> {
343    #[inline]
344    pub(crate) fn data_for_layout(self) -> AtomicRef<'dom, str> {
345        AtomicRef::map(self.unsafe_get().data.borrow(), |data| &**data)
346    }
347}
348
349/// Split the given string at the given position measured in UTF-16 code units from the start.
350///
351/// * `Err(())` indicates that `offset` if after the end of the string
352/// * `Ok((before, None, after))` indicates that `offset` is between Unicode code points.
353///   The two string slices are such that:
354///   `before == s.to_utf16()[..offset].to_utf8()` and
355///   `after == s.to_utf16()[offset..].to_utf8()`
356/// * `Ok((before, Some(ch), after))` indicates that `offset` is "in the middle"
357///   of a single Unicode code point that would be represented in UTF-16 by a surrogate pair
358///   of two 16-bit code units.
359///   `ch` is that code point.
360///   The two string slices are such that:
361///   `before == s.to_utf16()[..offset - 1].to_utf8()` and
362///   `after == s.to_utf16()[offset + 1..].to_utf8()`
363fn split_at_utf16_code_unit_offset(s: &str, offset: u32) -> Result<(&str, Option<char>, &str), ()> {
364    let mut code_units = 0;
365    for (i, c) in s.char_indices() {
366        if code_units == offset {
367            let (a, b) = s.split_at(i);
368            return Ok((a, None, b));
369        }
370        code_units += 1;
371        if c > '\u{FFFF}' {
372            if code_units == offset {
373                debug_assert_eq!(c.len_utf8(), 4);
374                warn!("Splitting a surrogate pair in CharacterData API.");
375                return Ok((&s[..i], Some(c), &s[i + c.len_utf8()..]));
376            }
377            code_units += 1;
378        }
379    }
380    if code_units == offset {
381        Ok((s, None, ""))
382    } else {
383        Err(())
384    }
385}