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