servo: Merge #13675 - Clean up the parsers into a single interface (from nox:servoparser); r=Ms2ger
authorAnthony Ramine <n.oxyde@gmail.com>
Tue, 11 Oct 2016 10:16:23 -0500
changeset 339884 1fbb397120eb5a59693f454216c3f915bc93aa36
parent 339883 5704ec2d788109a0495f0b4f4345dc02d678f4fe
child 339885 947ebc8b43fc8b3c435e02f41042894e040fafc9
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMs2ger
servo: Merge #13675 - Clean up the parsers into a single interface (from nox:servoparser); r=Ms2ger Source-Repo: https://github.com/servo/servo Source-Revision: 928e5ad1e5ca4344e69cd8029749ecb623eabd9f
servo/components/profile/time.rs
servo/components/profile_traits/time.rs
servo/components/script/dom/bindings/codegen/CodegenRust.py
servo/components/script/dom/document.rs
servo/components/script/dom/domparser.rs
servo/components/script/dom/mod.rs
servo/components/script/dom/node.rs
servo/components/script/dom/servohtmlparser.rs
servo/components/script/dom/servoparser/html.rs
servo/components/script/dom/servoparser/mod.rs
servo/components/script/dom/servoparser/xml.rs
servo/components/script/dom/servoxmlparser.rs
servo/components/script/dom/webidls/ServoHTMLParser.webidl
servo/components/script/dom/webidls/ServoParser.webidl
servo/components/script/dom/webidls/ServoXMLParser.webidl
servo/components/script/dom/xmlhttprequest.rs
servo/components/script/lib.rs
servo/components/script/parse/html.rs
servo/components/script/parse/mod.rs
servo/components/script/parse/xml.rs
servo/components/script/script_thread.rs
--- a/servo/components/profile/time.rs
+++ b/servo/components/profile/time.rs
@@ -133,16 +133,17 @@ impl Formattable for ProfilerCategory {
             ProfilerCategory::ScriptDocumentEvent => "Script Document Event",
             ProfilerCategory::ScriptDomEvent => "Script Dom Event",
             ProfilerCategory::ScriptEvaluate => "Script JS Evaluate",
             ProfilerCategory::ScriptFileRead => "Script File Read",
             ProfilerCategory::ScriptImageCacheMsg => "Script Image Cache Msg",
             ProfilerCategory::ScriptInputEvent => "Script Input Event",
             ProfilerCategory::ScriptNetworkEvent => "Script Network Event",
             ProfilerCategory::ScriptParseHTML => "Script Parse HTML",
+            ProfilerCategory::ScriptParseXML => "Script Parse XML",
             ProfilerCategory::ScriptPlannedNavigation => "Script Planned Navigation",
             ProfilerCategory::ScriptResize => "Script Resize",
             ProfilerCategory::ScriptEvent => "Script Event",
             ProfilerCategory::ScriptUpdateReplacedElement => "Script Update Replaced Element",
             ProfilerCategory::ScriptSetScrollState => "Script Set Scroll State",
             ProfilerCategory::ScriptSetViewport => "Script Set Viewport",
             ProfilerCategory::ScriptTimerEvent => "Script Timer Event",
             ProfilerCategory::ScriptStylesheetLoad => "Script Stylesheet Load",
--- a/servo/components/profile_traits/time.rs
+++ b/servo/components/profile_traits/time.rs
@@ -78,16 +78,17 @@ pub enum ProfilerCategory {
     ScriptSetScrollState = 0x6e,
     ScriptSetViewport = 0x6f,
     ScriptTimerEvent = 0x70,
     ScriptStylesheetLoad = 0x71,
     ScriptUpdateReplacedElement = 0x72,
     ScriptWebSocketEvent = 0x73,
     ScriptWorkerEvent = 0x74,
     ScriptServiceWorkerEvent = 0x75,
+    ScriptParseXML = 0x76,
     ApplicationHeartbeat = 0x90,
 }
 
 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
 pub enum TimerMetadataFrameType {
     RootWindow,
     IFrame,
 }
--- a/servo/components/script/dom/bindings/codegen/CodegenRust.py
+++ b/servo/components/script/dom/bindings/codegen/CodegenRust.py
@@ -2743,26 +2743,27 @@ create_callback_interface_object(cx, glo
 assert!(!interface.is_null());
 assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null());
 (*cache)[PrototypeList::Constructor::%(id)s as usize] = interface.get();
 <*mut JSObject>::post_barrier((*cache).as_mut_ptr().offset(PrototypeList::Constructor::%(id)s as isize),
                               ptr::null_mut(),
                               interface.get());
 """ % {"id": name, "name": str_to_const_array(name)})
 
-        if len(self.descriptor.prototypeChain) == 1:
+        parentName = self.descriptor.getParentName()
+        if not parentName:
             if self.descriptor.interface.getExtendedAttribute("ExceptionClass"):
                 getPrototypeProto = "prototype_proto.set(JS_GetErrorPrototype(cx))"
             elif self.descriptor.interface.isIteratorInterface():
                 getPrototypeProto = "prototype_proto.set(JS_GetIteratorPrototype(cx))"
             else:
                 getPrototypeProto = "prototype_proto.set(JS_GetObjectPrototype(cx, global))"
         else:
             getPrototypeProto = ("%s::GetProtoObject(cx, global, prototype_proto.handle_mut())" %
-                                 toBindingNamespace(self.descriptor.getParentName()))
+                                 toBindingNamespace(parentName))
 
         code = [CGGeneric("""\
 rooted!(in(cx) let mut prototype_proto = ptr::null_mut());
 %s;
 assert!(!prototype_proto.is_null());""" % getPrototypeProto)]
 
         properties = {
             "id": name,
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -71,16 +71,17 @@ use dom::mouseevent::MouseEvent;
 use dom::node::{self, CloneChildrenFlag, Node, NodeDamage, window_from_node};
 use dom::nodeiterator::NodeIterator;
 use dom::nodelist::NodeList;
 use dom::pagetransitionevent::PageTransitionEvent;
 use dom::popstateevent::PopStateEvent;
 use dom::processinginstruction::ProcessingInstruction;
 use dom::progressevent::ProgressEvent;
 use dom::range::Range;
+use dom::servoparser::ServoParser;
 use dom::storageevent::StorageEvent;
 use dom::stylesheetlist::StyleSheetList;
 use dom::text::Text;
 use dom::touch::Touch;
 use dom::touchevent::TouchEvent;
 use dom::touchlist::TouchList;
 use dom::treewalker::TreeWalker;
 use dom::uievent::UIEvent;
@@ -98,17 +99,16 @@ use msg::constellation_msg::{Key, KeyMod
 use msg::constellation_msg::{PipelineId, ReferrerPolicy};
 use net_traits::{AsyncResponseTarget, FetchResponseMsg, IpcSend};
 use net_traits::CookieSource::NonHTTP;
 use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl};
 use net_traits::request::RequestInit;
 use net_traits::response::HttpsState;
 use num_traits::ToPrimitive;
 use origin::Origin;
-use parse::{MutNullableParserField, ParserRef, ParserRoot};
 use script_layout_interface::message::{Msg, ReflowQueryType};
 use script_thread::{MainThreadScriptMsg, Runnable};
 use script_traits::{AnimationState, CompositorEvent, MouseButton, MouseEventType, MozBrowserEvent};
 use script_traits::{ScriptMsg as ConstellationMsg, TouchpadPressurePhase};
 use script_traits::{TouchEventType, TouchId};
 use script_traits::UntrustedNodeAddress;
 use std::ascii::AsciiExt;
 use std::borrow::ToOwned;
@@ -221,17 +221,17 @@ pub struct Document {
     /// Whether we're in the process of running animation callbacks.
     ///
     /// Tracking this is not necessary for correctness. Instead, it is an optimization to avoid
     /// sending needless `ChangeRunningAnimationsState` messages to the compositor.
     running_animation_callbacks: Cell<bool>,
     /// Tracks all outstanding loads related to this document.
     loader: DOMRefCell<DocumentLoader>,
     /// The current active HTML parser, to allow resuming after interruptions.
-    current_parser: MutNullableParserField,
+    current_parser: MutNullableHeap<JS<ServoParser>>,
     /// When we should kick off a reflow. This happens during parsing.
     reflow_timeout: Cell<Option<u64>>,
     /// The cached first `base` element with an `href` attribute.
     base_element: MutNullableHeap<JS<HTMLBaseElement>>,
     /// This field is set to the document itself for inert documents.
     /// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
     appropriate_template_contents_owner_document: MutNullableHeap<JS<Document>>,
     /// For each element that has had a state or attribute change since the last restyle,
@@ -1622,21 +1622,21 @@ impl Document {
 
     pub fn notify_constellation_load(&self) {
         let global_scope = self.window.upcast::<GlobalScope>();
         let pipeline_id = global_scope.pipeline_id();
         let load_event = ConstellationMsg::LoadComplete(pipeline_id);
         global_scope.constellation_chan().send(load_event).unwrap();
     }
 
-    pub fn set_current_parser(&self, script: Option<ParserRef>) {
+    pub fn set_current_parser(&self, script: Option<&ServoParser>) {
         self.current_parser.set(script);
     }
 
-    pub fn get_current_parser(&self) -> Option<ParserRoot> {
+    pub fn get_current_parser(&self) -> Option<Root<ServoParser>> {
         self.current_parser.get()
     }
 
     /// Find an iframe element in the document.
     pub fn find_iframe(&self, pipeline: PipelineId) -> Option<Root<HTMLIFrameElement>> {
         self.upcast::<Node>()
             .traverse_preorder()
             .filter_map(Root::downcast::<HTMLIFrameElement>)
--- a/servo/components/script/dom/domparser.rs
+++ b/servo/components/script/dom/domparser.rs
@@ -13,19 +13,19 @@ use dom::bindings::codegen::Bindings::Do
 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
 use dom::bindings::error::Fallible;
 use dom::bindings::js::{JS, Root};
 use dom::bindings::reflector::{Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::document::{Document, IsHTMLDocument};
 use dom::document::DocumentSource;
 use dom::globalscope::GlobalScope;
+use dom::servoparser::html::{ParseContext, parse_html};
+use dom::servoparser::xml::{self, parse_xml};
 use dom::window::Window;
-use parse::html::{ParseContext, parse_html};
-use parse::xml::{self, parse_xml};
 
 #[dom_struct]
 pub struct DOMParser {
     reflector_: Reflector,
     window: JS<Window>, // XXXjdm Document instead?
 }
 
 impl DOMParser {
--- a/servo/components/script/dom/mod.rs
+++ b/servo/components/script/dom/mod.rs
@@ -381,18 +381,17 @@ pub mod radionodelist;
 pub mod range;
 pub mod request;
 pub mod response;
 pub mod screen;
 pub mod serviceworker;
 pub mod serviceworkercontainer;
 pub mod serviceworkerglobalscope;
 pub mod serviceworkerregistration;
-pub mod servohtmlparser;
-pub mod servoxmlparser;
+pub mod servoparser;
 pub mod storage;
 pub mod storageevent;
 pub mod stylesheet;
 pub mod stylesheetlist;
 pub mod svgelement;
 pub mod svggraphicselement;
 pub mod svgsvgelement;
 pub mod testbinding;
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -42,29 +42,29 @@ use dom::htmlcollection::HTMLCollection;
 use dom::htmlelement::HTMLElement;
 use dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods};
 use dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
 use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
 use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers};
 use dom::nodelist::NodeList;
 use dom::processinginstruction::ProcessingInstruction;
 use dom::range::WeakRangeVec;
+use dom::servoparser::html::parse_html_fragment;
 use dom::svgsvgelement::{SVGSVGElement, LayoutSVGSVGElementHelpers};
 use dom::text::Text;
 use dom::virtualmethods::{VirtualMethods, vtable_for};
 use dom::window::Window;
 use euclid::point::Point2D;
 use euclid::rect::Rect;
 use euclid::size::Size2D;
 use heapsize::{HeapSizeOf, heap_size_of};
 use html5ever::tree_builder::QuirksMode;
 use js::jsapi::{JSContext, JSObject, JSRuntime};
 use libc::{self, c_void, uintptr_t};
 use msg::constellation_msg::PipelineId;
-use parse::html::parse_html_fragment;
 use ref_slice::ref_slice;
 use script_layout_interface::{HTMLCanvasData, OpaqueStyleAndLayoutData, SVGSVGData};
 use script_layout_interface::{LayoutElementType, LayoutNodeType, TrustedNodeAddress};
 use script_layout_interface::message::Msg;
 use script_traits::UntrustedNodeAddress;
 use selectors::matching::{MatchingReason, matches};
 use selectors::parser::Selector;
 use selectors::parser::parse_author_origin_selector_list_from_str;
deleted file mode 100644
--- a/servo/components/script/dom/servohtmlparser.rs
+++ /dev/null
@@ -1,431 +0,0 @@
-/* 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 http://mozilla.org/MPL/2.0/. */
-
-//! The bulk of the HTML parser integration is in `script::parse::html`.
-//! This module is mostly about its interaction with DOM memory management.
-
-use document_loader::LoadType;
-use dom::bindings::cell::DOMRefCell;
-use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
-use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
-use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
-use dom::bindings::codegen::Bindings::ServoHTMLParserBinding;
-use dom::bindings::inheritance::Castable;
-use dom::bindings::js::{JS, Root};
-use dom::bindings::refcounted::Trusted;
-use dom::bindings::reflector::{Reflector, reflect_dom_object};
-use dom::bindings::str::DOMString;
-use dom::bindings::trace::JSTraceable;
-use dom::document::Document;
-use dom::globalscope::GlobalScope;
-use dom::htmlimageelement::HTMLImageElement;
-use dom::node::Node;
-use dom::window::Window;
-use encoding::all::UTF_8;
-use encoding::types::{DecoderTrap, Encoding};
-use html5ever::tokenizer;
-use html5ever::tree_builder;
-use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts};
-use hyper::header::ContentType;
-use hyper::mime::{Mime, SubLevel, TopLevel};
-use hyper_serde::Serde;
-use js::jsapi::JSTracer;
-use msg::constellation_msg::PipelineId;
-use net_traits::{AsyncResponseListener, Metadata, NetworkError};
-use network_listener::PreInvoke;
-use parse::{Parser, ParserRef, TrustedParser};
-use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType, profile};
-use profile_traits::time::ProfilerCategory;
-use script_thread::ScriptThread;
-use std::cell::Cell;
-use std::default::Default;
-use url::Url;
-use util::resource_files::read_resource_file;
-
-#[must_root]
-#[derive(JSTraceable, HeapSizeOf)]
-pub struct Sink {
-    pub base_url: Option<Url>,
-    pub document: JS<Document>,
-}
-
-/// FragmentContext is used only to pass this group of related values
-/// into functions.
-#[derive(Copy, Clone)]
-pub struct FragmentContext<'a> {
-    pub context_elem: &'a Node,
-    pub form_elem: Option<&'a Node>,
-}
-
-pub type Tokenizer = tokenizer::Tokenizer<TreeBuilder<JS<Node>, Sink>>;
-
-/// The context required for asynchronously fetching a document and parsing it progressively.
-pub struct ParserContext {
-    /// The parser that initiated the request.
-    parser: Option<TrustedParser>,
-    /// Is this a synthesized document
-    is_synthesized_document: bool,
-    /// The pipeline associated with this document.
-    id: PipelineId,
-    /// The URL for this document.
-    url: Url,
-}
-
-impl ParserContext {
-    pub fn new(id: PipelineId, url: Url) -> ParserContext {
-        ParserContext {
-            parser: None,
-            is_synthesized_document: false,
-            id: id,
-            url: url,
-        }
-    }
-}
-
-impl AsyncResponseListener for ParserContext {
-    fn headers_available(&mut self, meta_result: Result<Metadata, NetworkError>) {
-        let mut ssl_error = None;
-        let metadata = match meta_result {
-            Ok(meta) => Some(meta),
-            Err(NetworkError::SslValidation(url, reason)) => {
-                ssl_error = Some(reason);
-                let mut meta = Metadata::default(url);
-                let mime: Option<Mime> = "text/html".parse().ok();
-                meta.set_content_type(mime.as_ref());
-                Some(meta)
-            },
-            Err(_) => None,
-        };
-        let content_type =
-            metadata.clone().and_then(|meta| meta.content_type).map(Serde::into_inner);
-        let parser = match ScriptThread::page_headers_available(&self.id,
-                                                                metadata) {
-            Some(parser) => parser,
-            None => return,
-        };
-
-        let parser = parser.r();
-        self.parser = Some(match parser {
-            ParserRef::HTML(parser) => TrustedParser::HTML(
-                                        Trusted::new(parser)),
-            ParserRef::XML(parser) => TrustedParser::XML(
-                                        Trusted::new(parser)),
-        });
-
-        match content_type {
-            Some(ContentType(Mime(TopLevel::Image, _, _))) => {
-                self.is_synthesized_document = true;
-                let page = "<html><body></body></html>".into();
-                parser.pending_input().borrow_mut().push(page);
-                parser.parse_sync();
-
-                let doc = parser.document();
-                let doc_body = Root::upcast::<Node>(doc.GetBody().unwrap());
-                let img = HTMLImageElement::new(atom!("img"), None, doc);
-                img.SetSrc(DOMString::from(self.url.to_string()));
-                doc_body.AppendChild(&Root::upcast::<Node>(img)).expect("Appending failed");
-
-            },
-            Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => {
-                // https://html.spec.whatwg.org/multipage/#read-text
-                let page = "<pre>\n".into();
-                parser.pending_input().borrow_mut().push(page);
-                parser.parse_sync();
-                parser.set_plaintext_state();
-            },
-            Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => { // Handle text/html
-                if let Some(reason) = ssl_error {
-                    self.is_synthesized_document = true;
-                    let page_bytes = read_resource_file("badcert.html").unwrap();
-                    let page = String::from_utf8(page_bytes).unwrap();
-                    let page = page.replace("${reason}", &reason);
-                    parser.pending_input().borrow_mut().push(page);
-                    parser.parse_sync();
-                }
-            },
-            Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {}, // Handle text/xml
-            Some(ContentType(Mime(toplevel, sublevel, _))) => {
-                if toplevel.as_str() == "application" && sublevel.as_str() == "xhtml+xml" {
-                    // Handle xhtml (application/xhtml+xml).
-                    return;
-                }
-
-                // Show warning page for unknown mime types.
-                let page = format!("<html><body><p>Unknown content type ({}/{}).</p></body></html>",
-                    toplevel.as_str(), sublevel.as_str());
-                self.is_synthesized_document = true;
-                parser.pending_input().borrow_mut().push(page);
-                parser.parse_sync();
-            },
-            None => {
-                // No content-type header.
-                // Merge with #4212 when fixed.
-            }
-        }
-    }
-
-    fn data_available(&mut self, payload: Vec<u8>) {
-        if !self.is_synthesized_document {
-            // FIXME: use Vec<u8> (html5ever #34)
-            let data = UTF_8.decode(&payload, DecoderTrap::Replace).unwrap();
-            let parser = match self.parser.as_ref() {
-                Some(parser) => parser.root(),
-                None => return,
-            };
-            parser.r().parse_chunk(data);
-        }
-    }
-
-    fn response_complete(&mut self, status: Result<(), NetworkError>) {
-        let parser = match self.parser.as_ref() {
-            Some(parser) => parser.root(),
-            None => return,
-        };
-
-        if let Err(NetworkError::Internal(ref reason)) = status {
-            // Show an error page for network errors,
-            // certificate errors are handled earlier.
-            self.is_synthesized_document = true;
-            let parser = parser.r();
-            let page_bytes = read_resource_file("neterror.html").unwrap();
-            let page = String::from_utf8(page_bytes).unwrap();
-            let page = page.replace("${reason}", reason);
-            parser.pending_input().borrow_mut().push(page);
-            parser.parse_sync();
-        } else if let Err(err) = status {
-            // TODO(Savago): we should send a notification to callers #5463.
-            debug!("Failed to load page URL {}, error: {:?}", self.url, err);
-        }
-
-        parser.r().document().finish_load(LoadType::PageSource(self.url.clone()));
-
-        parser.r().last_chunk_received().set(true);
-        if !parser.r().is_suspended() {
-            parser.r().parse_sync();
-        }
-    }
-}
-
-impl PreInvoke for ParserContext {
-}
-
-#[dom_struct]
-pub struct ServoHTMLParser {
-    reflector_: Reflector,
-    #[ignore_heap_size_of = "Defined in html5ever"]
-    tokenizer: DOMRefCell<Tokenizer>,
-    /// Input chunks received but not yet passed to the parser.
-    pending_input: DOMRefCell<Vec<String>>,
-    /// The document associated with this parser.
-    document: JS<Document>,
-    /// True if this parser should avoid passing any further data to the tokenizer.
-    suspended: Cell<bool>,
-    /// Whether to expect any further input from the associated network request.
-    last_chunk_received: Cell<bool>,
-    /// The pipeline associated with this parse, unavailable if this parse does not
-    /// correspond to a page load.
-    pipeline: Option<PipelineId>,
-}
-
-impl<'a> Parser for &'a ServoHTMLParser {
-    fn parse_chunk(self, input: String) {
-        self.document.set_current_parser(Some(ParserRef::HTML(self)));
-        self.pending_input.borrow_mut().push(input);
-        if !self.is_suspended() {
-            self.parse_sync();
-        }
-    }
-
-    fn finish(self) {
-        assert!(!self.suspended.get());
-        assert!(self.pending_input.borrow().is_empty());
-
-        self.tokenizer.borrow_mut().end();
-        debug!("finished parsing");
-
-        self.document.set_current_parser(None);
-
-        if let Some(pipeline) = self.pipeline {
-            ScriptThread::parsing_complete(pipeline);
-        }
-    }
-}
-
-impl ServoHTMLParser {
-    #[allow(unrooted_must_root)]
-    pub fn new(base_url: Option<Url>, document: &Document, pipeline: Option<PipelineId>)
-               -> Root<ServoHTMLParser> {
-        let sink = Sink {
-            base_url: base_url,
-            document: JS::from_ref(document),
-        };
-
-        let tb = TreeBuilder::new(sink, TreeBuilderOpts {
-            ignore_missing_rules: true,
-            .. Default::default()
-        });
-
-        let tok = tokenizer::Tokenizer::new(tb, Default::default());
-
-        let parser = ServoHTMLParser {
-            reflector_: Reflector::new(),
-            tokenizer: DOMRefCell::new(tok),
-            pending_input: DOMRefCell::new(vec!()),
-            document: JS::from_ref(document),
-            suspended: Cell::new(false),
-            last_chunk_received: Cell::new(false),
-            pipeline: pipeline,
-        };
-
-        reflect_dom_object(box parser, document.window(), ServoHTMLParserBinding::Wrap)
-    }
-
-    #[allow(unrooted_must_root)]
-    pub fn new_for_fragment(base_url: Option<Url>, document: &Document,
-                            fragment_context: FragmentContext) -> Root<ServoHTMLParser> {
-        let sink = Sink {
-            base_url: base_url,
-            document: JS::from_ref(document),
-        };
-
-        let tb_opts = TreeBuilderOpts {
-            ignore_missing_rules: true,
-            .. Default::default()
-        };
-        let tb = TreeBuilder::new_for_fragment(sink,
-                                               JS::from_ref(fragment_context.context_elem),
-                                               fragment_context.form_elem.map(|n| JS::from_ref(n)),
-                                               tb_opts);
-
-        let tok_opts = tokenizer::TokenizerOpts {
-            initial_state: Some(tb.tokenizer_state_for_context_elem()),
-            .. Default::default()
-        };
-        let tok = tokenizer::Tokenizer::new(tb, tok_opts);
-
-        let parser = ServoHTMLParser {
-            reflector_: Reflector::new(),
-            tokenizer: DOMRefCell::new(tok),
-            pending_input: DOMRefCell::new(vec!()),
-            document: JS::from_ref(document),
-            suspended: Cell::new(false),
-            last_chunk_received: Cell::new(true),
-            pipeline: None,
-        };
-
-        reflect_dom_object(box parser, document.window(), ServoHTMLParserBinding::Wrap)
-    }
-
-    #[inline]
-    pub fn tokenizer(&self) -> &DOMRefCell<Tokenizer> {
-        &self.tokenizer
-    }
-
-    pub fn set_plaintext_state(&self) {
-        self.tokenizer.borrow_mut().set_plaintext_state()
-    }
-
-    pub fn end_tokenizer(&self) {
-        self.tokenizer.borrow_mut().end()
-    }
-
-    pub fn pending_input(&self) -> &DOMRefCell<Vec<String>> {
-        &self.pending_input
-    }
-
-}
-
-impl ServoHTMLParser {
-    pub fn parse_sync(&self) {
-        let metadata = TimerMetadata {
-            url: self.document.url().as_str().into(),
-            iframe: TimerMetadataFrameType::RootWindow,
-            incremental: TimerMetadataReflowType::FirstReflow,
-        };
-        profile(ProfilerCategory::ScriptParseHTML,
-                Some(metadata),
-                self.document.window().upcast::<GlobalScope>().time_profiler_chan().clone(),
-                || self.do_parse_sync())
-    }
-
-    fn do_parse_sync(&self) {
-        // This parser will continue to parse while there is either pending input or
-        // the parser remains unsuspended.
-        loop {
-           self.document.reflow_if_reflow_timer_expired();
-            let mut pending_input = self.pending_input.borrow_mut();
-            if !pending_input.is_empty() {
-                let chunk = pending_input.remove(0);
-                self.tokenizer.borrow_mut().feed(chunk.into());
-            } else {
-                self.tokenizer.borrow_mut().run();
-            }
-
-            // Document parsing is blocked on an external resource.
-            if self.suspended.get() {
-                return;
-            }
-
-            if pending_input.is_empty() {
-                break;
-            }
-        }
-
-        if self.last_chunk_received.get() {
-            self.finish();
-        }
-    }
-
-    pub fn window(&self) -> &Window {
-        self.document.window()
-    }
-
-    pub fn suspend(&self) {
-        assert!(!self.suspended.get());
-        self.suspended.set(true);
-    }
-
-    pub fn resume(&self) {
-        assert!(self.suspended.get());
-        self.suspended.set(false);
-        self.parse_sync();
-    }
-
-    pub fn is_suspended(&self) -> bool {
-        self.suspended.get()
-    }
-
-    pub fn document(&self) -> &Document {
-        &self.document
-    }
-
-    pub fn last_chunk_received(&self) -> &Cell<bool> {
-        &self.last_chunk_received
-    }
-}
-
-struct Tracer {
-    trc: *mut JSTracer,
-}
-
-impl tree_builder::Tracer for Tracer {
-    type Handle = JS<Node>;
-    #[allow(unrooted_must_root)]
-    fn trace_handle(&self, node: &JS<Node>) {
-        node.trace(self.trc);
-    }
-}
-
-impl JSTraceable for Tokenizer {
-    fn trace(&self, trc: *mut JSTracer) {
-        let tracer = Tracer {
-            trc: trc,
-        };
-        let tracer = &tracer as &tree_builder::Tracer<Handle=JS<Node>>;
-
-        let tree_builder = self.sink();
-        tree_builder.trace_handles(tracer);
-        tree_builder.sink().trace(trc);
-    }
-}
rename from servo/components/script/parse/html.rs
rename to servo/components/script/dom/servoparser/html.rs
--- a/servo/components/script/parse/html.rs
+++ b/servo/components/script/dom/servoparser/html.rs
@@ -18,47 +18,47 @@ use dom::document::Document;
 use dom::documenttype::DocumentType;
 use dom::element::{Element, ElementCreator};
 use dom::htmlformelement::HTMLFormElement;
 use dom::htmlscriptelement::HTMLScriptElement;
 use dom::htmltemplateelement::HTMLTemplateElement;
 use dom::node::{document_from_node, window_from_node};
 use dom::node::Node;
 use dom::processinginstruction::ProcessingInstruction;
-use dom::servohtmlparser;
-use dom::servohtmlparser::{FragmentContext, ServoHTMLParser};
 use dom::text::Text;
 use html5ever::Attribute;
 use html5ever::serialize::{AttrRef, Serializable, Serializer};
 use html5ever::serialize::TraversalScope;
 use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
 use html5ever::tendril::StrTendril;
-use html5ever::tree_builder::{NextParserState, NodeOrText, QuirksMode, TreeSink};
+use html5ever::tokenizer::{Tokenizer as HtmlTokenizer, TokenizerOpts};
+use html5ever::tree_builder::{NextParserState, NodeOrText, QuirksMode};
+use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts, TreeSink};
 use msg::constellation_msg::PipelineId;
-use parse::Parser;
 use std::borrow::Cow;
 use std::io::{self, Write};
 use string_cache::QualName;
+use super::{LastChunkState, ServoParser, Sink, Tokenizer};
 use url::Url;
 
 fn insert(parent: &Node, reference_child: Option<&Node>, child: NodeOrText<JS<Node>>) {
     match child {
         NodeOrText::AppendNode(n) => {
             assert!(parent.InsertBefore(&n, reference_child).is_ok());
         },
         NodeOrText::AppendText(t) => {
             // FIXME(ajeffrey): convert directly from tendrils to DOMStrings
             let s: String = t.into();
             let text = Text::new(DOMString::from(s), &parent.owner_doc());
             assert!(parent.InsertBefore(text.upcast(), reference_child).is_ok());
         }
     }
 }
 
-impl<'a> TreeSink for servohtmlparser::Sink {
+impl<'a> TreeSink for Sink {
     type Output = Self;
     fn finish(self) -> Self { self }
 
     type Handle = JS<Node>;
 
     fn get_document(&mut self) -> JS<Node> {
         JS::from_ref(self.document.upcast())
     }
@@ -241,30 +241,67 @@ impl<'a> Serializable for &'a Node {
 
             (IncludeNode, NodeTypeId::DocumentFragment) => Ok(()),
 
             (IncludeNode, NodeTypeId::Document(_)) => panic!("Can't serialize Document node itself"),
         }
     }
 }
 
+/// FragmentContext is used only to pass this group of related values
+/// into functions.
+#[derive(Copy, Clone)]
+pub struct FragmentContext<'a> {
+    pub context_elem: &'a Node,
+    pub form_elem: Option<&'a Node>,
+}
+
 pub enum ParseContext<'a> {
     Fragment(FragmentContext<'a>),
     Owner(Option<PipelineId>),
 }
 
 pub fn parse_html(document: &Document,
                   input: DOMString,
                   url: Url,
                   context: ParseContext) {
+    let sink = Sink {
+        base_url: url,
+        document: JS::from_ref(document),
+    };
+
+    let options = TreeBuilderOpts {
+        ignore_missing_rules: true,
+        .. Default::default()
+    };
+
     let parser = match context {
-        ParseContext::Owner(owner) =>
-            ServoHTMLParser::new(Some(url), document, owner),
-        ParseContext::Fragment(fc) =>
-            ServoHTMLParser::new_for_fragment(Some(url), document, fc),
+        ParseContext::Owner(owner) => {
+            let tb = TreeBuilder::new(sink, options);
+            let tok = HtmlTokenizer::new(tb, Default::default());
+
+            ServoParser::new(
+                document, owner, Tokenizer::HTML(tok), LastChunkState::NotReceived)
+        },
+        ParseContext::Fragment(fc) => {
+            let tb = TreeBuilder::new_for_fragment(
+                sink,
+                JS::from_ref(fc.context_elem),
+                fc.form_elem.map(|n| JS::from_ref(n)),
+                options);
+
+            let tok_options = TokenizerOpts {
+                initial_state: Some(tb.tokenizer_state_for_context_elem()),
+                .. Default::default()
+            };
+            let tok = HtmlTokenizer::new(tb, tok_options);
+
+            ServoParser::new(
+                document, None, Tokenizer::HTML(tok), LastChunkState::Received)
+        }
     };
     parser.parse_chunk(String::from(input));
 }
 
 // https://html.spec.whatwg.org/multipage/#parsing-html-fragments
 pub fn parse_html_fragment(context_node: &Node,
                            input: DOMString,
                            output: &Node) {
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/servoparser/mod.rs
@@ -0,0 +1,452 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use document_loader::LoadType;
+use dom::bindings::cell::DOMRefCell;
+use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
+use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::Bindings::ServoParserBinding;
+use dom::bindings::inheritance::Castable;
+use dom::bindings::js::{JS, Root};
+use dom::bindings::refcounted::Trusted;
+use dom::bindings::reflector::{Reflector, reflect_dom_object};
+use dom::bindings::str::DOMString;
+use dom::bindings::trace::JSTraceable;
+use dom::document::Document;
+use dom::globalscope::GlobalScope;
+use dom::htmlimageelement::HTMLImageElement;
+use dom::node::Node;
+use encoding::all::UTF_8;
+use encoding::types::{DecoderTrap, Encoding};
+use html5ever::tokenizer::Tokenizer as HtmlTokenizer;
+use html5ever::tree_builder::Tracer as HtmlTracer;
+use html5ever::tree_builder::TreeBuilder as HtmlTreeBuilder;
+use hyper::header::ContentType;
+use hyper::mime::{Mime, SubLevel, TopLevel};
+use hyper_serde::Serde;
+use js::jsapi::JSTracer;
+use msg::constellation_msg::PipelineId;
+use net_traits::{AsyncResponseListener, Metadata, NetworkError};
+use network_listener::PreInvoke;
+use profile_traits::time::{TimerMetadata, TimerMetadataFrameType};
+use profile_traits::time::{TimerMetadataReflowType, ProfilerCategory, profile};
+use script_thread::ScriptThread;
+use std::cell::Cell;
+use url::Url;
+use util::resource_files::read_resource_file;
+use xml5ever::tokenizer::XmlTokenizer;
+use xml5ever::tree_builder::{Tracer as XmlTracer, XmlTreeBuilder};
+
+pub mod html;
+pub mod xml;
+
+#[dom_struct]
+pub struct ServoParser {
+    reflector: Reflector,
+    /// The document associated with this parser.
+    document: JS<Document>,
+    /// The pipeline associated with this parse, unavailable if this parse
+    /// does not correspond to a page load.
+    pipeline: Option<PipelineId>,
+    /// Input chunks received but not yet passed to the parser.
+    pending_input: DOMRefCell<Vec<String>>,
+    /// The tokenizer of this parser.
+    tokenizer: DOMRefCell<Tokenizer>,
+    /// Whether to expect any further input from the associated network request.
+    last_chunk_received: Cell<bool>,
+    /// Whether this parser should avoid passing any further data to the tokenizer.
+    suspended: Cell<bool>,
+}
+
+#[derive(PartialEq)]
+enum LastChunkState {
+    Received,
+    NotReceived,
+}
+
+impl ServoParser {
+    #[allow(unrooted_must_root)]
+    fn new_inherited(
+            document: &Document,
+            pipeline: Option<PipelineId>,
+            tokenizer: Tokenizer,
+            last_chunk_state: LastChunkState)
+            -> Self {
+        ServoParser {
+            reflector: Reflector::new(),
+            document: JS::from_ref(document),
+            pipeline: pipeline,
+            pending_input: DOMRefCell::new(vec![]),
+            tokenizer: DOMRefCell::new(tokenizer),
+            last_chunk_received: Cell::new(last_chunk_state == LastChunkState::Received),
+            suspended: Default::default(),
+        }
+    }
+
+    #[allow(unrooted_must_root)]
+    fn new(
+            document: &Document,
+            pipeline: Option<PipelineId>,
+            tokenizer: Tokenizer,
+            last_chunk_state: LastChunkState)
+            -> Root<Self> {
+        reflect_dom_object(
+            box ServoParser::new_inherited(document, pipeline, tokenizer, last_chunk_state),
+            document.window(),
+            ServoParserBinding::Wrap)
+    }
+
+    pub fn document(&self) -> &Document {
+        &self.document
+    }
+
+    pub fn pipeline(&self) -> Option<PipelineId> {
+        self.pipeline
+    }
+
+    fn has_pending_input(&self) -> bool {
+        !self.pending_input.borrow().is_empty()
+    }
+
+    fn push_input_chunk(&self, chunk: String) {
+        self.pending_input.borrow_mut().push(chunk);
+    }
+
+    fn take_next_input_chunk(&self) -> Option<String> {
+        let mut pending_input = self.pending_input.borrow_mut();
+        if pending_input.is_empty() {
+            None
+        } else {
+            Some(pending_input.remove(0))
+        }
+    }
+
+    fn last_chunk_received(&self) -> bool {
+        self.last_chunk_received.get()
+    }
+
+    fn mark_last_chunk_received(&self) {
+        self.last_chunk_received.set(true)
+    }
+
+    fn set_plaintext_state(&self) {
+        self.tokenizer.borrow_mut().set_plaintext_state()
+    }
+
+    pub fn end_tokenizer(&self) {
+        self.tokenizer.borrow_mut().end()
+    }
+
+    pub fn suspend(&self) {
+        assert!(!self.suspended.get());
+        self.suspended.set(true);
+    }
+
+    pub fn resume(&self) {
+        assert!(self.suspended.get());
+        self.suspended.set(false);
+        self.parse_sync();
+    }
+
+    pub fn is_suspended(&self) -> bool {
+        self.suspended.get()
+    }
+
+    fn parse_sync(&self) {
+        let metadata = TimerMetadata {
+            url: self.document().url().as_str().into(),
+            iframe: TimerMetadataFrameType::RootWindow,
+            incremental: TimerMetadataReflowType::FirstReflow,
+        };
+        let profiler_category = self.tokenizer.borrow().profiler_category();
+        profile(profiler_category,
+                Some(metadata),
+                self.document().window().upcast::<GlobalScope>().time_profiler_chan().clone(),
+                || self.do_parse_sync())
+    }
+
+    fn do_parse_sync(&self) {
+        // This parser will continue to parse while there is either pending input or
+        // the parser remains unsuspended.
+        loop {
+            self.document().reflow_if_reflow_timer_expired();
+            if let Some(chunk) = self.take_next_input_chunk() {
+                self.tokenizer.borrow_mut().feed(chunk);
+            } else {
+                self.tokenizer.borrow_mut().run();
+            }
+
+            // Document parsing is blocked on an external resource.
+            if self.suspended.get() {
+                return;
+            }
+
+            if !self.has_pending_input() {
+                break;
+            }
+        }
+
+        if self.last_chunk_received() {
+            self.finish();
+        }
+    }
+
+    fn parse_chunk(&self, input: String) {
+        self.document().set_current_parser(Some(self));
+        self.push_input_chunk(input);
+        if !self.is_suspended() {
+            self.parse_sync();
+        }
+    }
+
+    fn finish(&self) {
+        assert!(!self.suspended.get());
+        assert!(!self.has_pending_input());
+
+        self.tokenizer.borrow_mut().end();
+        debug!("finished parsing");
+
+        self.document().set_current_parser(None);
+
+        if let Some(pipeline) = self.pipeline() {
+            ScriptThread::parsing_complete(pipeline);
+        }
+    }
+}
+
+#[derive(HeapSizeOf)]
+#[must_root]
+enum Tokenizer {
+    HTML(
+        #[ignore_heap_size_of = "Defined in html5ever"]
+        HtmlTokenizer<HtmlTreeBuilder<JS<Node>, Sink>>
+    ),
+    XML(
+        #[ignore_heap_size_of = "Defined in xml5ever"]
+        XmlTokenizer<XmlTreeBuilder<JS<Node>, Sink>>
+    ),
+}
+
+#[derive(JSTraceable, HeapSizeOf)]
+#[must_root]
+struct Sink {
+    pub base_url: Url,
+    pub document: JS<Document>,
+}
+
+impl Tokenizer {
+    fn feed(&mut self, input: String) {
+        match *self {
+            Tokenizer::HTML(ref mut tokenizer) => tokenizer.feed(input.into()),
+            Tokenizer::XML(ref mut tokenizer) => tokenizer.feed(input.into()),
+        }
+    }
+
+    fn run(&mut self) {
+        match *self {
+            Tokenizer::HTML(ref mut tokenizer) => tokenizer.run(),
+            Tokenizer::XML(ref mut tokenizer) => tokenizer.run(),
+        }
+    }
+
+    fn end(&mut self) {
+        match *self {
+            Tokenizer::HTML(ref mut tokenizer) => tokenizer.end(),
+            Tokenizer::XML(ref mut tokenizer) => tokenizer.end(),
+        }
+    }
+
+    fn set_plaintext_state(&mut self) {
+        match *self {
+            Tokenizer::HTML(ref mut tokenizer) => tokenizer.set_plaintext_state(),
+            Tokenizer::XML(_) => { /* todo */ },
+        }
+    }
+
+    fn profiler_category(&self) -> ProfilerCategory {
+        match *self {
+            Tokenizer::HTML(_) => ProfilerCategory::ScriptParseHTML,
+            Tokenizer::XML(_) => ProfilerCategory::ScriptParseXML,
+        }
+    }
+}
+
+impl JSTraceable for Tokenizer {
+    fn trace(&self, trc: *mut JSTracer) {
+        struct Tracer(*mut JSTracer);
+        let tracer = Tracer(trc);
+
+        match *self {
+            Tokenizer::HTML(ref tokenizer) => {
+                impl HtmlTracer for Tracer {
+                    type Handle = JS<Node>;
+                    #[allow(unrooted_must_root)]
+                    fn trace_handle(&self, node: &JS<Node>) {
+                        node.trace(self.0);
+                    }
+                }
+                let tree_builder = tokenizer.sink();
+                tree_builder.trace_handles(&tracer);
+                tree_builder.sink().trace(trc);
+            },
+            Tokenizer::XML(ref tokenizer) => {
+                impl XmlTracer for Tracer {
+                    type Handle = JS<Node>;
+                    #[allow(unrooted_must_root)]
+                    fn trace_handle(&self, node: JS<Node>) {
+                        node.trace(self.0);
+                    }
+                }
+                let tree_builder = tokenizer.sink();
+                tree_builder.trace_handles(&tracer);
+                tree_builder.sink().trace(trc);
+            }
+        }
+    }
+}
+
+/// The context required for asynchronously fetching a document
+/// and parsing it progressively.
+pub struct ParserContext {
+    /// The parser that initiated the request.
+    parser: Option<Trusted<ServoParser>>,
+    /// Is this a synthesized document
+    is_synthesized_document: bool,
+    /// The pipeline associated with this document.
+    id: PipelineId,
+    /// The URL for this document.
+    url: Url,
+}
+
+impl ParserContext {
+    pub fn new(id: PipelineId, url: Url) -> ParserContext {
+        ParserContext {
+            parser: None,
+            is_synthesized_document: false,
+            id: id,
+            url: url,
+        }
+    }
+}
+
+impl AsyncResponseListener for ParserContext {
+    fn headers_available(&mut self, meta_result: Result<Metadata, NetworkError>) {
+        let mut ssl_error = None;
+        let metadata = match meta_result {
+            Ok(meta) => Some(meta),
+            Err(NetworkError::SslValidation(url, reason)) => {
+                ssl_error = Some(reason);
+                let mut meta = Metadata::default(url);
+                let mime: Option<Mime> = "text/html".parse().ok();
+                meta.set_content_type(mime.as_ref());
+                Some(meta)
+            },
+            Err(_) => None,
+        };
+        let content_type =
+            metadata.clone().and_then(|meta| meta.content_type).map(Serde::into_inner);
+        let parser = match ScriptThread::page_headers_available(&self.id,
+                                                                metadata) {
+            Some(parser) => parser,
+            None => return,
+        };
+
+        self.parser = Some(Trusted::new(&*parser));
+
+        match content_type {
+            Some(ContentType(Mime(TopLevel::Image, _, _))) => {
+                self.is_synthesized_document = true;
+                let page = "<html><body></body></html>".into();
+                parser.push_input_chunk(page);
+                parser.parse_sync();
+
+                let doc = parser.document();
+                let doc_body = Root::upcast::<Node>(doc.GetBody().unwrap());
+                let img = HTMLImageElement::new(atom!("img"), None, doc);
+                img.SetSrc(DOMString::from(self.url.to_string()));
+                doc_body.AppendChild(&Root::upcast::<Node>(img)).expect("Appending failed");
+
+            },
+            Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => {
+                // https://html.spec.whatwg.org/multipage/#read-text
+                let page = "<pre>\n".into();
+                parser.push_input_chunk(page);
+                parser.parse_sync();
+                parser.set_plaintext_state();
+            },
+            Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => { // Handle text/html
+                if let Some(reason) = ssl_error {
+                    self.is_synthesized_document = true;
+                    let page_bytes = read_resource_file("badcert.html").unwrap();
+                    let page = String::from_utf8(page_bytes).unwrap();
+                    let page = page.replace("${reason}", &reason);
+                    parser.push_input_chunk(page);
+                    parser.parse_sync();
+                }
+            },
+            Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {}, // Handle text/xml
+            Some(ContentType(Mime(toplevel, sublevel, _))) => {
+                if toplevel.as_str() == "application" && sublevel.as_str() == "xhtml+xml" {
+                    // Handle xhtml (application/xhtml+xml).
+                    return;
+                }
+
+                // Show warning page for unknown mime types.
+                let page = format!("<html><body><p>Unknown content type ({}/{}).</p></body></html>",
+                    toplevel.as_str(), sublevel.as_str());
+                self.is_synthesized_document = true;
+                parser.push_input_chunk(page);
+                parser.parse_sync();
+            },
+            None => {
+                // No content-type header.
+                // Merge with #4212 when fixed.
+            }
+        }
+    }
+
+    fn data_available(&mut self, payload: Vec<u8>) {
+        if !self.is_synthesized_document {
+            // FIXME: use Vec<u8> (html5ever #34)
+            let data = UTF_8.decode(&payload, DecoderTrap::Replace).unwrap();
+            let parser = match self.parser.as_ref() {
+                Some(parser) => parser.root(),
+                None => return,
+            };
+            parser.parse_chunk(data);
+        }
+    }
+
+    fn response_complete(&mut self, status: Result<(), NetworkError>) {
+        let parser = match self.parser.as_ref() {
+            Some(parser) => parser.root(),
+            None => return,
+        };
+
+        if let Err(NetworkError::Internal(ref reason)) = status {
+            // Show an error page for network errors,
+            // certificate errors are handled earlier.
+            self.is_synthesized_document = true;
+            let page_bytes = read_resource_file("neterror.html").unwrap();
+            let page = String::from_utf8(page_bytes).unwrap();
+            let page = page.replace("${reason}", reason);
+            parser.push_input_chunk(page);
+            parser.parse_sync();
+        } else if let Err(err) = status {
+            // TODO(Savago): we should send a notification to callers #5463.
+            debug!("Failed to load page URL {}, error: {:?}", self.url, err);
+        }
+
+        parser.document()
+            .finish_load(LoadType::PageSource(self.url.clone()));
+
+        parser.mark_last_chunk_received();
+        if !parser.is_suspended() {
+            parser.parse_sync();
+        }
+    }
+}
+
+impl PreInvoke for ParserContext {}
rename from servo/components/script/parse/xml.rs
rename to servo/components/script/dom/servoparser/xml.rs
--- a/servo/components/script/parse/xml.rs
+++ b/servo/components/script/dom/servoparser/xml.rs
@@ -10,30 +10,28 @@ use dom::bindings::js::{JS, Root};
 use dom::bindings::str::DOMString;
 use dom::comment::Comment;
 use dom::document::Document;
 use dom::documenttype::DocumentType;
 use dom::element::{Element, ElementCreator};
 use dom::htmlscriptelement::HTMLScriptElement;
 use dom::node::Node;
 use dom::processinginstruction::ProcessingInstruction;
-use dom::servoxmlparser;
-use dom::servoxmlparser::ServoXMLParser;
 use dom::text::Text;
 use html5ever;
 use msg::constellation_msg::PipelineId;
-use parse::Parser;
 use std::borrow::Cow;
 use string_cache::{Atom, QualName, Namespace};
+use super::{LastChunkState, ServoParser, Sink, Tokenizer};
 use url::Url;
 use xml5ever::tendril::StrTendril;
-use xml5ever::tokenizer::{Attribute, QName};
-use xml5ever::tree_builder::{NextParserState, NodeOrText, TreeSink};
+use xml5ever::tokenizer::{Attribute, QName, XmlTokenizer};
+use xml5ever::tree_builder::{NextParserState, NodeOrText, TreeSink, XmlTreeBuilder};
 
-impl<'a> TreeSink for servoxmlparser::Sink {
+impl<'a> TreeSink for Sink {
     type Handle = JS<Node>;
 
     fn parse_error(&mut self, msg: Cow<'static, str>) {
         debug!("Parse error: {}", msg);
     }
 
     fn get_document(&mut self) -> JS<Node> {
         JS::from_ref(self.document.upcast())
@@ -129,13 +127,21 @@ pub enum ParseContext {
 }
 
 
 pub fn parse_xml(document: &Document,
                  input: DOMString,
                  url: Url,
                  context: ParseContext) {
     let parser = match context {
-        ParseContext::Owner(owner) =>
-            ServoXMLParser::new(Some(url), document, owner),
+        ParseContext::Owner(owner) => {
+            let tb = XmlTreeBuilder::new(Sink {
+                base_url: url,
+                document: JS::from_ref(document),
+            });
+            let tok = XmlTokenizer::new(tb, Default::default());
+
+            ServoParser::new(
+                document, owner, Tokenizer::XML(tok), LastChunkState::NotReceived)
+        }
     };
     parser.parse_chunk(String::from(input));
 }
deleted file mode 100644
--- a/servo/components/script/dom/servoxmlparser.rs
+++ /dev/null
@@ -1,195 +0,0 @@
-/* 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 http://mozilla.org/MPL/2.0/. */
-
-use dom::bindings::cell::DOMRefCell;
-use dom::bindings::codegen::Bindings::ServoXMLParserBinding;
-use dom::bindings::js::{JS, Root};
-use dom::bindings::reflector::{Reflector, reflect_dom_object};
-use dom::bindings::trace::JSTraceable;
-use dom::document::Document;
-use dom::node::Node;
-use dom::window::Window;
-use js::jsapi::JSTracer;
-use msg::constellation_msg::PipelineId;
-use parse::{Parser, ParserRef};
-use script_thread::ScriptThread;
-use std::cell::Cell;
-use url::Url;
-use xml5ever::tokenizer;
-use xml5ever::tree_builder::{self, XmlTreeBuilder};
-
-pub type Tokenizer = tokenizer::XmlTokenizer<XmlTreeBuilder<JS<Node>, Sink>>;
-
-#[must_root]
-#[derive(JSTraceable, HeapSizeOf)]
-pub struct Sink {
-    pub base_url: Option<Url>,
-    pub document: JS<Document>,
-}
-
-#[must_root]
-#[dom_struct]
-pub struct ServoXMLParser {
-    reflector_: Reflector,
-    #[ignore_heap_size_of = "Defined in xml5ever"]
-    tokenizer: DOMRefCell<Tokenizer>,
-    /// Input chunks received but not yet passed to the parser.
-    pending_input: DOMRefCell<Vec<String>>,
-    /// The document associated with this parser.
-    document: JS<Document>,
-    /// True if this parser should avoid passing any further data to the tokenizer.
-    suspended: Cell<bool>,
-    /// Whether to expect any further input from the associated network request.
-    last_chunk_received: Cell<bool>,
-    /// The pipeline associated with this parse, unavailable if this parse does not
-    /// correspond to a page load.
-    pipeline: Option<PipelineId>,
-}
-
-impl<'a> Parser for &'a ServoXMLParser {
-    fn parse_chunk(self, input: String) {
-        self.document.set_current_parser(Some(ParserRef::XML(self)));
-        self.pending_input.borrow_mut().push(input);
-        if !self.is_suspended() {
-            self.parse_sync();
-        }
-    }
-
-    fn finish(self) {
-        assert!(!self.suspended.get());
-        assert!(self.pending_input.borrow().is_empty());
-
-        self.tokenizer.borrow_mut().end();
-        debug!("finished parsing");
-
-        self.document.set_current_parser(None);
-
-        if let Some(pipeline) = self.pipeline {
-            ScriptThread::parsing_complete(pipeline);
-        }
-    }
-}
-
-impl ServoXMLParser {
-    #[allow(unrooted_must_root)]
-    pub fn new(base_url: Option<Url>, document: &Document, pipeline: Option<PipelineId>)
-               -> Root<ServoXMLParser> {
-        let sink = Sink {
-            base_url: base_url,
-            document: JS::from_ref(document),
-        };
-
-        let tb = XmlTreeBuilder::new(sink);
-
-        let tok = tokenizer::XmlTokenizer::new(tb, Default::default());
-
-        let parser = ServoXMLParser {
-            reflector_: Reflector::new(),
-            tokenizer: DOMRefCell::new(tok),
-            pending_input: DOMRefCell::new(vec!()),
-            document: JS::from_ref(document),
-            suspended: Cell::new(false),
-            last_chunk_received: Cell::new(false),
-            pipeline: pipeline,
-        };
-
-        reflect_dom_object(box parser, document.window(), ServoXMLParserBinding::Wrap)
-    }
-
-    pub fn window(&self) -> &Window {
-        self.document.window()
-    }
-
-    pub fn resume(&self) {
-        assert!(self.suspended.get());
-        self.suspended.set(false);
-        self.parse_sync();
-    }
-
-    pub fn suspend(&self) {
-        assert!(!self.suspended.get());
-        self.suspended.set(true);
-    }
-
-    pub fn is_suspended(&self) -> bool {
-        self.suspended.get()
-    }
-
-    pub fn parse_sync(&self) {
-        // This parser will continue to parse while there is either pending input or
-        // the parser remains unsuspended.
-        loop {
-           self.document.reflow_if_reflow_timer_expired();
-            let mut pending_input = self.pending_input.borrow_mut();
-            if !pending_input.is_empty() {
-                let chunk = pending_input.remove(0);
-                self.tokenizer.borrow_mut().feed(chunk.into());
-            } else {
-                self.tokenizer.borrow_mut().run();
-            }
-
-            // Document parsing is blocked on an external resource.
-            if self.suspended.get() {
-                return;
-            }
-
-            if pending_input.is_empty() {
-                break;
-            }
-        }
-
-        if self.last_chunk_received.get() {
-            self.finish();
-        }
-    }
-
-    pub fn pending_input(&self) -> &DOMRefCell<Vec<String>> {
-        &self.pending_input
-    }
-
-    pub fn set_plaintext_state(&self) {
-        //self.tokenizer.borrow_mut().set_plaintext_state()
-    }
-
-    pub fn end_tokenizer(&self) {
-        self.tokenizer.borrow_mut().end()
-    }
-
-    pub fn document(&self) -> &Document {
-        &self.document
-    }
-
-    pub fn last_chunk_received(&self) -> &Cell<bool> {
-        &self.last_chunk_received
-    }
-
-    pub fn tokenizer(&self) -> &DOMRefCell<Tokenizer> {
-        &self.tokenizer
-    }
-}
-
-struct Tracer {
-    trc: *mut JSTracer,
-}
-
-impl tree_builder::Tracer for Tracer {
-    type Handle = JS<Node>;
-    #[allow(unrooted_must_root)]
-    fn trace_handle(&self, node: JS<Node>) {
-        node.trace(self.trc);
-    }
-}
-
-impl JSTraceable for Tokenizer {
-    fn trace(&self, trc: *mut JSTracer) {
-        let tracer = Tracer {
-            trc: trc,
-        };
-        let tracer = &tracer as &tree_builder::Tracer<Handle=JS<Node>>;
-
-        let tree_builder = self.sink();
-        tree_builder.trace_handles(tracer);
-        tree_builder.sink().trace(trc);
-    }
-}
deleted file mode 100644
--- a/servo/components/script/dom/webidls/ServoHTMLParser.webidl
+++ /dev/null
@@ -1,11 +0,0 @@
-/* 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 http://mozilla.org/MPL/2.0/. */
-
-// This interface is entirely internal to Servo, and should not be accessible to
-// web pages.
-
-// FIXME: find a better way to hide this from content (#3688)
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface ServoHTMLParser {
-};
rename from servo/components/script/dom/webidls/ServoXMLParser.webidl
rename to servo/components/script/dom/webidls/ServoParser.webidl
--- a/servo/components/script/dom/webidls/ServoXMLParser.webidl
+++ b/servo/components/script/dom/webidls/ServoParser.webidl
@@ -1,11 +1,10 @@
 /* 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 http://mozilla.org/MPL/2.0/. */
 
 // This interface is entirely internal to Servo, and should not be accessible to
 // web pages.
 
-[NoInterfaceObject, Exposed=(Window,Worker)]
-interface ServoXMLParser {
-};
-
+[Exposed=(Window,Worker),
+ NoInterfaceObject]
+interface ServoParser {};
--- a/servo/components/script/dom/xmlhttprequest.rs
+++ b/servo/components/script/dom/xmlhttprequest.rs
@@ -23,16 +23,18 @@ use dom::blob::{Blob, BlobImpl};
 use dom::document::{Document, IsHTMLDocument};
 use dom::document::DocumentSource;
 use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::eventtarget::EventTarget;
 use dom::globalscope::GlobalScope;
 use dom::headers::is_forbidden_header_name;
 use dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
 use dom::progressevent::ProgressEvent;
+use dom::servoparser::html::{ParseContext, parse_html};
+use dom::servoparser::xml::{self, parse_xml};
 use dom::window::Window;
 use dom::workerglobalscope::WorkerGlobalScope;
 use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
 use dom::xmlhttprequestupload::XMLHttpRequestUpload;
 use encoding::all::UTF_8;
 use encoding::label::encoding_from_whatwg_label;
 use encoding::types::{DecoderTrap, EncoderTrap, Encoding, EncodingRef};
 use euclid::length::Length;
@@ -48,18 +50,16 @@ use js::jsapi::JS_ClearPendingException;
 use js::jsval::{JSVal, NullValue, UndefinedValue};
 use msg::constellation_msg::{PipelineId, ReferrerPolicy};
 use net_traits::{CoreResourceThread, FetchMetadata, FilteredMetadata};
 use net_traits::{FetchResponseListener, LoadOrigin, NetworkError};
 use net_traits::CoreResourceMsg::Fetch;
 use net_traits::request::{CredentialsMode, Destination, RequestInit, RequestMode};
 use net_traits::trim_http_whitespace;
 use network_listener::{NetworkListener, PreInvoke};
-use parse::html::{ParseContext, parse_html};
-use parse::xml::{self, parse_xml};
 use script_runtime::ScriptChan;
 use std::ascii::AsciiExt;
 use std::borrow::ToOwned;
 use std::cell::Cell;
 use std::default::Default;
 use std::str;
 use std::sync::{Arc, Mutex};
 use string_cache::Atom;
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -100,17 +100,16 @@ mod devtools;
 pub mod document_loader;
 #[macro_use]
 pub mod dom;
 pub mod fetch;
 pub mod layout_wrapper;
 mod mem;
 mod network_listener;
 pub mod origin;
-pub mod parse;
 pub mod script_runtime;
 #[allow(unsafe_code)]
 pub mod script_thread;
 mod serviceworker_manager;
 mod task_source;
 pub mod textinput;
 mod timers;
 mod unpremultiplytable;
deleted file mode 100644
--- a/servo/components/script/parse/mod.rs
+++ /dev/null
@@ -1,177 +0,0 @@
-/* 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 http://mozilla.org/MPL/2.0/. */
-
-use dom::bindings::cell::DOMRefCell;
-use dom::bindings::js::{JS, Root};
-use dom::bindings::refcounted::Trusted;
-use dom::document::Document;
-use dom::servohtmlparser::ServoHTMLParser;
-use dom::servoxmlparser::ServoXMLParser;
-use dom::window::Window;
-use std::cell::Cell;
-use std::cell::UnsafeCell;
-use std::ptr;
-
-pub mod html;
-pub mod xml;
-
-pub trait Parser {
-    fn parse_chunk(self, input: String);
-    fn finish(self);
-}
-
-#[must_root]
-#[derive(JSTraceable, HeapSizeOf)]
-pub enum ParserField {
-    HTML(JS<ServoHTMLParser>),
-    XML(JS<ServoXMLParser>),
-}
-
-#[must_root]
-#[derive(JSTraceable, HeapSizeOf)]
-pub struct MutNullableParserField {
-    #[ignore_heap_size_of = "XXXjdm"]
-    ptr: UnsafeCell<Option<ParserField>>,
-}
-
-impl Default for MutNullableParserField {
-    #[allow(unrooted_must_root)]
-    fn default() -> MutNullableParserField {
-        MutNullableParserField {
-            ptr: UnsafeCell::new(None),
-        }
-    }
-}
-
-impl MutNullableParserField {
-    #[allow(unsafe_code)]
-    pub fn set(&self, val: Option<ParserRef>) {
-        unsafe {
-            *self.ptr.get() = val.map(|val| {
-                match val {
-                    ParserRef::HTML(parser) => ParserField::HTML(JS::from_ref(parser)),
-                    ParserRef::XML(parser) => ParserField::XML(JS::from_ref(parser)),
-                }
-            });
-        }
-    }
-
-    #[allow(unsafe_code, unrooted_must_root)]
-    pub fn get(&self) -> Option<ParserRoot> {
-        unsafe {
-            ptr::read(self.ptr.get()).map(|o| {
-                match o {
-                    ParserField::HTML(parser) => ParserRoot::HTML(Root::from_ref(&*parser)),
-                    ParserField::XML(parser) => ParserRoot::XML(Root::from_ref(&*parser)),
-                }
-            })
-        }
-    }
-}
-
-pub enum ParserRoot {
-    HTML(Root<ServoHTMLParser>),
-    XML(Root<ServoXMLParser>),
-}
-
-impl ParserRoot {
-    pub fn r(&self) -> ParserRef {
-        match *self {
-            ParserRoot::HTML(ref parser) => ParserRef::HTML(parser.r()),
-            ParserRoot::XML(ref parser) => ParserRef::XML(parser.r()),
-        }
-    }
-}
-
-pub enum TrustedParser {
-    HTML(Trusted<ServoHTMLParser>),
-    XML(Trusted<ServoXMLParser>),
-}
-
-impl TrustedParser {
-    pub fn root(&self) -> ParserRoot {
-        match *self {
-            TrustedParser::HTML(ref parser) => ParserRoot::HTML(parser.root()),
-            TrustedParser::XML(ref parser) => ParserRoot::XML(parser.root()),
-        }
-    }
-}
-
-pub enum ParserRef<'a> {
-    HTML(&'a ServoHTMLParser),
-    XML(&'a ServoXMLParser),
-}
-
-impl<'a> ParserRef<'a> {
-    pub fn parse_chunk(&self, input: String) {
-        match *self {
-            ParserRef::HTML(parser) => parser.parse_chunk(input),
-            ParserRef::XML(parser) => parser.parse_chunk(input),
-        }
-    }
-
-    pub fn window(&self) -> &Window {
-        match *self {
-            ParserRef::HTML(parser) => parser.window(),
-            ParserRef::XML(parser) => parser.window(),
-        }
-    }
-
-    pub fn resume(&self) {
-        match *self {
-            ParserRef::HTML(parser) => parser.resume(),
-            ParserRef::XML(parser) => parser.resume(),
-        }
-    }
-
-    pub fn suspend(&self) {
-        match *self {
-            ParserRef::HTML(parser) => parser.suspend(),
-            ParserRef::XML(parser) => parser.suspend(),
-        }
-    }
-
-    pub fn is_suspended(&self) -> bool {
-        match *self {
-            ParserRef::HTML(parser) => parser.is_suspended(),
-            ParserRef::XML(parser) => parser.is_suspended(),
-        }
-    }
-
-    pub fn pending_input(&self) -> &DOMRefCell<Vec<String>> {
-        match *self {
-            ParserRef::HTML(parser) => parser.pending_input(),
-            ParserRef::XML(parser) => parser.pending_input(),
-        }
-    }
-
-    pub fn set_plaintext_state(&self) {
-        match *self {
-            ParserRef::HTML(parser) => parser.set_plaintext_state(),
-            ParserRef::XML(parser) => parser.set_plaintext_state(),
-        }
-    }
-
-    pub fn parse_sync(&self) {
-        match *self {
-            ParserRef::HTML(parser) => parser.parse_sync(),
-            ParserRef::XML(parser) => parser.parse_sync(),
-        }
-    }
-
-    pub fn document(&self) -> &Document {
-        match *self {
-            ParserRef::HTML(parser) => parser.document(),
-            ParserRef::XML(parser) => parser.document(),
-        }
-    }
-
-    pub fn last_chunk_received(&self) -> &Cell<bool> {
-        match *self {
-            ParserRef::HTML(parser) => parser.last_chunk_received(),
-            ParserRef::XML(parser) => parser.last_chunk_received(),
-        }
-    }
-}
-
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -39,17 +39,19 @@ use dom::browsingcontext::BrowsingContex
 use dom::document::{Document, DocumentProgressHandler, DocumentSource, FocusType, IsHTMLDocument, TouchEventResult};
 use dom::element::Element;
 use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::globalscope::GlobalScope;
 use dom::htmlanchorelement::HTMLAnchorElement;
 use dom::node::{Node, NodeDamage, window_from_node};
 use dom::serviceworker::TrustedServiceWorkerAddress;
 use dom::serviceworkerregistration::ServiceWorkerRegistration;
-use dom::servohtmlparser::ParserContext;
+use dom::servoparser::{ParserContext, ServoParser};
+use dom::servoparser::html::{ParseContext, parse_html};
+use dom::servoparser::xml::{self, parse_xml};
 use dom::uievent::UIEvent;
 use dom::window::{ReflowReason, Window};
 use dom::worker::TrustedWorkerAddress;
 use euclid::Rect;
 use euclid::point::Point2D;
 use gfx_traits::LayerId;
 use hyper::header::{ContentType, Headers, HttpDate, LastModified};
 use hyper::header::ReferrerPolicy as ReferrerPolicyHeader;
@@ -66,19 +68,16 @@ use js::rust::Runtime;
 use mem::heap_size_of_self_and_children;
 use msg::constellation_msg::{FrameType, LoadData, PipelineId, PipelineNamespace};
 use msg::constellation_msg::{ReferrerPolicy, WindowSizeType};
 use net_traits::{AsyncResponseTarget, CoreResourceMsg, LoadConsumer, LoadContext, Metadata, ResourceThreads};
 use net_traits::{IpcSend, LoadData as NetLoadData};
 use net_traits::bluetooth_thread::BluetoothMethodMsg;
 use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheResult, ImageCacheThread};
 use network_listener::NetworkListener;
-use parse::ParserRoot;
-use parse::html::{ParseContext, parse_html};
-use parse::xml::{self, parse_xml};
 use profile_traits::mem::{self, OpaqueSender, Report, ReportKind, ReportsChan};
 use profile_traits::time::{self, ProfilerCategory, profile};
 use script_layout_interface::message::{self, NewLayoutThreadInfo, ReflowQueryType};
 use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory, EnqueuedPromiseCallback};
 use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx, PromiseJobQueue};
 use script_traits::{CompositorEvent, ConstellationControlMsg, EventResult};
 use script_traits::{InitialScriptState, MouseButton, MouseEventType, MozBrowserEvent};
 use script_traits::{NewLayoutInfo, ScriptMsg as ConstellationMsg};
@@ -485,17 +484,17 @@ impl ScriptThreadFactory for ScriptThrea
         });
 
         (sender, receiver)
     }
 }
 
 impl ScriptThread {
     pub fn page_headers_available(id: &PipelineId, metadata: Option<Metadata>)
-                                  -> Option<ParserRoot> {
+                                  -> Option<Root<ServoParser>> {
         SCRIPT_THREAD_ROOT.with(|root| {
             let script_thread = unsafe { &*root.get().unwrap() };
             script_thread.handle_page_headers_available(id, metadata)
         })
     }
 
     // stores a service worker registration
     pub fn set_registration(scope_url: Url, registration:&ServiceWorkerRegistration, pipeline_id: PipelineId) {
@@ -1409,17 +1408,17 @@ impl ScriptThread {
         // so this can afford to be naive and just shut down the
         // constellation. In the future it'll need to be smarter.
         self.constellation_chan.send(ConstellationMsg::Exit).unwrap();
     }
 
     /// We have received notification that the response associated with a load has completed.
     /// Kick off the document and frame tree creation process using the result.
     fn handle_page_headers_available(&self, id: &PipelineId,
-                                     metadata: Option<Metadata>) -> Option<ParserRoot> {
+                                     metadata: Option<Metadata>) -> Option<Root<ServoParser>> {
         let idx = self.incomplete_loads.borrow().iter().position(|load| { load.pipeline_id == *id });
         // The matching in progress load structure may not exist if
         // the pipeline exited before the page load completed.
         match idx {
             Some(idx) => {
                 let load = self.incomplete_loads.borrow_mut().remove(idx);
                 metadata.map(|meta| self.load(meta, load))
             }
@@ -1539,17 +1538,17 @@ impl ScriptThread {
         };
         if let Some(iframe) = document.find_iframe(id) {
             iframe.iframe_load_event_steps(id);
         }
     }
 
     /// The entry point to document loading. Defines bindings, sets up the window and document
     /// objects, parses HTML and CSS, and kicks off initial layout.
-    fn load(&self, metadata: Metadata, incomplete: InProgressLoad) -> ParserRoot {
+    fn load(&self, metadata: Metadata, incomplete: InProgressLoad) -> Root<ServoParser> {
         let final_url = metadata.final_url.clone();
         {
             // send the final url to the layout thread.
             incomplete.layout_chan
                       .send(message::Msg::SetFinalUrl(final_url.clone()))
                       .unwrap();
 
             // update the pipeline url