1use html5ever::ParseOpts;
4use html5ever::tokenizer::TokenizerOpts;
5use html5ever::tree_builder::TreeBuilderOpts;
6use std::borrow::Cow;
7use std::cell::{Cell, Ref, RefCell, RefMut};
8use std::sync::Arc;
9
10use blitz_dom::node::{Attribute, MarkupNode, NodeFlags};
11use blitz_dom::{DocumentMutator, HtmlParserProvider, NodeId};
12use html5ever::{
13 QualName,
14 tendril::{StrTendril, TendrilSink},
15 tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink},
16};
17
18fn html5ever_to_blitz_attr(attr: html5ever::Attribute) -> Attribute {
26 Attribute {
27 name: attr.name,
28 value: attr.value.as_ref().into(),
29 }
30}
31
32#[derive(Copy, Clone, Default, Debug)]
33pub struct HtmlProvider;
34
35impl HtmlParserProvider for HtmlProvider {
36 fn parse_inner_html<'m2, 'doc2>(
37 &self,
38 mutr: &'m2 mut DocumentMutator<'doc2>,
39 element_id: NodeId,
40 html: &str,
41 ) {
42 DocumentHtmlParser::parse_inner_html_into_mutator(mutr, element_id, html, false);
43 }
44
45 fn parse_fragment<'m2, 'doc2>(
46 &self,
47 mutr: &'m2 mut DocumentMutator<'doc2>,
48 element_id: NodeId,
49 html: &str,
50 allow_declarative_shadow_roots: bool,
51 ) {
52 DocumentHtmlParser::parse_inner_html_into_mutator(
53 mutr,
54 element_id,
55 html,
56 allow_declarative_shadow_roots,
57 );
58 }
59
60 fn parse_document(
61 &self,
62 html: &str,
63 config: blitz_dom::DocumentConfig,
64 ) -> Box<dyn blitz_dom::Document> {
65 Box::new(crate::HtmlDocument::from_html(html, config))
66 }
67}
68
69pub struct DocumentHtmlParser<'m, 'doc> {
70 document_mutator: RefCell<&'m mut DocumentMutator<'doc>>,
71
72 pub errors: RefCell<Vec<Cow<'static, str>>>,
74
75 pub quirks_mode: Cell<QuirksMode>,
77 pub is_xml: bool,
78 document_id: Option<NodeId>,
79 parsing_document: bool,
80 allow_declarative_shadow_roots: bool,
83}
84
85impl<'m, 'doc> DocumentHtmlParser<'m, 'doc> {
86 #[track_caller]
87 fn mutr(&self) -> RefMut<'_, &'m mut DocumentMutator<'doc>> {
89 self.document_mutator.borrow_mut()
90 }
91
92 fn adopt_created(&self, node_id: NodeId) {
93 if let Some(document_id) = self.document_id {
94 self.mutr().adopt_node(node_id, document_id);
95 }
96 }
97}
98
99impl<'m, 'doc> DocumentHtmlParser<'m, 'doc> {
100 pub fn new(mutr: &'m mut DocumentMutator<'doc>) -> DocumentHtmlParser<'m, 'doc> {
101 DocumentHtmlParser {
102 document_mutator: RefCell::new(mutr),
103 errors: RefCell::new(Vec::new()),
104 quirks_mode: Cell::new(QuirksMode::NoQuirks),
105 is_xml: false,
106 document_id: None,
107 parsing_document: true,
108 allow_declarative_shadow_roots: true,
109 }
110 }
111
112 pub fn parse_inert_into_mutator(
115 mutr: &mut DocumentMutator<'_>,
116 document_id: NodeId,
117 source: &str,
118 is_xml: bool,
119 ) -> Vec<Cow<'static, str>> {
120 let mut sink = DocumentHtmlParser::new(mutr);
121 sink.document_id = Some(document_id);
122 sink.is_xml = is_xml;
123 sink.allow_declarative_shadow_roots = false;
124 if is_xml {
125 let errors = xml5ever::driver::parse_document(sink, Default::default()).one(source);
126 Self::repair_xml_prolog(mutr, document_id, source);
127 errors
128 } else {
129 let opts = ParseOpts {
130 tokenizer: TokenizerOpts::default(),
131 tree_builder: TreeBuilderOpts {
132 exact_errors: false,
133 scripting_enabled: false,
134 iframe_srcdoc: false,
135 drop_doctype: false,
136 quirks_mode: QuirksMode::NoQuirks,
137 },
138 };
139 html5ever::parse_document(sink, opts).one(source)
140 }
141 }
142
143 fn repair_xml_prolog(mutr: &mut DocumentMutator<'_>, document_id: NodeId, source: &str) {
148 let children: Vec<NodeId> = mutr
149 .doc
150 .get_node(document_id)
151 .map(|node| node.children.to_vec())
152 .unwrap_or_default();
153 let name = source.find("<!DOCTYPE").map(|start| {
154 source[start + "<!DOCTYPE".len()..]
155 .trim_start()
156 .split(|c: char| c.is_whitespace() || c == '[' || c == '>')
157 .next()
158 .unwrap_or_default()
159 .to_owned()
160 });
161 for child in children {
162 let markup = mutr
163 .doc
164 .get_node(child)
165 .and_then(|node| node.markup.clone());
166 match markup.as_deref() {
167 Some(MarkupNode::ProcessingInstruction { target }) if target == "xml" => {
168 mutr.remove_node(child);
169 }
170 Some(MarkupNode::Doctype {
171 name: parsed,
172 public_id,
173 system_id,
174 }) => {
175 if let Some(original) = &name
176 && original.eq_ignore_ascii_case(parsed)
177 && original != parsed
178 {
179 let repaired = MarkupNode::Doctype {
180 name: original.clone(),
181 public_id: public_id.clone(),
182 system_id: system_id.clone(),
183 };
184 if let Some(node) = mutr.doc.get_node_mut(child) {
185 node.markup = Some(Arc::new(repaired));
186 }
187 }
188 }
189 _ => {}
190 }
191 }
192 }
193
194 fn root_element_has_xhtml_namespace(html: &str) -> bool {
197 let rest = html.trim_start_matches('\u{feff}').trim_start();
198 let Some(rest) = rest.strip_prefix("<html") else {
199 return false;
200 };
201 let Some(tag_end) = rest.find('>') else {
202 return false;
203 };
204 rest[..tag_end].contains("xmlns=\"http://www.w3.org/1999/xhtml\"")
205 || rest[..tag_end].contains("xmlns='http://www.w3.org/1999/xhtml'")
206 }
207
208 pub fn is_xhtml_document(html: &str) -> bool {
209 html.starts_with("<?xml")
210 || html.starts_with("<!DOCTYPE") && {
211 let first_line = html.lines().next().unwrap();
212 first_line.contains("XHTML") || first_line.contains("xhtml")
213 }
214 || Self::root_element_has_xhtml_namespace(html)
215 }
216
217 pub fn parse_into_mutator<'a, 'd>(mutr: &'a mut DocumentMutator<'d>, html: &str) {
218 let mut sink = DocumentHtmlParser::new(mutr);
219
220 let is_xhtml_doc = Self::is_xhtml_document(html);
221
222 if is_xhtml_doc {
223 sink.is_xml = true;
225 xml5ever::driver::parse_document(sink, Default::default())
226 .from_utf8()
227 .read_from(&mut html.as_bytes())
228 .unwrap();
229 } else {
230 sink.is_xml = false;
232 let opts = ParseOpts {
233 tokenizer: TokenizerOpts::default(),
234 tree_builder: TreeBuilderOpts {
235 exact_errors: false,
236 scripting_enabled: true, iframe_srcdoc: false,
238 drop_doctype: false,
239 quirks_mode: QuirksMode::NoQuirks,
240 },
241 };
242 html5ever::parse_document(sink, opts)
243 .from_utf8()
244 .read_from(&mut html.as_bytes())
245 .unwrap();
246 }
247 }
248
249 pub fn parse_html_unsafe_into_mutator(
252 mutr: &mut DocumentMutator<'_>,
253 document_id: NodeId,
254 source: &str,
255 ) -> Vec<Cow<'static, str>> {
256 let mut sink = DocumentHtmlParser::new(mutr);
257 sink.document_id = Some(document_id);
258 sink.is_xml = false;
259 sink.allow_declarative_shadow_roots = true;
260 let opts = ParseOpts {
261 tokenizer: TokenizerOpts::default(),
262 tree_builder: TreeBuilderOpts {
263 exact_errors: false,
264 scripting_enabled: false,
265 iframe_srcdoc: false,
266 drop_doctype: false,
267 quirks_mode: QuirksMode::NoQuirks,
268 },
269 };
270 html5ever::parse_document(sink, opts).one(source)
271 }
272
273 pub fn parse_inner_html_into_mutator<'a, 'd>(
274 mutr: &'a mut DocumentMutator<'d>,
275 element_id: NodeId,
276 html: &str,
277 allow_declarative_shadow_roots: bool,
278 ) {
279 let mut sink = DocumentHtmlParser::new(mutr);
280 sink.parsing_document = false;
281 sink.allow_declarative_shadow_roots = allow_declarative_shadow_roots;
282
283 let opts = ParseOpts {
284 tokenizer: TokenizerOpts::default(),
285 tree_builder: TreeBuilderOpts {
286 exact_errors: false,
287 scripting_enabled: true, iframe_srcdoc: false,
289 drop_doctype: true,
290 quirks_mode: QuirksMode::NoQuirks,
291 },
292 };
293 html5ever::driver::parse_fragment_for_element(sink, opts, element_id, false, None)
294 .from_utf8()
295 .read_from(&mut html.as_bytes())
296 .unwrap();
297
298 let document_id = mutr.doc.root_node().id;
301 let fragment_root_id = mutr.last_child_id(document_id).unwrap();
302 let child_ids = mutr.child_ids(fragment_root_id);
303 let target_id = mutr
304 .doc
305 .get_node(element_id)
306 .and_then(|node| node.element_data())
307 .and_then(|element| element.template_contents)
308 .unwrap_or(element_id);
309 mutr.append_children(target_id, &child_ids);
310 mutr.remove_and_drop_node(fragment_root_id);
311 }
312}
313
314impl<'m, 'doc> TreeSink for DocumentHtmlParser<'m, 'doc> {
315 type Output = Vec<Cow<'static, str>>;
316
317 type Handle = NodeId;
319
320 type ElemName<'a>
321 = Ref<'a, QualName>
322 where
323 Self: 'a;
324
325 fn finish(self) -> Self::Output {
326 #[cfg(feature = "tracing")]
327 for error in self.errors.borrow().iter() {
328 tracing::error!("{error}");
329 }
330 self.errors.into_inner()
331 }
332
333 fn parse_error(&self, msg: Cow<'static, str>) {
334 self.errors.borrow_mut().push(msg);
335 }
336
337 fn get_document(&self) -> Self::Handle {
338 self.document_id
339 .unwrap_or_else(|| self.document_mutator.borrow().doc.root_node().id)
340 }
341
342 fn elem_name<'a>(&'a self, target: &'a Self::Handle) -> Self::ElemName<'a> {
343 Ref::map(self.document_mutator.borrow(), |docm| {
344 docm.element_name(*target)
345 .expect("TreeSink::elem_name called on a node which is not an element!")
346 })
347 }
348
349 fn create_element(
350 &self,
351 name: QualName,
352 attrs: Vec<html5ever::Attribute>,
353 _flags: ElementFlags,
354 ) -> Self::Handle {
355 let inert_script = self.document_id.is_some() && name.local.as_ref() == "script";
356 let attrs = attrs.into_iter().map(html5ever_to_blitz_attr).collect();
357 let id = self.mutr().create_element(name, attrs);
358 self.adopt_created(id);
359 if inert_script {
360 self.mutr()
361 .doc
362 .get_node_mut(id)
363 .expect("new parser element missing")
364 .flags
365 .insert(NodeFlags::IS_PARSER_INERT_SCRIPT);
366 }
367 id
368 }
369
370 fn create_comment(&self, text: StrTendril) -> Self::Handle {
371 let id = self.mutr().create_comment_node(&text);
372 self.adopt_created(id);
373 id
374 }
375
376 fn create_pi(&self, target: StrTendril, data: StrTendril) -> Self::Handle {
377 if self.document_id.is_none() {
378 return self.mutr().create_comment_node("");
379 }
380 let id = self.mutr().create_comment_node(&data);
381 self.adopt_created(id);
382 self.mutr()
383 .doc
384 .get_node_mut(id)
385 .expect("new processing instruction missing")
386 .markup = Some(Arc::new(MarkupNode::ProcessingInstruction {
387 target: target.to_string(),
388 }));
389 id
390 }
391
392 fn append(&self, parent_id: &Self::Handle, child: NodeOrText<Self::Handle>) {
393 match child {
394 NodeOrText::AppendNode(id) => self.mutr().append_children(*parent_id, &[id]),
395 NodeOrText::AppendText(text) => {
398 let last_child_id = self.mutr().last_child_id(*parent_id);
399 let has_appended = if let Some(id) = last_child_id {
400 self.mutr().append_text_to_node(id, &text).is_ok()
401 } else {
402 false
403 };
404 if !has_appended {
405 let new_child_id = self.mutr().create_text_node(&text);
406 self.adopt_created(new_child_id);
407 self.mutr().append_children(*parent_id, &[new_child_id]);
408 }
409 }
410 }
411 }
412
413 fn append_before_sibling(&self, sibling_id: &Self::Handle, new_node: NodeOrText<Self::Handle>) {
416 match new_node {
417 NodeOrText::AppendNode(id) => self.mutr().insert_nodes_before(*sibling_id, &[id]),
418 NodeOrText::AppendText(text) => {
421 let previous_sibling_id = self.mutr().previous_sibling_id(*sibling_id);
422 let has_appended = if let Some(id) = previous_sibling_id {
423 self.mutr().append_text_to_node(id, &text).is_ok()
424 } else {
425 false
426 };
427 if !has_appended {
428 let new_child_id = self.mutr().create_text_node(&text);
429 self.adopt_created(new_child_id);
430 self.mutr()
431 .insert_nodes_before(*sibling_id, &[new_child_id]);
432 }
433 }
434 };
435 }
436
437 fn append_based_on_parent_node(
438 &self,
439 element: &Self::Handle,
440 prev_element: &Self::Handle,
441 child: NodeOrText<Self::Handle>,
442 ) {
443 if self.mutr().node_has_parent(*element) {
444 self.append_before_sibling(element, child);
445 } else {
446 self.append(prev_element, child);
447 }
448 }
449
450 fn append_doctype_to_document(
451 &self,
452 name: StrTendril,
453 public_id: StrTendril,
454 system_id: StrTendril,
455 ) {
456 let document_id = self.get_document();
457 let mut mutr = self.mutr();
458 let id = mutr.create_comment_node("");
459 mutr.adopt_node(id, document_id);
460 mutr.doc
461 .get_node_mut(id)
462 .expect("new doctype missing")
463 .markup = Some(Arc::new(MarkupNode::Doctype {
464 name: name.to_string(),
465 public_id: public_id.to_string(),
466 system_id: system_id.to_string(),
467 }));
468 mutr.append_children(document_id, &[id]);
469 }
470
471 fn get_template_contents(&self, target: &Self::Handle) -> Self::Handle {
472 #[cfg(feature = "shadow-dom")]
476 if let Some(shadow) = self.mutr().declarative_shadow_for_template(*target) {
477 return shadow;
478 }
479 self.mutr().template_contents(*target)
480 }
481
482 fn allow_declarative_shadow_roots(&self, _intended_parent: &Self::Handle) -> bool {
483 self.allow_declarative_shadow_roots && cfg!(feature = "shadow-dom")
487 }
488
489 fn attach_declarative_shadow(
490 &self,
491 location: &Self::Handle,
492 template: &Self::Handle,
493 attrs: &[html5ever::Attribute],
494 ) -> bool {
495 #[cfg(not(feature = "shadow-dom"))]
496 {
497 let _ = (location, template, attrs);
498 return false;
499 }
500 #[cfg(feature = "shadow-dom")]
501 {
502 let mut mode = None;
503 let mut clonable = false;
504 let mut serializable = false;
505 let mut delegates_focus = false;
506 for attr in attrs {
507 let name = attr.name.local.as_ref();
508 if name == "shadowrootmode" {
509 let value = attr.value.as_ref();
510 if value.eq_ignore_ascii_case("open") {
511 mode = Some(blitz_dom::node::ShadowRootMode::Open);
512 } else if value.eq_ignore_ascii_case("closed") {
513 mode = Some(blitz_dom::node::ShadowRootMode::Closed);
514 }
515 } else if name == "shadowrootclonable" {
516 clonable = true;
517 } else if name == "shadowrootserializable" {
518 serializable = true;
519 } else if name == "shadowrootdelegatesfocus" {
520 delegates_focus = true;
521 }
522 }
523 let Some(mode) = mode else {
524 return false;
525 };
526 self.mutr().try_attach_declarative_shadow(
527 *location,
528 *template,
529 mode,
530 clonable,
531 serializable,
532 delegates_focus,
533 )
534 }
535 }
536
537 fn pop(&self, node: &Self::Handle) {
538 let drop_template = {
544 let mutr = self.mutr();
545 mutr.doc.get_node(*node).is_some_and(|current| {
546 current.parent.is_none()
547 && current
548 .element_data()
549 .is_some_and(|element| element.name.local.as_ref() == "template")
550 })
551 };
552 if !drop_template {
553 return;
554 }
555 #[cfg(feature = "shadow-dom")]
556 self.mutr().forget_declarative_template(*node);
557 self.mutr().remove_and_drop_node(*node);
558 }
559
560 fn same_node(&self, x: &Self::Handle, y: &Self::Handle) -> bool {
561 x == y
562 }
563
564 fn set_quirks_mode(&self, mode: QuirksMode) {
565 self.quirks_mode.set(mode);
566 if self.parsing_document {
567 self.mutr().doc.set_document_quirks_mode(match mode {
568 QuirksMode::NoQuirks => 0,
569 QuirksMode::LimitedQuirks => 1,
570 QuirksMode::Quirks => 2,
571 });
572 }
573 }
574
575 fn add_attrs_if_missing(&self, target: &Self::Handle, attrs: Vec<html5ever::Attribute>) {
576 let attrs = attrs.into_iter().map(html5ever_to_blitz_attr).collect();
577 self.mutr().add_attrs_if_missing(*target, attrs);
578 }
579
580 fn remove_from_parent(&self, target: &Self::Handle) {
581 self.mutr().remove_node(*target);
582 }
583
584 fn reparent_children(&self, old_parent_id: &Self::Handle, new_parent_id: &Self::Handle) {
585 self.mutr()
586 .reparent_children(*old_parent_id, *new_parent_id);
587 }
588}
589
590#[test]
591fn parses_some_html() {
592 use blitz_dom::{BaseDocument, DocumentConfig};
593
594 let html = "<!DOCTYPE html><html><body><h1>hello world</h1></body></html>";
595 let mut doc = BaseDocument::new(DocumentConfig::default());
596 let mut mutr = doc.mutate();
597 let sink = DocumentHtmlParser::new(&mut mutr);
598
599 html5ever::parse_document(sink, Default::default())
600 .from_utf8()
601 .read_from(&mut html.as_bytes())
602 .unwrap();
603
604 drop(mutr);
605 doc.print_tree()
606
607 }