Skip to main content

blitz_html/
stream.rs

1//! A document tokenizer which releases its DOM borrow at script boundaries.
2
3use std::borrow::Cow;
4use std::cell::{Ref, RefCell};
5use std::rc::Rc;
6
7use blitz_dom::{BaseDocument, NodeId, QualName};
8use html5ever::TokenizerResult;
9use html5ever::buffer_queue::BufferQueue;
10use html5ever::tendril::StrTendril;
11use html5ever::tokenizer::{Tokenizer, TokenizerOpts};
12use html5ever::tree_builder::{
13    ElementFlags, NodeOrText, QuirksMode, TreeBuilder, TreeBuilderOpts, TreeSink,
14};
15
16use crate::DocumentHtmlParser;
17
18pub struct StreamInput(BufferQueue);
19
20impl StreamInput {
21    pub fn new(text: &str) -> Self {
22        let queue = BufferQueue::default();
23        queue.push_back(StrTendril::from(text));
24        Self(queue)
25    }
26}
27
28pub struct StreamingParser {
29    tokenizer: Tokenizer<TreeBuilder<NodeId, StreamSink>>,
30    input: StreamInput,
31}
32
33impl StreamingParser {
34    pub fn new(document: Rc<RefCell<BaseDocument>>, html: &str) -> Self {
35        let builder = TreeBuilder::new(
36            StreamSink { document },
37            TreeBuilderOpts {
38                scripting_enabled: true,
39                drop_doctype: false,
40                ..Default::default()
41            },
42        );
43        Self {
44            tokenizer: Tokenizer::new(builder, TokenizerOpts::default()),
45            input: StreamInput::new(html),
46        }
47    }
48
49    pub fn next_script(&mut self) -> Option<NodeId> {
50        // The input is already decoded, so a <meta charset> indicator only
51        // means: keep feeding.
52        loop {
53            match self.tokenizer.feed(&self.input.0) {
54                TokenizerResult::Script(id) => return Some(id),
55                TokenizerResult::Done => return None,
56                TokenizerResult::EncodingIndicator(_) => {}
57            }
58        }
59    }
60
61    /// Consume inserted input without consuming the navigation's remaining input.
62    pub fn feed_written(&mut self, input: &StreamInput) -> Option<NodeId> {
63        // The input is already decoded, so a <meta charset> indicator only
64        // means: keep feeding.
65        loop {
66            match self.tokenizer.feed(&input.0) {
67                TokenizerResult::Script(id) => return Some(id),
68                TokenizerResult::Done => return None,
69                TokenizerResult::EncodingIndicator(_) => {}
70            }
71        }
72    }
73
74    pub fn finish(&mut self) {
75        self.tokenizer.end();
76    }
77}
78
79struct StreamSink {
80    document: Rc<RefCell<BaseDocument>>,
81}
82
83impl StreamSink {
84    fn with_sink<R>(&self, callback: impl FnOnce(&DocumentHtmlParser<'_, '_>) -> R) -> R {
85        let mut document = self.document.borrow_mut();
86        let mut mutator = document.mutate();
87        let sink = DocumentHtmlParser::new(&mut mutator);
88        callback(&sink)
89    }
90}
91
92impl TreeSink for StreamSink {
93    type Output = ();
94    type Handle = NodeId;
95    type ElemName<'a>
96        = Ref<'a, QualName>
97    where
98        Self: 'a;
99
100    fn finish(self) {}
101
102    fn parse_error(&self, _: Cow<'static, str>) {}
103
104    fn get_document(&self) -> NodeId {
105        self.document.borrow().root_node().id
106    }
107
108    fn elem_name<'a>(&'a self, target: &'a NodeId) -> Self::ElemName<'a> {
109        Ref::map(self.document.borrow(), |document| {
110            &document
111                .get_node(*target)
112                .expect("parser element disappeared")
113                .element_data()
114                .expect("parser handle is not an element")
115                .name
116        })
117    }
118
119    fn create_element(
120        &self,
121        name: QualName,
122        attributes: Vec<html5ever::Attribute>,
123        flags: ElementFlags,
124    ) -> NodeId {
125        self.with_sink(|sink| sink.create_element(name, attributes, flags))
126    }
127
128    fn create_comment(&self, text: StrTendril) -> NodeId {
129        self.with_sink(|sink| sink.create_comment(text))
130    }
131
132    fn create_pi(&self, target: StrTendril, data: StrTendril) -> NodeId {
133        self.with_sink(|sink| sink.create_pi(target, data))
134    }
135
136    fn append(&self, parent: &NodeId, child: NodeOrText<NodeId>) {
137        self.with_sink(|sink| sink.append(parent, child));
138    }
139
140    fn append_before_sibling(&self, sibling: &NodeId, child: NodeOrText<NodeId>) {
141        self.with_sink(|sink| sink.append_before_sibling(sibling, child));
142    }
143
144    fn append_based_on_parent_node(
145        &self,
146        element: &NodeId,
147        previous: &NodeId,
148        child: NodeOrText<NodeId>,
149    ) {
150        self.with_sink(|sink| sink.append_based_on_parent_node(element, previous, child));
151    }
152
153    fn append_doctype_to_document(
154        &self,
155        name: StrTendril,
156        public_id: StrTendril,
157        system_id: StrTendril,
158    ) {
159        self.with_sink(|sink| sink.append_doctype_to_document(name, public_id, system_id));
160    }
161
162    fn get_template_contents(&self, target: &NodeId) -> NodeId {
163        self.with_sink(|sink| sink.get_template_contents(target))
164    }
165
166    fn same_node(&self, left: &NodeId, right: &NodeId) -> bool {
167        left == right
168    }
169
170    fn set_quirks_mode(&self, mode: QuirksMode) {
171        self.document
172            .borrow_mut()
173            .set_document_quirks_mode(match mode {
174                QuirksMode::NoQuirks => 0,
175                QuirksMode::LimitedQuirks => 1,
176                QuirksMode::Quirks => 2,
177            });
178    }
179
180    fn add_attrs_if_missing(&self, target: &NodeId, attributes: Vec<html5ever::Attribute>) {
181        self.with_sink(|sink| sink.add_attrs_if_missing(target, attributes));
182    }
183
184    fn remove_from_parent(&self, target: &NodeId) {
185        self.with_sink(|sink| sink.remove_from_parent(target));
186    }
187
188    fn reparent_children(&self, previous: &NodeId, parent: &NodeId) {
189        self.with_sink(|sink| sink.reparent_children(previous, parent));
190    }
191
192    fn allow_declarative_shadow_roots(&self, intended_parent: &NodeId) -> bool {
193        self.with_sink(|sink| sink.allow_declarative_shadow_roots(intended_parent))
194    }
195
196    fn attach_declarative_shadow(
197        &self,
198        location: &NodeId,
199        template: &NodeId,
200        attributes: &[html5ever::Attribute],
201    ) -> bool {
202        self.with_sink(|sink| sink.attach_declarative_shadow(location, template, attributes))
203    }
204
205    fn pop(&self, node: &NodeId) {
206        self.with_sink(|sink| sink.pop(node));
207    }
208}