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