diff --git a/components/profile/time.rs b/components/profile/time.rs index e3a550322c0..62f85b362d2 100644 --- a/components/profile/time.rs +++ b/components/profile/time.rs @@ -138,6 +138,7 @@ impl Formattable for ProfilerCategory { 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", diff --git a/components/profile_traits/time.rs b/components/profile_traits/time.rs index 64664265a1a..ec8a3617cd6 100644 --- a/components/profile_traits/time.rs +++ b/components/profile_traits/time.rs @@ -83,6 +83,7 @@ pub enum ProfilerCategory { ScriptWebSocketEvent = 0x73, ScriptWorkerEvent = 0x74, ScriptServiceWorkerEvent = 0x75, + ScriptParseXML = 0x76, ApplicationHeartbeat = 0x90, } diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 24826ced714..d4c12f8c2ae 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -2748,7 +2748,8 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); 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(): @@ -2757,7 +2758,7 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); 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()); diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 2fdd36667cf..242d2bf98c1 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -76,6 +76,7 @@ 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; @@ -103,7 +104,6 @@ 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}; @@ -226,7 +226,7 @@ pub struct Document { /// Tracks all outstanding loads related to this document. loader: DOMRefCell, /// The current active HTML parser, to allow resuming after interruptions. - current_parser: MutNullableParserField, + current_parser: MutNullableHeap>, /// When we should kick off a reflow. This happens during parsing. reflow_timeout: Cell>, /// The cached first `base` element with an `href` attribute. @@ -1627,11 +1627,11 @@ impl Document { global_scope.constellation_chan().send(load_event).unwrap(); } - pub fn set_current_parser(&self, script: Option) { + pub fn set_current_parser(&self, script: Option<&ServoParser>) { self.current_parser.set(script); } - pub fn get_current_parser(&self) -> Option { + pub fn get_current_parser(&self) -> Option> { self.current_parser.get() } diff --git a/components/script/dom/domparser.rs b/components/script/dom/domparser.rs index e73d9bf1aad..98019005c2b 100644 --- a/components/script/dom/domparser.rs +++ b/components/script/dom/domparser.rs @@ -18,9 +18,9 @@ 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 { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index ace4f979be6..afc91be7c4b 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -386,8 +386,7 @@ 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; diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index b52a8aec965..0bc6cdc267e 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -47,6 +47,7 @@ use dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHel 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}; @@ -59,7 +60,6 @@ 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}; diff --git a/components/script/parse/html.rs b/components/script/dom/servoparser/html.rs similarity index 87% rename from components/script/parse/html.rs rename to components/script/dom/servoparser/html.rs index 39684bd1b3e..46739c5dc59 100644 --- a/components/script/parse/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -23,20 +23,20 @@ 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>) { @@ -53,7 +53,7 @@ fn insert(parent: &Node, reference_child: Option<&Node>, child: NodeOrText TreeSink for servohtmlparser::Sink { +impl<'a> TreeSink for Sink { type Output = Self; fn finish(self) -> Self { self } @@ -246,6 +246,14 @@ impl<'a> Serializable for &'a Node { } } +/// 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), @@ -255,11 +263,40 @@ 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)); } diff --git a/components/script/dom/servohtmlparser.rs b/components/script/dom/servoparser/mod.rs similarity index 57% rename from components/script/dom/servohtmlparser.rs rename to components/script/dom/servoparser/mod.rs index a65d9e84a6d..252305619fc 100644 --- a/components/script/dom/servohtmlparser.rs +++ b/components/script/dom/servoparser/mod.rs @@ -2,15 +2,12 @@ * 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::codegen::Bindings::ServoParserBinding; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, Root}; use dom::bindings::refcounted::Trusted; @@ -21,12 +18,11 @@ 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 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; @@ -34,36 +30,288 @@ 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 profile_traits::time::{TimerMetadata, TimerMetadataFrameType}; +use profile_traits::time::{TimerMetadataReflowType, ProfilerCategory, profile}; use script_thread::ScriptThread; use std::cell::Cell; -use std::default::Default; 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, + /// The pipeline associated with this parse, unavailable if this parse + /// does not correspond to a page load. + pipeline: Option, + /// Input chunks received but not yet passed to the parser. + pending_input: DOMRefCell>, + /// The tokenizer of this parser. + tokenizer: DOMRefCell, + /// Whether to expect any further input from the associated network request. + last_chunk_received: Cell, + /// Whether this parser should avoid passing any further data to the tokenizer. + suspended: Cell, +} + +#[derive(PartialEq)] +enum LastChunkState { + Received, + NotReceived, +} + +impl ServoParser { + #[allow(unrooted_must_root)] + fn new_inherited( + document: &Document, + pipeline: Option, + 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, + tokenizer: Tokenizer, + last_chunk_state: LastChunkState) + -> Root { + 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 { + 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 { + 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::().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, Sink>> + ), + XML( + #[ignore_heap_size_of = "Defined in xml5ever"] + XmlTokenizer, Sink>> + ), +} + #[derive(JSTraceable, HeapSizeOf)] -pub struct Sink { - pub base_url: Option, +#[must_root] +struct Sink { + pub base_url: Url, pub document: JS, } -/// 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>, +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, + } + } } -pub type Tokenizer = tokenizer::Tokenizer, Sink>>; +impl JSTraceable for Tokenizer { + fn trace(&self, trc: *mut JSTracer) { + struct Tracer(*mut JSTracer); + let tracer = Tracer(trc); -/// The context required for asynchronously fetching a document and parsing it progressively. + match *self { + Tokenizer::HTML(ref tokenizer) => { + impl HtmlTracer for Tracer { + type Handle = JS; + #[allow(unrooted_must_root)] + fn trace_handle(&self, node: &JS) { + 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; + #[allow(unrooted_must_root)] + fn trace_handle(&self, node: JS) { + 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, + parser: Option>, /// Is this a synthesized document is_synthesized_document: bool, /// The pipeline associated with this document. @@ -105,19 +353,13 @@ impl AsyncResponseListener for ParserContext { 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)), - }); + self.parser = Some(Trusted::new(&*parser)); match content_type { Some(ContentType(Mime(TopLevel::Image, _, _))) => { self.is_synthesized_document = true; let page = "".into(); - parser.pending_input().borrow_mut().push(page); + parser.push_input_chunk(page); parser.parse_sync(); let doc = parser.document(); @@ -130,7 +372,7 @@ impl AsyncResponseListener for ParserContext { Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => { // https://html.spec.whatwg.org/multipage/#read-text let page = "
\n".into();
-                parser.pending_input().borrow_mut().push(page);
+                parser.push_input_chunk(page);
                 parser.parse_sync();
                 parser.set_plaintext_state();
             },
@@ -140,7 +382,7 @@ impl AsyncResponseListener for ParserContext {
                     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.push_input_chunk(page);
                     parser.parse_sync();
                 }
             },
@@ -155,7 +397,7 @@ impl AsyncResponseListener for ParserContext {
                 let page = format!("

Unknown content type ({}/{}).

", toplevel.as_str(), sublevel.as_str()); self.is_synthesized_document = true; - parser.pending_input().borrow_mut().push(page); + parser.push_input_chunk(page); parser.parse_sync(); }, None => { @@ -173,7 +415,7 @@ impl AsyncResponseListener for ParserContext { Some(parser) => parser.root(), None => return, }; - parser.r().parse_chunk(data); + parser.parse_chunk(data); } } @@ -187,245 +429,24 @@ impl AsyncResponseListener for ParserContext { // 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.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.r().document().finish_load(LoadType::PageSource(self.url.clone())); + parser.document() + .finish_load(LoadType::PageSource(self.url.clone())); - parser.r().last_chunk_received().set(true); - if !parser.r().is_suspended() { - parser.r().parse_sync(); + parser.mark_last_chunk_received(); + if !parser.is_suspended() { + parser.parse_sync(); } } } -impl PreInvoke for ParserContext { -} - -#[dom_struct] -pub struct ServoHTMLParser { - reflector_: Reflector, - #[ignore_heap_size_of = "Defined in html5ever"] - tokenizer: DOMRefCell, - /// Input chunks received but not yet passed to the parser. - pending_input: DOMRefCell>, - /// The document associated with this parser. - document: JS, - /// True if this parser should avoid passing any further data to the tokenizer. - suspended: Cell, - /// Whether to expect any further input from the associated network request. - last_chunk_received: Cell, - /// The pipeline associated with this parse, unavailable if this parse does not - /// correspond to a page load. - pipeline: Option, -} - -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, document: &Document, pipeline: Option) - -> Root { - 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, document: &Document, - fragment_context: FragmentContext) -> Root { - 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 { - &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> { - &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::().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 { - &self.last_chunk_received - } -} - -struct Tracer { - trc: *mut JSTracer, -} - -impl tree_builder::Tracer for Tracer { - type Handle = JS; - #[allow(unrooted_must_root)] - fn trace_handle(&self, node: &JS) { - 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>; - - let tree_builder = self.sink(); - tree_builder.trace_handles(tracer); - tree_builder.sink().trace(trc); - } -} +impl PreInvoke for ParserContext {} diff --git a/components/script/parse/xml.rs b/components/script/dom/servoparser/xml.rs similarity index 89% rename from components/script/parse/xml.rs rename to components/script/dom/servoparser/xml.rs index ee0959d04ef..0713fa5c58a 100644 --- a/components/script/parse/xml.rs +++ b/components/script/dom/servoparser/xml.rs @@ -15,20 +15,18 @@ 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; fn parse_error(&mut self, msg: Cow<'static, str>) { @@ -134,8 +132,16 @@ pub fn parse_xml(document: &Document, 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)); } diff --git a/components/script/dom/servoxmlparser.rs b/components/script/dom/servoxmlparser.rs deleted file mode 100644 index 2b79a5cf117..00000000000 --- a/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, Sink>>; - -#[must_root] -#[derive(JSTraceable, HeapSizeOf)] -pub struct Sink { - pub base_url: Option, - pub document: JS, -} - -#[must_root] -#[dom_struct] -pub struct ServoXMLParser { - reflector_: Reflector, - #[ignore_heap_size_of = "Defined in xml5ever"] - tokenizer: DOMRefCell, - /// Input chunks received but not yet passed to the parser. - pending_input: DOMRefCell>, - /// The document associated with this parser. - document: JS, - /// True if this parser should avoid passing any further data to the tokenizer. - suspended: Cell, - /// Whether to expect any further input from the associated network request. - last_chunk_received: Cell, - /// The pipeline associated with this parse, unavailable if this parse does not - /// correspond to a page load. - pipeline: Option, -} - -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, document: &Document, pipeline: Option) - -> Root { - 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> { - &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 { - &self.last_chunk_received - } - - pub fn tokenizer(&self) -> &DOMRefCell { - &self.tokenizer - } -} - -struct Tracer { - trc: *mut JSTracer, -} - -impl tree_builder::Tracer for Tracer { - type Handle = JS; - #[allow(unrooted_must_root)] - fn trace_handle(&self, node: JS) { - 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>; - - let tree_builder = self.sink(); - tree_builder.trace_handles(tracer); - tree_builder.sink().trace(trc); - } -} diff --git a/components/script/dom/webidls/ServoHTMLParser.webidl b/components/script/dom/webidls/ServoHTMLParser.webidl deleted file mode 100644 index b05515dcf3c..00000000000 --- a/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 { -}; diff --git a/components/script/dom/webidls/ServoXMLParser.webidl b/components/script/dom/webidls/ServoParser.webidl similarity index 79% rename from components/script/dom/webidls/ServoXMLParser.webidl rename to components/script/dom/webidls/ServoParser.webidl index 1111499de1b..c3b0926d824 100644 --- a/components/script/dom/webidls/ServoXMLParser.webidl +++ b/components/script/dom/webidls/ServoParser.webidl @@ -5,7 +5,6 @@ // 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 {}; diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index cd2cd37c476..7d4ce833f87 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -28,6 +28,8 @@ 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; @@ -53,8 +55,6 @@ 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; diff --git a/components/script/lib.rs b/components/script/lib.rs index c3832c646e2..d3161c410f3 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -105,7 +105,6 @@ 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; diff --git a/components/script/parse/mod.rs b/components/script/parse/mod.rs deleted file mode 100644 index 1f561df0e16..00000000000 --- a/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), - XML(JS), -} - -#[must_root] -#[derive(JSTraceable, HeapSizeOf)] -pub struct MutNullableParserField { - #[ignore_heap_size_of = "XXXjdm"] - ptr: UnsafeCell>, -} - -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) { - 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 { - 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), - XML(Root), -} - -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), - XML(Trusted), -} - -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> { - 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 { - match *self { - ParserRef::HTML(parser) => parser.last_chunk_received(), - ParserRef::XML(parser) => parser.last_chunk_received(), - } - } -} - diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 6e18216ddc4..87d4d148253 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -44,7 +44,9 @@ 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; @@ -71,9 +73,6 @@ 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}; @@ -490,7 +489,7 @@ impl ScriptThreadFactory for ScriptThread { impl ScriptThread { pub fn page_headers_available(id: &PipelineId, metadata: Option) - -> Option { + -> Option> { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; script_thread.handle_page_headers_available(id, metadata) @@ -1414,7 +1413,7 @@ impl ScriptThread { /// 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) -> Option { + metadata: Option) -> Option> { 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. @@ -1544,7 +1543,7 @@ impl ScriptThread { /// 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 { let final_url = metadata.final_url.clone(); { // send the final url to the layout thread.