Coverage Report

Created: 2026-02-14 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/html5ever/rcdom/lib.rs
Line
Count
Source
1
// Copyright 2014-2017 The html5ever Project Developers. See the
2
// COPYRIGHT file at the top-level directory of this distribution.
3
//
4
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7
// option. This file may not be copied, modified, or distributed
8
// except according to those terms.
9
10
//! A simple reference-counted DOM.
11
//!
12
//! This is sufficient as a static parse tree, but don't build a
13
//! web browser using it. :)
14
//!
15
//! A DOM is a [tree structure] with ordered children that can be represented in an XML-like
16
//! format. For example, the following graph
17
//!
18
//! ```text
19
//! div
20
//!  +- "text node"
21
//!  +- span
22
//! ```
23
//! in HTML would be serialized as
24
//!
25
//! ```html
26
//! <div>text node<span></span></div>
27
//! ```
28
//!
29
//! See the [document object model article on wikipedia][dom wiki] for more information.
30
//!
31
//! This implementation stores the information associated with each node once, and then hands out
32
//! refs to children. The nodes themselves are reference-counted to avoid copying - you can create
33
//! a new ref and then a node will outlive the document. Nodes own their children, but only have
34
//! weak references to their parents.
35
//!
36
//! [tree structure]: https://en.wikipedia.org/wiki/Tree_(data_structure)
37
//! [dom wiki]: https://en.wikipedia.org/wiki/Document_Object_Model
38
39
extern crate markup5ever;
40
extern crate tendril;
41
42
use std::borrow::Cow;
43
use std::cell::{Cell, RefCell};
44
use std::collections::{HashSet, VecDeque};
45
use std::default::Default;
46
use std::fmt;
47
use std::io;
48
use std::mem;
49
use std::rc::{Rc, Weak};
50
51
use tendril::StrTendril;
52
53
use markup5ever::interface::tree_builder;
54
use markup5ever::interface::tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink};
55
use markup5ever::serialize::TraversalScope;
56
use markup5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
57
use markup5ever::serialize::{Serialize, Serializer};
58
use markup5ever::Attribute;
59
use markup5ever::ExpandedName;
60
use markup5ever::QualName;
61
use xml5ever::interface::ElemName;
62
use xml5ever::local_name;
63
64
/// The different kinds of nodes in the DOM.
65
#[derive(Debug, Clone)]
66
pub enum NodeData {
67
    /// The `Document` itself - the root node of a HTML document.
68
    Document,
69
70
    /// A `DOCTYPE` with name, public id, and system id. See
71
    /// [document type declaration on wikipedia][dtd wiki].
72
    ///
73
    /// [dtd wiki]: https://en.wikipedia.org/wiki/Document_type_declaration
74
    Doctype {
75
        name: StrTendril,
76
        public_id: StrTendril,
77
        system_id: StrTendril,
78
    },
79
80
    /// A text node.
81
    Text { contents: RefCell<StrTendril> },
82
83
    /// A comment.
84
    Comment { contents: StrTendril },
85
86
    /// An element with attributes.
87
    Element {
88
        name: QualName,
89
        attrs: RefCell<Vec<Attribute>>,
90
91
        /// For HTML \<template\> elements, the [template contents].
92
        ///
93
        /// [template contents]: https://html.spec.whatwg.org/multipage/#template-contents
94
        template_contents: RefCell<Option<Handle>>,
95
96
        /// Whether the node is a [HTML integration point].
97
        ///
98
        /// [HTML integration point]: https://html.spec.whatwg.org/multipage/#html-integration-point
99
        mathml_annotation_xml_integration_point: bool,
100
    },
101
102
    /// A Processing instruction.
103
    ProcessingInstruction {
104
        target: StrTendril,
105
        contents: StrTendril,
106
    },
107
}
108
109
/// A DOM node.
110
pub struct Node {
111
    /// Parent node.
112
    pub parent: Cell<Option<WeakHandle>>,
113
    /// Child nodes of this node.
114
    pub children: RefCell<Vec<Handle>>,
115
    /// Represents this node's data.
116
    pub data: NodeData,
117
}
118
119
impl Node {
120
    /// Create a new node from its contents
121
10.3M
    pub fn new(data: NodeData) -> Rc<Self> {
122
10.3M
        Rc::new(Node {
123
10.3M
            data,
124
10.3M
            parent: Cell::new(None),
125
10.3M
            children: RefCell::new(Vec::new()),
126
10.3M
        })
127
10.3M
    }
128
129
    /// <https://html.spec.whatwg.org/#option-element-nearest-ancestor-select>
130
0
    fn get_option_element_nearest_ancestor_select(&self) -> Option<Rc<Self>> {
131
        // Step 1. Let ancestorOptgroup be null.
132
        // NOTE: The algorithm doesn't actually need the value, so a boolean is enough.
133
0
        let mut did_see_ancestor_optgroup = false;
134
135
        // Step 2. For each ancestor of option's ancestors, in reverse tree order:
136
0
        let mut current = self.parent().and_then(|parent| parent.upgrade())?;
137
        loop {
138
0
            if let NodeData::Element { name, .. } = &current.data {
139
                // Step 2.1 If ancestor is a datalist, hr, or option element, then return null.
140
0
                if matches!(
141
0
                    name.local_name(),
142
                    &local_name!("datalist") | &local_name!("hr") | &local_name!("option")
143
                ) {
144
0
                    return None;
145
0
                }
146
147
                // Step 2.2 If ancestor is an optgroup element:
148
0
                if name.local_name() == &local_name!("optgroup") {
149
                    // Step 2.2.1 If ancestorOptgroup is not null, then return null.
150
0
                    if did_see_ancestor_optgroup {
151
0
                        return None;
152
0
                    }
153
154
                    // Step 2.2.2 Set ancestorOptgroup to ancestor.
155
0
                    did_see_ancestor_optgroup = true;
156
0
                }
157
158
                // Step 2.3 If ancestor is a select, then return ancestor.
159
0
                if name.local_name() == &local_name!("select") {
160
0
                    return Some(current);
161
0
                }
162
0
            };
163
164
            // Move on to the next ancestor
165
0
            let Some(next_ancestor) = current.parent().and_then(|parent| parent.upgrade()) else {
166
0
                break;
167
            };
168
0
            current = next_ancestor;
169
        }
170
171
        // Step 3. Return null.
172
0
        None
173
0
    }
174
175
0
    fn parent(&self) -> Option<Weak<Self>> {
176
0
        let parent = self.parent.take();
177
0
        self.parent.set(parent.clone());
178
0
        parent
179
0
    }
180
181
    /// <https://html.spec.whatwg.org/#select-enabled-selectedcontent>
182
0
    fn get_a_selects_enabled_selectedcontent(&self) -> Option<Rc<Self>> {
183
        // Step 1. If select has the multiple attribute, then return null.
184
0
        let NodeData::Element { name, attrs, .. } = &self.data else {
185
0
            panic!("Trying to get selectedcontent of non-element");
186
        };
187
0
        debug_assert_eq!(name.local_name(), &local_name!("select"));
188
0
        if attrs
189
0
            .borrow()
190
0
            .iter()
191
0
            .any(|attribute| attribute.name.local == local_name!("multiple"))
192
        {
193
0
            return None;
194
0
        }
195
196
        // Step 2. Let selectedcontent be the first selectedcontent element descendant of select in tree order
197
        // if any such element exists; otherwise return null.
198
        // FIXME: This does not visit the nodes in tree order
199
0
        let mut remaining = VecDeque::default();
200
0
        remaining.extend(self.children.borrow().iter().cloned());
201
0
        let mut selectedcontent = None;
202
0
        while let Some(node) = remaining.pop_front() {
203
0
            remaining.extend(node.children.borrow().iter().cloned());
204
205
0
            let NodeData::Element { name, .. } = &self.data else {
206
0
                continue;
207
            };
208
0
            if name.local_name() == &local_name!("selectedcontent") {
209
0
                selectedcontent = Some(node);
210
0
                break;
211
0
            }
212
        }
213
0
        let selectedcontent = selectedcontent?;
214
215
        // Step 3. If selectedcontent's disabled is true, then return null.
216
        // FIXME: This step is unimplemented for now to reduce complexity.
217
218
        // Step 4. Return selectedcontent.
219
0
        Some(selectedcontent)
220
0
    }
221
222
    /// <https://html.spec.whatwg.org/#clone-an-option-into-a-selectedcontent>
223
0
    fn clone_an_option_into_selectedcontent(&self, selectedcontent: Rc<Self>) {
224
        // Step 1. Let documentFragment be a new DocumentFragment whose node document is option's node document.
225
        // NOTE: We just remember the children of said fragment, thats good enough.
226
0
        let mut document_fragment = Vec::new();
227
228
        // Step 2. For each child of option's children:
229
0
        for child in self.children.borrow().iter() {
230
0
            // Step 2.1 Let childClone be the result of running clone given child with subtree set to true.
231
0
            let child_clone = child.clone_with_subtree();
232
0
233
0
            // Step 2.2 Append childClone to documentFragment.
234
0
            document_fragment.push(child_clone);
235
0
        }
236
237
        // Step 3. Replace all with documentFragment within selectedcontent.
238
0
        *selectedcontent.children.borrow_mut() = document_fragment;
239
0
    }
240
241
    /// Clones the node and all of its descendants, returning a handle to the new subtree.
242
    ///
243
    /// This function will run into infinite recursion when the DOM tree contains cycles and it makes
244
    /// no attempts to guard against that.
245
0
    fn clone_with_subtree(&self) -> Rc<Self> {
246
0
        let children = self
247
0
            .children
248
0
            .borrow()
249
0
            .iter()
250
0
            .map(|child| child.clone_with_subtree())
251
0
            .collect();
252
0
        Rc::new(Self {
253
0
            parent: Cell::new(self.parent()),
254
0
            data: self.data.clone(),
255
0
            children: RefCell::new(children),
256
0
        })
257
0
    }
258
}
259
260
impl Drop for Node {
261
10.3M
    fn drop(&mut self) {
262
10.3M
        let mut nodes = mem::take(&mut *self.children.borrow_mut());
263
20.6M
        while let Some(node) = nodes.pop() {
264
10.2M
            let children = mem::take(&mut *node.children.borrow_mut());
265
10.2M
            nodes.extend(children.into_iter());
266
            if let NodeData::Element {
267
1.22M
                ref template_contents,
268
                ..
269
10.2M
            } = node.data
270
            {
271
1.22M
                if let Some(template_contents) = template_contents.borrow_mut().take() {
272
1.46k
                    nodes.push(template_contents);
273
1.22M
                }
274
9.07M
            }
275
        }
276
10.3M
    }
277
}
278
279
impl fmt::Debug for Node {
280
0
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
281
0
        fmt.debug_struct("Node")
282
0
            .field("data", &self.data)
283
0
            .field("children", &self.children)
284
0
            .finish()
285
0
    }
286
}
287
288
/// Reference to a DOM node.
289
pub type Handle = Rc<Node>;
290
291
/// Weak reference to a DOM node, used for parent pointers.
292
pub type WeakHandle = Weak<Node>;
293
294
/// Append a parentless node to another nodes' children
295
10.2M
fn append(new_parent: &Handle, child: Handle) {
296
10.2M
    let previous_parent = child.parent.replace(Some(Rc::downgrade(new_parent)));
297
    // Invariant: child cannot have existing parent
298
10.2M
    assert!(previous_parent.is_none());
299
10.2M
    new_parent.children.borrow_mut().push(child);
300
10.2M
}
301
302
/// If the node has a parent, get it and this node's position in its children
303
0
fn get_parent_and_index(target: &Handle) -> Option<(Handle, usize)> {
304
0
    if let Some(weak) = target.parent.take() {
305
0
        let parent = weak.upgrade().expect("dangling weak pointer");
306
0
        target.parent.set(Some(weak));
307
0
        let i = match parent
308
0
            .children
309
0
            .borrow()
310
0
            .iter()
311
0
            .enumerate()
312
0
            .find(|&(_, child)| Rc::ptr_eq(child, target))
313
        {
314
0
            Some((i, _)) => i,
315
0
            None => panic!("have parent but couldn't find in parent's children!"),
316
        };
317
0
        Some((parent, i))
318
    } else {
319
0
        None
320
    }
321
0
}
322
323
7.05M
fn append_to_existing_text(prev: &Handle, text: &str) -> bool {
324
7.05M
    match prev.data {
325
2.90M
        NodeData::Text { ref contents } => {
326
2.90M
            contents.borrow_mut().push_slice(text);
327
2.90M
            true
328
        },
329
4.14M
        _ => false,
330
    }
331
7.05M
}
332
333
0
fn remove_from_parent(target: &Handle) {
334
0
    if let Some((parent, i)) = get_parent_and_index(target) {
335
0
        parent.children.borrow_mut().remove(i);
336
0
        target.parent.set(None);
337
0
    }
338
0
}
339
340
/// The DOM itself; the result of parsing.
341
pub struct RcDom {
342
    /// The `Document` itself.
343
    pub document: Handle,
344
345
    /// Errors that occurred during parsing.
346
    pub errors: RefCell<Vec<Cow<'static, str>>>,
347
348
    /// The document's quirks mode.
349
    pub quirks_mode: Cell<QuirksMode>,
350
}
351
352
impl TreeSink for RcDom {
353
    type Output = Self;
354
12.9k
    fn finish(self) -> Self {
355
12.9k
        self
356
12.9k
    }
357
358
    type Handle = Handle;
359
360
    type ElemName<'a>
361
        = ExpandedName<'a>
362
    where
363
        Self: 'a;
364
365
42.7M
    fn parse_error(&self, msg: Cow<'static, str>) {
366
42.7M
        self.errors.borrow_mut().push(msg);
367
42.7M
    }
368
369
12.9k
    fn get_document(&self) -> Handle {
370
12.9k
        self.document.clone()
371
12.9k
    }
372
373
0
    fn get_template_contents(&self, target: &Handle) -> Handle {
374
        if let NodeData::Element {
375
0
            ref template_contents,
376
            ..
377
0
        } = target.data
378
        {
379
0
            template_contents
380
0
                .borrow()
381
0
                .as_ref()
382
0
                .expect("not a template element!")
383
0
                .clone()
384
        } else {
385
0
            panic!("not a template element!")
386
        }
387
0
    }
388
389
0
    fn set_quirks_mode(&self, mode: QuirksMode) {
390
0
        self.quirks_mode.set(mode);
391
0
    }
392
393
0
    fn same_node(&self, x: &Handle, y: &Handle) -> bool {
394
0
        Rc::ptr_eq(x, y)
395
0
    }
396
397
77.4M
    fn elem_name<'a>(&self, target: &'a Handle) -> ExpandedName<'a> {
398
77.4M
        match target.data {
399
77.4M
            NodeData::Element { ref name, .. } => name.expanded(),
400
0
            _ => panic!("not an element!"),
401
        }
402
77.4M
    }
403
404
1.22M
    fn create_element(&self, name: QualName, attrs: Vec<Attribute>, flags: ElementFlags) -> Handle {
405
1.22M
        Node::new(NodeData::Element {
406
1.22M
            name,
407
1.22M
            attrs: RefCell::new(attrs),
408
1.22M
            template_contents: RefCell::new(if flags.template {
409
1.46k
                Some(Node::new(NodeData::Document))
410
            } else {
411
1.22M
                None
412
            }),
413
1.22M
            mathml_annotation_xml_integration_point: flags.mathml_annotation_xml_integration_point,
414
        })
415
1.22M
    }
416
417
4.73M
    fn create_comment(&self, text: StrTendril) -> Handle {
418
4.73M
        Node::new(NodeData::Comment { contents: text })
419
4.73M
    }
420
421
21.4k
    fn create_pi(&self, target: StrTendril, data: StrTendril) -> Handle {
422
21.4k
        Node::new(NodeData::ProcessingInstruction {
423
21.4k
            target,
424
21.4k
            contents: data,
425
21.4k
        })
426
21.4k
    }
427
428
13.1M
    fn append(&self, parent: &Handle, child: NodeOrText<Handle>) {
429
        // Append to an existing Text node if we have one.
430
13.1M
        if let NodeOrText::AppendText(text) = &child {
431
7.19M
            if let Some(h) = parent.children.borrow().last() {
432
7.05M
                if append_to_existing_text(h, text) {
433
2.90M
                    return;
434
4.14M
                }
435
139k
            }
436
5.98M
        }
437
438
10.2M
        append(
439
10.2M
            parent,
440
10.2M
            match child {
441
4.28M
                NodeOrText::AppendText(text) => Node::new(NodeData::Text {
442
4.28M
                    contents: RefCell::new(text),
443
4.28M
                }),
444
5.98M
                NodeOrText::AppendNode(node) => node,
445
            },
446
        );
447
13.1M
    }
448
449
0
    fn append_before_sibling(&self, sibling: &Handle, child: NodeOrText<Handle>) {
450
0
        let (parent, i) = get_parent_and_index(sibling)
451
0
            .expect("append_before_sibling called on node without parent");
452
453
0
        let child = match (child, i) {
454
            // No previous node.
455
0
            (NodeOrText::AppendText(text), 0) => Node::new(NodeData::Text {
456
0
                contents: RefCell::new(text),
457
0
            }),
458
459
            // Look for a text node before the insertion point.
460
0
            (NodeOrText::AppendText(text), i) => {
461
0
                let children = parent.children.borrow();
462
0
                let prev = &children[i - 1];
463
0
                if append_to_existing_text(prev, &text) {
464
0
                    return;
465
0
                }
466
0
                Node::new(NodeData::Text {
467
0
                    contents: RefCell::new(text),
468
0
                })
469
            },
470
471
            // The tree builder promises we won't have a text node after
472
            // the insertion point.
473
474
            // Any other kind of node.
475
0
            (NodeOrText::AppendNode(node), _) => node,
476
        };
477
478
0
        remove_from_parent(&child);
479
480
0
        child.parent.set(Some(Rc::downgrade(&parent)));
481
0
        parent.children.borrow_mut().insert(i, child);
482
0
    }
483
484
0
    fn append_based_on_parent_node(
485
0
        &self,
486
0
        element: &Self::Handle,
487
0
        prev_element: &Self::Handle,
488
0
        child: NodeOrText<Self::Handle>,
489
0
    ) {
490
0
        let parent = element.parent.take();
491
0
        let has_parent = parent.is_some();
492
0
        element.parent.set(parent);
493
494
0
        if has_parent {
495
0
            self.append_before_sibling(element, child);
496
0
        } else {
497
0
            self.append(prev_element, child);
498
0
        }
499
0
    }
500
501
24.1k
    fn append_doctype_to_document(
502
24.1k
        &self,
503
24.1k
        name: StrTendril,
504
24.1k
        public_id: StrTendril,
505
24.1k
        system_id: StrTendril,
506
24.1k
    ) {
507
24.1k
        append(
508
24.1k
            &self.document,
509
24.1k
            Node::new(NodeData::Doctype {
510
24.1k
                name,
511
24.1k
                public_id,
512
24.1k
                system_id,
513
24.1k
            }),
514
        );
515
24.1k
    }
516
517
0
    fn add_attrs_if_missing(&self, target: &Handle, attrs: Vec<Attribute>) {
518
0
        let mut existing = if let NodeData::Element { ref attrs, .. } = target.data {
519
0
            attrs.borrow_mut()
520
        } else {
521
0
            panic!("not an element")
522
        };
523
524
0
        let existing_names = existing
525
0
            .iter()
526
0
            .map(|e| e.name.clone())
527
0
            .collect::<HashSet<_>>();
528
0
        existing.extend(
529
0
            attrs
530
0
                .into_iter()
531
0
                .filter(|attr| !existing_names.contains(&attr.name)),
532
        );
533
0
    }
534
535
0
    fn remove_from_parent(&self, target: &Handle) {
536
0
        remove_from_parent(target);
537
0
    }
538
539
0
    fn reparent_children(&self, node: &Handle, new_parent: &Handle) {
540
0
        let mut children = node.children.borrow_mut();
541
0
        let mut new_children = new_parent.children.borrow_mut();
542
0
        for child in children.iter() {
543
0
            let previous_parent = child.parent.replace(Some(Rc::downgrade(new_parent)));
544
0
            assert!(Rc::ptr_eq(
545
0
                node,
546
0
                &previous_parent.unwrap().upgrade().expect("dangling weak")
547
0
            ))
548
        }
549
0
        new_children.extend(mem::take(&mut *children));
550
0
    }
551
552
0
    fn is_mathml_annotation_xml_integration_point(&self, target: &Handle) -> bool {
553
        if let NodeData::Element {
554
0
            mathml_annotation_xml_integration_point,
555
            ..
556
0
        } = target.data
557
        {
558
0
            mathml_annotation_xml_integration_point
559
        } else {
560
0
            panic!("not an element!")
561
        }
562
0
    }
563
564
0
    fn maybe_clone_an_option_into_selectedcontent(&self, option: &Self::Handle) {
565
0
        let NodeData::Element { name, attrs, .. } = &option.data else {
566
0
            panic!("\"maybe clone an option into selectedcontent\" called with non-element node");
567
        };
568
0
        debug_assert_eq!(name.local_name(), &local_name!("option"));
569
570
        // Step 1. Let select be option's option element nearest ancestor select.
571
0
        let select = option.get_option_element_nearest_ancestor_select();
572
573
        // Step 2. If all of the following conditions are true:
574
        // * select is not null;
575
        // * option's selectedness is true; and
576
        // * select's enabled selectedcontent is not null,
577
        // then run clone an option into a selectedcontent given option and select's enabled selectedcontent.
578
0
        if let Some(selectedcontent) =
579
0
            select.and_then(|select| select.get_a_selects_enabled_selectedcontent())
580
        {
581
0
            if attrs
582
0
                .borrow()
583
0
                .iter()
584
0
                .any(|attribute| attribute.name.local == local_name!("selected"))
585
0
            {
586
0
                option.clone_an_option_into_selectedcontent(selectedcontent);
587
0
            }
588
0
        }
589
0
    }
590
}
591
592
impl Default for RcDom {
593
12.9k
    fn default() -> RcDom {
594
12.9k
        RcDom {
595
12.9k
            document: Node::new(NodeData::Document),
596
12.9k
            errors: Default::default(),
597
12.9k
            quirks_mode: Cell::new(tree_builder::NoQuirks),
598
12.9k
        }
599
12.9k
    }
600
}
601
602
enum SerializeOp {
603
    Open(Handle),
604
    Close(QualName),
605
}
606
607
pub struct SerializableHandle(Handle);
608
609
impl From<Handle> for SerializableHandle {
610
12.9k
    fn from(h: Handle) -> SerializableHandle {
611
12.9k
        SerializableHandle(h)
612
12.9k
    }
613
}
614
615
impl Serialize for SerializableHandle {
616
12.9k
    fn serialize<S>(&self, serializer: &mut S, traversal_scope: TraversalScope) -> io::Result<()>
617
12.9k
    where
618
12.9k
        S: Serializer,
619
    {
620
12.9k
        let mut ops = VecDeque::new();
621
12.9k
        match traversal_scope {
622
0
            IncludeNode => ops.push_back(SerializeOp::Open(self.0.clone())),
623
12.9k
            ChildrenOnly(_) => ops.extend(
624
12.9k
                self.0
625
12.9k
                    .children
626
12.9k
                    .borrow()
627
12.9k
                    .iter()
628
510k
                    .map(|h| SerializeOp::Open(h.clone())),
629
            ),
630
        }
631
632
11.5M
        while let Some(op) = ops.pop_front() {
633
11.5M
            match op {
634
10.2M
                SerializeOp::Open(handle) => match handle.data {
635
                    NodeData::Element {
636
1.22M
                        ref name,
637
1.22M
                        ref attrs,
638
                        ..
639
                    } => {
640
1.22M
                        serializer.start_elem(
641
1.22M
                            name.clone(),
642
2.28M
                            attrs.borrow().iter().map(|at| (&at.name, &at.value[..])),
643
0
                        )?;
644
645
1.22M
                        ops.reserve(1 + handle.children.borrow().len());
646
1.22M
                        ops.push_front(SerializeOp::Close(name.clone()));
647
648
9.78M
                        for child in handle.children.borrow().iter().rev() {
649
9.78M
                            ops.push_front(SerializeOp::Open(child.clone()));
650
9.78M
                        }
651
                    },
652
653
24.1k
                    NodeData::Doctype { ref name, .. } => serializer.write_doctype(name)?,
654
655
4.28M
                    NodeData::Text { ref contents } => serializer.write_text(&contents.borrow())?,
656
657
4.73M
                    NodeData::Comment { ref contents } => serializer.write_comment(contents)?,
658
659
                    NodeData::ProcessingInstruction {
660
21.4k
                        ref target,
661
21.4k
                        ref contents,
662
21.4k
                    } => serializer.write_processing_instruction(target, contents)?,
663
664
0
                    NodeData::Document => panic!("Can't serialize Document node itself"),
665
                },
666
667
1.22M
                SerializeOp::Close(name) => {
668
1.22M
                    serializer.end_elem(name)?;
669
                },
670
            }
671
        }
672
673
12.9k
        Ok(())
674
12.9k
    }
675
}