aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/servoparser/async_html.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/servoparser/async_html.rs')
-rw-r--r--components/script/dom/servoparser/async_html.rs891
1 files changed, 891 insertions, 0 deletions
diff --git a/components/script/dom/servoparser/async_html.rs b/components/script/dom/servoparser/async_html.rs
new file mode 100644
index 00000000000..8b0a156842d
--- /dev/null
+++ b/components/script/dom/servoparser/async_html.rs
@@ -0,0 +1,891 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#![allow(unrooted_must_root)]
+
+use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
+use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use crate::dom::bindings::inheritance::Castable;
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::comment::Comment;
+use crate::dom::document::Document;
+use crate::dom::documenttype::DocumentType;
+use crate::dom::element::{Element, ElementCreator};
+use crate::dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement};
+use crate::dom::htmlscriptelement::HTMLScriptElement;
+use crate::dom::htmltemplateelement::HTMLTemplateElement;
+use crate::dom::node::Node;
+use crate::dom::processinginstruction::ProcessingInstruction;
+use crate::dom::servoparser::{create_element_for_token, ElementAttribute, ParsingAlgorithm};
+use crate::dom::virtualmethods::vtable_for;
+use crossbeam_channel::{unbounded, Receiver, Sender};
+use html5ever::buffer_queue::BufferQueue;
+use html5ever::tendril::fmt::UTF8;
+use html5ever::tendril::{SendTendril, StrTendril, Tendril};
+use html5ever::tokenizer::{Tokenizer as HtmlTokenizer, TokenizerOpts, TokenizerResult};
+use html5ever::tree_builder::{
+ ElementFlags, NextParserState, NodeOrText as HtmlNodeOrText, QuirksMode, TreeSink,
+};
+use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts};
+use html5ever::{Attribute as HtmlAttribute, ExpandedName, QualName};
+use servo_url::ServoUrl;
+use std::borrow::Cow;
+use std::cell::Cell;
+use std::collections::vec_deque::VecDeque;
+use std::collections::HashMap;
+use std::thread;
+use style::context::QuirksMode as ServoQuirksMode;
+
+type ParseNodeId = usize;
+
+#[derive(Clone, JSTraceable, MallocSizeOf)]
+pub struct ParseNode {
+ id: ParseNodeId,
+ qual_name: Option<QualName>,
+}
+
+#[derive(JSTraceable, MallocSizeOf)]
+enum NodeOrText {
+ Node(ParseNode),
+ Text(String),
+}
+
+#[derive(JSTraceable, MallocSizeOf)]
+struct Attribute {
+ name: QualName,
+ value: String,
+}
+
+#[derive(JSTraceable, MallocSizeOf)]
+enum ParseOperation {
+ GetTemplateContents {
+ target: ParseNodeId,
+ contents: ParseNodeId,
+ },
+
+ CreateElement {
+ node: ParseNodeId,
+ name: QualName,
+ attrs: Vec<Attribute>,
+ current_line: u64,
+ },
+
+ CreateComment {
+ text: String,
+ node: ParseNodeId,
+ },
+ AppendBeforeSibling {
+ sibling: ParseNodeId,
+ node: NodeOrText,
+ },
+ AppendBasedOnParentNode {
+ element: ParseNodeId,
+ prev_element: ParseNodeId,
+ node: NodeOrText,
+ },
+ Append {
+ parent: ParseNodeId,
+ node: NodeOrText,
+ },
+
+ AppendDoctypeToDocument {
+ name: String,
+ public_id: String,
+ system_id: String,
+ },
+
+ AddAttrsIfMissing {
+ target: ParseNodeId,
+ attrs: Vec<Attribute>,
+ },
+ RemoveFromParent {
+ target: ParseNodeId,
+ },
+ MarkScriptAlreadyStarted {
+ node: ParseNodeId,
+ },
+ ReparentChildren {
+ parent: ParseNodeId,
+ new_parent: ParseNodeId,
+ },
+
+ AssociateWithForm {
+ target: ParseNodeId,
+ form: ParseNodeId,
+ element: ParseNodeId,
+ prev_element: Option<ParseNodeId>,
+ },
+
+ CreatePI {
+ node: ParseNodeId,
+ target: String,
+ data: String,
+ },
+
+ Pop {
+ node: ParseNodeId,
+ },
+
+ SetQuirksMode {
+ #[ignore_malloc_size_of = "Defined in style"]
+ mode: ServoQuirksMode,
+ },
+}
+
+#[derive(MallocSizeOf)]
+enum ToTokenizerMsg {
+ // From HtmlTokenizer
+ TokenizerResultDone {
+ #[ignore_malloc_size_of = "Defined in html5ever"]
+ updated_input: VecDeque<SendTendril<UTF8>>,
+ },
+ TokenizerResultScript {
+ script: ParseNode,
+ #[ignore_malloc_size_of = "Defined in html5ever"]
+ updated_input: VecDeque<SendTendril<UTF8>>,
+ },
+ End, // Sent to Tokenizer to signify HtmlTokenizer's end method has returned
+
+ // From Sink
+ ProcessOperation(ParseOperation),
+}
+
+#[derive(MallocSizeOf)]
+enum ToHtmlTokenizerMsg {
+ Feed {
+ #[ignore_malloc_size_of = "Defined in html5ever"]
+ input: VecDeque<SendTendril<UTF8>>,
+ },
+ End,
+ SetPlainTextState,
+}
+
+fn create_buffer_queue(mut buffers: VecDeque<SendTendril<UTF8>>) -> BufferQueue {
+ let mut buffer_queue = BufferQueue::new();
+ while let Some(st) = buffers.pop_front() {
+ buffer_queue.push_back(StrTendril::from(st));
+ }
+ buffer_queue
+}
+
+// The async HTML Tokenizer consists of two separate types working together: the Tokenizer
+// (defined below), which lives on the main thread, and the HtmlTokenizer, defined in html5ever, which
+// lives on the parser thread.
+// Steps:
+// 1. A call to Tokenizer::new will spin up a new parser thread, creating an HtmlTokenizer instance,
+// which starts listening for messages from Tokenizer.
+// 2. Upon receiving an input from ServoParser, the Tokenizer forwards it to HtmlTokenizer, where it starts
+// creating the necessary tree actions based on the input.
+// 3. HtmlTokenizer sends these tree actions to the Tokenizer as soon as it creates them. The Tokenizer
+// then executes the received actions.
+//
+// _____________ _______________
+// | | | |
+// | | | |
+// | | ToHtmlTokenizerMsg | |
+// | |------------------------>| HtmlTokenizer |
+// | | | |
+// | Tokenizer | ToTokenizerMsg | |
+// | |<------------------------| ________ |
+// | | | | | |
+// | | ToTokenizerMsg | | Sink | |
+// | |<------------------------|---| | |
+// | | | |________| |
+// |_____________| |_______________|
+//
+#[derive(JSTraceable, MallocSizeOf)]
+#[unrooted_must_root_lint::must_root]
+pub struct Tokenizer {
+ document: Dom<Document>,
+ #[ignore_malloc_size_of = "Defined in std"]
+ receiver: Receiver<ToTokenizerMsg>,
+ #[ignore_malloc_size_of = "Defined in std"]
+ html_tokenizer_sender: Sender<ToHtmlTokenizerMsg>,
+ #[ignore_malloc_size_of = "Defined in std"]
+ nodes: HashMap<ParseNodeId, Dom<Node>>,
+ url: ServoUrl,
+ parsing_algorithm: ParsingAlgorithm,
+}
+
+impl Tokenizer {
+ pub fn new(
+ document: &Document,
+ url: ServoUrl,
+ fragment_context: Option<super::FragmentContext>,
+ ) -> Self {
+ // Messages from the Tokenizer (main thread) to HtmlTokenizer (parser thread)
+ let (to_html_tokenizer_sender, html_tokenizer_receiver) = unbounded();
+ // Messages from HtmlTokenizer and Sink (parser thread) to Tokenizer (main thread)
+ let (to_tokenizer_sender, tokenizer_receiver) = unbounded();
+
+ let algorithm = match fragment_context {
+ Some(_) => ParsingAlgorithm::Fragment,
+ None => ParsingAlgorithm::Normal,
+ };
+
+ let mut tokenizer = Tokenizer {
+ document: Dom::from_ref(document),
+ receiver: tokenizer_receiver,
+ html_tokenizer_sender: to_html_tokenizer_sender,
+ nodes: HashMap::new(),
+ url: url,
+ parsing_algorithm: algorithm,
+ };
+ tokenizer.insert_node(0, Dom::from_ref(document.upcast()));
+
+ let mut sink = Sink::new(to_tokenizer_sender.clone());
+ let mut ctxt_parse_node = None;
+ let mut form_parse_node = None;
+ let mut fragment_context_is_some = false;
+ if let Some(fc) = fragment_context {
+ let node = sink.new_parse_node();
+ tokenizer.insert_node(node.id, Dom::from_ref(fc.context_elem));
+ ctxt_parse_node = Some(node);
+
+ form_parse_node = fc.form_elem.map(|form_elem| {
+ let node = sink.new_parse_node();
+ tokenizer.insert_node(node.id, Dom::from_ref(form_elem));
+ node
+ });
+ fragment_context_is_some = true;
+ };
+
+ // Create new thread for HtmlTokenizer. This is where parser actions
+ // will be generated from the input provided. These parser actions are then passed
+ // onto the main thread to be executed.
+ thread::Builder::new()
+ .name(String::from("HTML Parser"))
+ .spawn(move || {
+ run(
+ sink,
+ fragment_context_is_some,
+ ctxt_parse_node,
+ form_parse_node,
+ to_tokenizer_sender,
+ html_tokenizer_receiver,
+ );
+ })
+ .expect("HTML Parser thread spawning failed");
+
+ tokenizer
+ }
+
+ pub fn feed(&mut self, input: &mut BufferQueue) -> Result<(), DomRoot<HTMLScriptElement>> {
+ let mut send_tendrils = VecDeque::new();
+ while let Some(str) = input.pop_front() {
+ send_tendrils.push_back(SendTendril::from(str));
+ }
+
+ // Send message to parser thread, asking it to start reading from the input.
+ // Parser operation messages will be sent to main thread as they are evaluated.
+ self.html_tokenizer_sender
+ .send(ToHtmlTokenizerMsg::Feed {
+ input: send_tendrils,
+ })
+ .unwrap();
+
+ loop {
+ match self
+ .receiver
+ .recv()
+ .expect("Unexpected channel panic in main thread.")
+ {
+ ToTokenizerMsg::ProcessOperation(parse_op) => self.process_operation(parse_op),
+ ToTokenizerMsg::TokenizerResultDone { updated_input } => {
+ let buffer_queue = create_buffer_queue(updated_input);
+ *input = buffer_queue;
+ return Ok(());
+ },
+ ToTokenizerMsg::TokenizerResultScript {
+ script,
+ updated_input,
+ } => {
+ let buffer_queue = create_buffer_queue(updated_input);
+ *input = buffer_queue;
+ let script = self.get_node(&script.id);
+ return Err(DomRoot::from_ref(script.downcast().unwrap()));
+ },
+ ToTokenizerMsg::End => unreachable!(),
+ };
+ }
+ }
+
+ pub fn end(&mut self) {
+ self.html_tokenizer_sender
+ .send(ToHtmlTokenizerMsg::End)
+ .unwrap();
+ loop {
+ match self
+ .receiver
+ .recv()
+ .expect("Unexpected channel panic in main thread.")
+ {
+ ToTokenizerMsg::ProcessOperation(parse_op) => self.process_operation(parse_op),
+ ToTokenizerMsg::End => return,
+ _ => unreachable!(),
+ };
+ }
+ }
+
+ pub fn url(&self) -> &ServoUrl {
+ &self.url
+ }
+
+ pub fn set_plaintext_state(&mut self) {
+ self.html_tokenizer_sender
+ .send(ToHtmlTokenizerMsg::SetPlainTextState)
+ .unwrap();
+ }
+
+ fn insert_node(&mut self, id: ParseNodeId, node: Dom<Node>) {
+ assert!(self.nodes.insert(id, node).is_none());
+ }
+
+ fn get_node<'a>(&'a self, id: &ParseNodeId) -> &'a Dom<Node> {
+ self.nodes.get(id).expect("Node not found!")
+ }
+
+ fn append_before_sibling(&mut self, sibling: ParseNodeId, node: NodeOrText) {
+ let node = match node {
+ NodeOrText::Node(n) => {
+ HtmlNodeOrText::AppendNode(Dom::from_ref(&**self.get_node(&n.id)))
+ },
+ NodeOrText::Text(text) => HtmlNodeOrText::AppendText(Tendril::from(text)),
+ };
+ let sibling = &**self.get_node(&sibling);
+ let parent = &*sibling
+ .GetParentNode()
+ .expect("append_before_sibling called on node without parent");
+
+ super::insert(parent, Some(sibling), node, self.parsing_algorithm);
+ }
+
+ fn append(&mut self, parent: ParseNodeId, node: NodeOrText) {
+ let node = match node {
+ NodeOrText::Node(n) => {
+ HtmlNodeOrText::AppendNode(Dom::from_ref(&**self.get_node(&n.id)))
+ },
+ NodeOrText::Text(text) => HtmlNodeOrText::AppendText(Tendril::from(text)),
+ };
+
+ let parent = &**self.get_node(&parent);
+ super::insert(parent, None, node, self.parsing_algorithm);
+ }
+
+ fn has_parent_node(&self, node: ParseNodeId) -> bool {
+ self.get_node(&node).GetParentNode().is_some()
+ }
+
+ fn same_tree(&self, x: ParseNodeId, y: ParseNodeId) -> bool {
+ let x = self.get_node(&x);
+ let y = self.get_node(&y);
+
+ let x = x.downcast::<Element>().expect("Element node expected");
+ let y = y.downcast::<Element>().expect("Element node expected");
+ x.is_in_same_home_subtree(y)
+ }
+
+ fn process_operation(&mut self, op: ParseOperation) {
+ let document = DomRoot::from_ref(&**self.get_node(&0));
+ let document = document
+ .downcast::<Document>()
+ .expect("Document node should be downcasted!");
+ match op {
+ ParseOperation::GetTemplateContents { target, contents } => {
+ let target = DomRoot::from_ref(&**self.get_node(&target));
+ let template = target
+ .downcast::<HTMLTemplateElement>()
+ .expect("Tried to extract contents from non-template element while parsing");
+ self.insert_node(contents, Dom::from_ref(template.Content().upcast()));
+ },
+ ParseOperation::CreateElement {
+ node,
+ name,
+ attrs,
+ current_line,
+ } => {
+ let attrs = attrs
+ .into_iter()
+ .map(|attr| ElementAttribute::new(attr.name, DOMString::from(attr.value)))
+ .collect();
+ let element = create_element_for_token(
+ name,
+ attrs,
+ &*self.document,
+ ElementCreator::ParserCreated(current_line),
+ ParsingAlgorithm::Normal,
+ );
+ self.insert_node(node, Dom::from_ref(element.upcast()));
+ },
+ ParseOperation::CreateComment { text, node } => {
+ let comment = Comment::new(DOMString::from(text), document);
+ self.insert_node(node, Dom::from_ref(&comment.upcast()));
+ },
+ ParseOperation::AppendBeforeSibling { sibling, node } => {
+ self.append_before_sibling(sibling, node);
+ },
+ ParseOperation::Append { parent, node } => {
+ self.append(parent, node);
+ },
+ ParseOperation::AppendBasedOnParentNode {
+ element,
+ prev_element,
+ node,
+ } => {
+ if self.has_parent_node(element) {
+ self.append_before_sibling(element, node);
+ } else {
+ self.append(prev_element, node);
+ }
+ },
+ ParseOperation::AppendDoctypeToDocument {
+ name,
+ public_id,
+ system_id,
+ } => {
+ let doctype = DocumentType::new(
+ DOMString::from(String::from(name)),
+ Some(DOMString::from(public_id)),
+ Some(DOMString::from(system_id)),
+ document,
+ );
+
+ document
+ .upcast::<Node>()
+ .AppendChild(doctype.upcast())
+ .expect("Appending failed");
+ },
+ ParseOperation::AddAttrsIfMissing { target, attrs } => {
+ let elem = self
+ .get_node(&target)
+ .downcast::<Element>()
+ .expect("tried to set attrs on non-Element in HTML parsing");
+ for attr in attrs {
+ elem.set_attribute_from_parser(attr.name, DOMString::from(attr.value), None);
+ }
+ },
+ ParseOperation::RemoveFromParent { target } => {
+ if let Some(ref parent) = self.get_node(&target).GetParentNode() {
+ parent.RemoveChild(&**self.get_node(&target)).unwrap();
+ }
+ },
+ ParseOperation::MarkScriptAlreadyStarted { node } => {
+ let script = self.get_node(&node).downcast::<HTMLScriptElement>();
+ script.map(|script| script.set_already_started(true));
+ },
+ ParseOperation::ReparentChildren { parent, new_parent } => {
+ let parent = self.get_node(&parent);
+ let new_parent = self.get_node(&new_parent);
+ while let Some(child) = parent.GetFirstChild() {
+ new_parent.AppendChild(&child).unwrap();
+ }
+ },
+ ParseOperation::AssociateWithForm {
+ target,
+ form,
+ element,
+ prev_element,
+ } => {
+ let tree_node = prev_element.map_or(element, |prev| {
+ if self.has_parent_node(element) {
+ element
+ } else {
+ prev
+ }
+ });
+
+ if !self.same_tree(tree_node, form) {
+ return;
+ }
+ let form = self.get_node(&form);
+ let form = DomRoot::downcast::<HTMLFormElement>(DomRoot::from_ref(&**form))
+ .expect("Owner must be a form element");
+
+ let node = self.get_node(&target);
+ let elem = node.downcast::<Element>();
+ let control = elem.and_then(|e| e.as_maybe_form_control());
+
+ if let Some(control) = control {
+ control.set_form_owner_from_parser(&form);
+ } else {
+ // TODO remove this code when keygen is implemented.
+ assert_eq!(
+ node.NodeName(),
+ "KEYGEN",
+ "Unknown form-associatable element"
+ );
+ }
+ },
+ ParseOperation::Pop { node } => {
+ vtable_for(self.get_node(&node)).pop();
+ },
+ ParseOperation::CreatePI { node, target, data } => {
+ let pi = ProcessingInstruction::new(
+ DOMString::from(target),
+ DOMString::from(data),
+ document,
+ );
+ self.insert_node(node, Dom::from_ref(pi.upcast()));
+ },
+ ParseOperation::SetQuirksMode { mode } => {
+ document.set_quirks_mode(mode);
+ },
+ }
+ }
+}
+
+fn run(
+ sink: Sink,
+ fragment_context_is_some: bool,
+ ctxt_parse_node: Option<ParseNode>,
+ form_parse_node: Option<ParseNode>,
+ sender: Sender<ToTokenizerMsg>,
+ receiver: Receiver<ToHtmlTokenizerMsg>,
+) {
+ let options = TreeBuilderOpts {
+ ignore_missing_rules: true,
+ ..Default::default()
+ };
+
+ let mut html_tokenizer = if fragment_context_is_some {
+ let tb =
+ TreeBuilder::new_for_fragment(sink, ctxt_parse_node.unwrap(), form_parse_node, options);
+
+ let tok_options = TokenizerOpts {
+ initial_state: Some(tb.tokenizer_state_for_context_elem()),
+ ..Default::default()
+ };
+
+ HtmlTokenizer::new(tb, tok_options)
+ } else {
+ HtmlTokenizer::new(TreeBuilder::new(sink, options), Default::default())
+ };
+
+ loop {
+ match receiver
+ .recv()
+ .expect("Unexpected channel panic in html parser thread")
+ {
+ ToHtmlTokenizerMsg::Feed { input } => {
+ let mut input = create_buffer_queue(input);
+ let res = html_tokenizer.feed(&mut input);
+
+ // Gather changes to 'input' and place them in 'updated_input',
+ // which will be sent to the main thread to update feed method's 'input'
+ let mut updated_input = VecDeque::new();
+ while let Some(st) = input.pop_front() {
+ updated_input.push_back(SendTendril::from(st));
+ }
+
+ let res = match res {
+ TokenizerResult::Done => ToTokenizerMsg::TokenizerResultDone { updated_input },
+ TokenizerResult::Script(script) => ToTokenizerMsg::TokenizerResultScript {
+ script,
+ updated_input,
+ },
+ };
+ sender.send(res).unwrap();
+ },
+ ToHtmlTokenizerMsg::End => {
+ html_tokenizer.end();
+ sender.send(ToTokenizerMsg::End).unwrap();
+ break;
+ },
+ ToHtmlTokenizerMsg::SetPlainTextState => html_tokenizer.set_plaintext_state(),
+ };
+ }
+}
+
+#[derive(Default, JSTraceable, MallocSizeOf)]
+struct ParseNodeData {
+ contents: Option<ParseNode>,
+ is_integration_point: bool,
+}
+
+pub struct Sink {
+ current_line: u64,
+ parse_node_data: HashMap<ParseNodeId, ParseNodeData>,
+ next_parse_node_id: Cell<ParseNodeId>,
+ document_node: ParseNode,
+ sender: Sender<ToTokenizerMsg>,
+}
+
+impl Sink {
+ fn new(sender: Sender<ToTokenizerMsg>) -> Sink {
+ let mut sink = Sink {
+ current_line: 1,
+ parse_node_data: HashMap::new(),
+ next_parse_node_id: Cell::new(1),
+ document_node: ParseNode {
+ id: 0,
+ qual_name: None,
+ },
+ sender: sender,
+ };
+ let data = ParseNodeData::default();
+ sink.insert_parse_node_data(0, data);
+ sink
+ }
+
+ fn new_parse_node(&mut self) -> ParseNode {
+ let id = self.next_parse_node_id.get();
+ let data = ParseNodeData::default();
+ self.insert_parse_node_data(id, data);
+ self.next_parse_node_id.set(id + 1);
+ ParseNode {
+ id: id,
+ qual_name: None,
+ }
+ }
+
+ fn send_op(&self, op: ParseOperation) {
+ self.sender
+ .send(ToTokenizerMsg::ProcessOperation(op))
+ .unwrap();
+ }
+
+ fn insert_parse_node_data(&mut self, id: ParseNodeId, data: ParseNodeData) {
+ assert!(self.parse_node_data.insert(id, data).is_none());
+ }
+
+ fn get_parse_node_data<'a>(&'a self, id: &'a ParseNodeId) -> &'a ParseNodeData {
+ self.parse_node_data
+ .get(id)
+ .expect("Parse Node data not found!")
+ }
+
+ fn get_parse_node_data_mut<'a>(&'a mut self, id: &'a ParseNodeId) -> &'a mut ParseNodeData {
+ self.parse_node_data
+ .get_mut(id)
+ .expect("Parse Node data not found!")
+ }
+}
+
+#[allow(unrooted_must_root)]
+impl TreeSink for Sink {
+ type Output = Self;
+ fn finish(self) -> Self {
+ self
+ }
+
+ type Handle = ParseNode;
+
+ fn get_document(&mut self) -> Self::Handle {
+ self.document_node.clone()
+ }
+
+ fn get_template_contents(&mut self, target: &Self::Handle) -> Self::Handle {
+ if let Some(ref contents) = self.get_parse_node_data(&target.id).contents {
+ return contents.clone();
+ }
+ let node = self.new_parse_node();
+ {
+ let data = self.get_parse_node_data_mut(&target.id);
+ data.contents = Some(node.clone());
+ }
+ self.send_op(ParseOperation::GetTemplateContents {
+ target: target.id,
+ contents: node.id,
+ });
+ node
+ }
+
+ fn same_node(&self, x: &Self::Handle, y: &Self::Handle) -> bool {
+ x.id == y.id
+ }
+
+ fn elem_name<'a>(&self, target: &'a Self::Handle) -> ExpandedName<'a> {
+ target
+ .qual_name
+ .as_ref()
+ .expect("Expected qual name of node!")
+ .expanded()
+ }
+
+ fn create_element(
+ &mut self,
+ name: QualName,
+ html_attrs: Vec<HtmlAttribute>,
+ _flags: ElementFlags,
+ ) -> Self::Handle {
+ let mut node = self.new_parse_node();
+ node.qual_name = Some(name.clone());
+ {
+ let node_data = self.get_parse_node_data_mut(&node.id);
+ node_data.is_integration_point = html_attrs.iter().any(|attr| {
+ let attr_value = &String::from(attr.value.clone());
+ (attr.name.local == local_name!("encoding") && attr.name.ns == ns!()) &&
+ (attr_value.eq_ignore_ascii_case("text/html") ||
+ attr_value.eq_ignore_ascii_case("application/xhtml+xml"))
+ });
+ }
+ let attrs = html_attrs
+ .into_iter()
+ .map(|attr| Attribute {
+ name: attr.name,
+ value: String::from(attr.value),
+ })
+ .collect();
+
+ self.send_op(ParseOperation::CreateElement {
+ node: node.id,
+ name,
+ attrs,
+ current_line: self.current_line,
+ });
+ node
+ }
+
+ fn create_comment(&mut self, text: StrTendril) -> Self::Handle {
+ let node = self.new_parse_node();
+ self.send_op(ParseOperation::CreateComment {
+ text: String::from(text),
+ node: node.id,
+ });
+ node
+ }
+
+ fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> ParseNode {
+ let node = self.new_parse_node();
+ self.send_op(ParseOperation::CreatePI {
+ node: node.id,
+ target: String::from(target),
+ data: String::from(data),
+ });
+ node
+ }
+
+ fn associate_with_form(
+ &mut self,
+ target: &Self::Handle,
+ form: &Self::Handle,
+ nodes: (&Self::Handle, Option<&Self::Handle>),
+ ) {
+ let (element, prev_element) = nodes;
+ self.send_op(ParseOperation::AssociateWithForm {
+ target: target.id,
+ form: form.id,
+ element: element.id,
+ prev_element: prev_element.map(|p| p.id),
+ });
+ }
+
+ fn append_before_sibling(
+ &mut self,
+ sibling: &Self::Handle,
+ new_node: HtmlNodeOrText<Self::Handle>,
+ ) {
+ let new_node = match new_node {
+ HtmlNodeOrText::AppendNode(node) => NodeOrText::Node(node),
+ HtmlNodeOrText::AppendText(text) => NodeOrText::Text(String::from(text)),
+ };
+ self.send_op(ParseOperation::AppendBeforeSibling {
+ sibling: sibling.id,
+ node: new_node,
+ });
+ }
+
+ fn append_based_on_parent_node(
+ &mut self,
+ elem: &Self::Handle,
+ prev_elem: &Self::Handle,
+ child: HtmlNodeOrText<Self::Handle>,
+ ) {
+ let child = match child {
+ HtmlNodeOrText::AppendNode(node) => NodeOrText::Node(node),
+ HtmlNodeOrText::AppendText(text) => NodeOrText::Text(String::from(text)),
+ };
+ self.send_op(ParseOperation::AppendBasedOnParentNode {
+ element: elem.id,
+ prev_element: prev_elem.id,
+ node: child,
+ });
+ }
+
+ fn parse_error(&mut self, msg: Cow<'static, str>) {
+ debug!("Parse error: {}", msg);
+ }
+
+ fn set_quirks_mode(&mut self, mode: QuirksMode) {
+ let mode = match mode {
+ QuirksMode::Quirks => ServoQuirksMode::Quirks,
+ QuirksMode::LimitedQuirks => ServoQuirksMode::LimitedQuirks,
+ QuirksMode::NoQuirks => ServoQuirksMode::NoQuirks,
+ };
+ self.send_op(ParseOperation::SetQuirksMode { mode });
+ }
+
+ fn append(&mut self, parent: &Self::Handle, child: HtmlNodeOrText<Self::Handle>) {
+ let child = match child {
+ HtmlNodeOrText::AppendNode(node) => NodeOrText::Node(node),
+ HtmlNodeOrText::AppendText(text) => NodeOrText::Text(String::from(text)),
+ };
+ self.send_op(ParseOperation::Append {
+ parent: parent.id,
+ node: child,
+ });
+ }
+
+ fn append_doctype_to_document(
+ &mut self,
+ name: StrTendril,
+ public_id: StrTendril,
+ system_id: StrTendril,
+ ) {
+ self.send_op(ParseOperation::AppendDoctypeToDocument {
+ name: String::from(name),
+ public_id: String::from(public_id),
+ system_id: String::from(system_id),
+ });
+ }
+
+ fn add_attrs_if_missing(&mut self, target: &Self::Handle, html_attrs: Vec<HtmlAttribute>) {
+ let attrs = html_attrs
+ .into_iter()
+ .map(|attr| Attribute {
+ name: attr.name,
+ value: String::from(attr.value),
+ })
+ .collect();
+ self.send_op(ParseOperation::AddAttrsIfMissing {
+ target: target.id,
+ attrs,
+ });
+ }
+
+ fn remove_from_parent(&mut self, target: &Self::Handle) {
+ self.send_op(ParseOperation::RemoveFromParent { target: target.id });
+ }
+
+ fn mark_script_already_started(&mut self, node: &Self::Handle) {
+ self.send_op(ParseOperation::MarkScriptAlreadyStarted { node: node.id });
+ }
+
+ fn complete_script(&mut self, _: &Self::Handle) -> NextParserState {
+ panic!("complete_script should not be called here!");
+ }
+
+ fn reparent_children(&mut self, parent: &Self::Handle, new_parent: &Self::Handle) {
+ self.send_op(ParseOperation::ReparentChildren {
+ parent: parent.id,
+ new_parent: new_parent.id,
+ });
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#html-integration-point>
+ /// Specifically, the <annotation-xml> cases.
+ fn is_mathml_annotation_xml_integration_point(&self, handle: &Self::Handle) -> bool {
+ let node_data = self.get_parse_node_data(&handle.id);
+ node_data.is_integration_point
+ }
+
+ fn set_current_line(&mut self, line_number: u64) {
+ self.current_line = line_number;
+ }
+
+ fn pop(&mut self, node: &Self::Handle) {
+ self.send_op(ParseOperation::Pop { node: node.id });
+ }
+}