Switch to synchronous script loading

This removes the old code for asyncronously loading scripts during HTML
parsing and then executing them afterward.

Fixes #3356.
This commit is contained in:
Matt Brubeck 2014-10-17 09:48:31 -07:00
parent 65a0d1fe9a
commit fe123ad07c
39 changed files with 284 additions and 1006 deletions

View file

@ -55,7 +55,7 @@ use dom::range::Range;
use dom::treewalker::TreeWalker;
use dom::uievent::UIEvent;
use dom::window::{Window, WindowHelpers};
use parse::html::build_element_from_tag;
use parse::html::{build_element_from_tag, ScriptCreated};
use servo_util::namespace;
use servo_util::str::{DOMString, split_html_space_chars};
@ -529,7 +529,7 @@ impl<'a> DocumentMethods for JSRef<'a, Document> {
}
let local_name = local_name.as_slice().to_ascii_lower();
let name = QualName::new(ns!(HTML), Atom::from_slice(local_name.as_slice()));
Ok(build_element_from_tag(name, None, self))
Ok(build_element_from_tag(name, None, self, ScriptCreated))
}
// http://dom.spec.whatwg.org/#dom-document-createelementns
@ -574,7 +574,8 @@ impl<'a> DocumentMethods for JSRef<'a, Document> {
if ns == ns!(HTML) {
let name = QualName::new(ns!(HTML), Atom::from_slice(local_name_from_qname));
Ok(build_element_from_tag(name, prefix_from_qname.map(|s| s.to_string()), self))
Ok(build_element_from_tag(name, prefix_from_qname.map(|s| s.to_string()), self,
ScriptCreated))
} else {
Ok(Element::new(local_name_from_qname.to_string(), ns,
prefix_from_qname.map(|s| s.to_string()), self))

View file

@ -2,12 +2,14 @@
* 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::attr::Attr;
use dom::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::HTMLScriptElementBinding;
use dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::InheritTypes::HTMLScriptElementDerived;
use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast};
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::bindings::utils::{Reflectable, Reflector};
use dom::document::Document;
@ -15,10 +17,12 @@ use dom::element::{HTMLScriptElementTypeId, Element, AttributeHandlers};
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::window::WindowHelpers;
use encoding::all::UTF_8;
use encoding::types::{Encoding, DecodeReplace};
use parse::html::{ElementCreator, ParserCreated};
use servo_net::resource_task::load_whole_resource;
use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
use std::cell::Cell;
@ -53,20 +57,20 @@ impl HTMLScriptElementDerived for EventTarget {
impl HTMLScriptElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>,
parser_inserted: bool) -> HTMLScriptElement {
creator: ElementCreator) -> HTMLScriptElement {
HTMLScriptElement {
htmlelement: HTMLElement::new_inherited(HTMLScriptElementTypeId, localName, prefix, document),
already_started: Cell::new(false),
parser_inserted: Cell::new(parser_inserted),
non_blocking: Cell::new(!parser_inserted),
parser_inserted: Cell::new(creator == ParserCreated),
non_blocking: Cell::new(creator != ParserCreated),
ready_to_be_parser_executed: Cell::new(false),
}
}
#[allow(unrooted_must_root)]
pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>,
parser_inserted: bool) -> Temporary<HTMLScriptElement> {
let element = HTMLScriptElement::new_inherited(localName, prefix, document, parser_inserted);
creator: ElementCreator) -> Temporary<HTMLScriptElement> {
let element = HTMLScriptElement::new_inherited(localName, prefix, document, creator);
Node::reflect_node(box element, document, HTMLScriptElementBinding::Wrap)
}
}
@ -77,6 +81,9 @@ pub trait HTMLScriptElementHelpers {
/// Prepare a script, steps 6 and 7.
fn is_javascript(self) -> bool;
/// Set the "already started" flag (<https://whatwg.org/html/#already-started>)
fn mark_already_started(self);
}
/// Supported script types as defined by
@ -232,6 +239,50 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
}
}
}
fn mark_already_started(self) {
self.already_started.set(true);
}
}
impl<'a> VirtualMethods for JSRef<'a, HTMLScriptElement> {
fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self);
Some(htmlelement as &VirtualMethods)
}
fn after_set_attr(&self, attr: JSRef<Attr>) {
match self.super_type() {
Some(ref s) => s.after_set_attr(attr),
_ => (),
}
let node: JSRef<Node> = NodeCast::from_ref(*self);
if attr.local_name() == &atom!("src") && !self.parser_inserted.get() && node.is_in_doc() {
self.prepare();
}
}
fn child_inserted(&self, child: JSRef<Node>) {
match self.super_type() {
Some(ref s) => s.child_inserted(child),
_ => (),
}
let node: JSRef<Node> = NodeCast::from_ref(*self);
if !self.parser_inserted.get() && node.is_in_doc() {
self.prepare();
}
}
fn bind_to_tree(&self, tree_in_doc: bool) {
match self.super_type() {
Some(ref s) => s.bind_to_tree(tree_in_doc),
_ => ()
}
if tree_in_doc && !self.parser_inserted.get() {
self.prepare();
}
}
}
impl<'a> HTMLScriptElementMethods for JSRef<'a, HTMLScriptElement> {

View file

@ -45,7 +45,7 @@ use dom::text::Text;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use dom::window::Window;
use geom::rect::Rect;
use parse::html::build_element_from_tag;
use parse::html::{build_element_from_tag, ScriptCreated};
use layout_interface::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC,
LayoutChan, ReapLayoutDataMsg};
use devtools_traits::NodeInfo;
@ -1521,7 +1521,7 @@ impl Node {
local: element.local_name().clone()
};
let element = build_element_from_tag(name,
Some(element.prefix().as_slice().to_string()), *document);
Some(element.prefix().as_slice().to_string()), *document, ScriptCreated);
NodeCast::from_temporary(element)
},
TextNodeTypeId => {

View file

@ -13,7 +13,6 @@ use dom::bindings::js::{JS, JSRef, Temporary};
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::node::TrustedNodeAddress;
use dom::document::{Document, DocumentHelpers};
use parse::html::JSMessage;
use parse::Parser;
use servo_util::task_state;
@ -28,7 +27,6 @@ use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts};
#[must_root]
#[jstraceable]
pub struct Sink {
pub js_chan: Sender<JSMessage>,
pub base_url: Option<Url>,
pub document: JS<Document>,
}
@ -55,11 +53,9 @@ impl Parser for ServoHTMLParser{
impl ServoHTMLParser {
#[allow(unrooted_must_root)]
pub fn new(js_chan: Sender<JSMessage>, base_url: Option<Url>, document: JSRef<Document>)
-> Temporary<ServoHTMLParser> {
pub fn new(base_url: Option<Url>, document: JSRef<Document>) -> Temporary<ServoHTMLParser> {
let window = document.window().root();
let sink = Sink {
js_chan: js_chan,
base_url: base_url,
document: JS::from_rooted(document),
};

View file

@ -18,6 +18,7 @@ use dom::bindings::codegen::InheritTypes::HTMLLinkElementCast;
use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast;
use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementCast;
use dom::bindings::codegen::InheritTypes::HTMLOptionElementCast;
use dom::bindings::codegen::InheritTypes::HTMLScriptElementCast;
use dom::bindings::codegen::InheritTypes::HTMLSelectElementCast;
use dom::bindings::codegen::InheritTypes::HTMLStyleElementCast;
use dom::bindings::codegen::InheritTypes::HTMLTableCellElementCast;
@ -37,6 +38,7 @@ use dom::element::HTMLLinkElementTypeId;
use dom::element::HTMLObjectElementTypeId;
use dom::element::HTMLOptGroupElementTypeId;
use dom::element::HTMLOptionElementTypeId;
use dom::element::HTMLScriptElementTypeId;
use dom::element::HTMLSelectElementTypeId;
use dom::element::HTMLStyleElementTypeId;
use dom::element::HTMLTableDataCellElementTypeId;
@ -56,6 +58,7 @@ use dom::htmllinkelement::HTMLLinkElement;
use dom::htmlobjectelement::HTMLObjectElement;
use dom::htmloptgroupelement::HTMLOptGroupElement;
use dom::htmloptionelement::HTMLOptionElement;
use dom::htmlscriptelement::HTMLScriptElement;
use dom::htmlselectelement::HTMLSelectElement;
use dom::htmlstyleelement::HTMLStyleElement;
use dom::htmltablecellelement::HTMLTableCellElement;
@ -189,6 +192,10 @@ pub fn vtable_for<'a>(node: &'a JSRef<'a, Node>) -> &'a VirtualMethods + 'a {
let element: &'a JSRef<'a, HTMLOptionElement> = HTMLOptionElementCast::to_borrowed_ref(node).unwrap();
element as &'a VirtualMethods + 'a
}
ElementNodeTypeId(HTMLScriptElementTypeId) => {
let element: &'a JSRef<'a, HTMLScriptElement> = HTMLScriptElementCast::to_borrowed_ref(node).unwrap();
element as &'a VirtualMethods + 'a
}
ElementNodeTypeId(HTMLSelectElementTypeId) => {
let element: &'a JSRef<'a, HTMLSelectElement> = HTMLSelectElementCast::to_borrowed_ref(node).unwrap();
element as &'a VirtualMethods + 'a

View file

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast, HTMLScriptElementCast};
use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, Root};
@ -23,82 +22,26 @@ use parse::Parser;
use encoding::all::UTF_8;
use encoding::types::{Encoding, DecodeReplace};
use servo_net::resource_task::{Load, LoadData, Payload, Done, ResourceTask, load_whole_resource};
use servo_net::resource_task::{Load, LoadData, Payload, Done, ResourceTask};
use servo_msg::constellation_msg::LoadData as MsgLoadData;
use servo_util::task::spawn_named;
use servo_util::task_state;
use servo_util::task_state::InHTMLParser;
use servo_util::str::DOMString;
use std::ascii::StrAsciiExt;
use std::comm::{channel, Sender, Receiver};
use std::comm::channel;
use std::str::MaybeOwned;
use url::{Url, UrlParser};
use url::Url;
use http::headers::HeaderEnum;
use time;
use html5ever::Attribute;
use html5ever::tree_builder::{TreeSink, QuirksMode, NodeOrText, AppendNode, AppendText};
use string_cache::QualName;
pub struct JSFile {
pub data: String,
pub url: Option<Url>,
}
pub type JSResult = Vec<JSFile>;
pub enum HTMLInput {
InputString(String),
InputUrl(Url),
}
pub enum JSMessage {
JSTaskNewFile(Url),
JSTaskNewInlineScript(String, Option<Url>),
JSTaskExit
}
/// Messages generated by the HTML parser upon discovery of additional resources
pub enum HtmlDiscoveryMessage {
HtmlDiscoveredScript(JSResult)
}
pub struct HtmlParserResult {
pub discovery_port: Receiver<HtmlDiscoveryMessage>,
}
fn js_script_listener(to_parent: Sender<HtmlDiscoveryMessage>,
from_parent: Receiver<JSMessage>,
resource_task: ResourceTask) {
let mut result_vec = vec!();
loop {
match from_parent.recv_opt() {
Ok(JSTaskNewFile(url)) => {
match load_whole_resource(&resource_task, url.clone()) {
Err(_) => {
error!("error loading script {:s}", url.serialize());
}
Ok((metadata, bytes)) => {
let decoded = UTF_8.decode(bytes.as_slice(), DecodeReplace).unwrap();
result_vec.push(JSFile {
data: decoded.to_string(),
url: Some(metadata.final_url),
});
}
}
}
Ok(JSTaskNewInlineScript(data, url)) => {
result_vec.push(JSFile { data: data, url: url });
}
Ok(JSTaskExit) | Err(()) => {
break;
}
}
}
assert!(to_parent.send_opt(HtmlDiscoveredScript(result_vec)).is_ok());
}
// Parses an RFC 2616 compliant date/time string, and returns a localized
// date/time string in a format suitable for document.lastModified.
fn parse_last_modified(timestamp: &str) -> String {
@ -123,9 +66,16 @@ fn parse_last_modified(timestamp: &str) -> String {
}
}
#[deriving(PartialEq)]
pub enum ElementCreator {
ParserCreated,
ScriptCreated,
}
pub fn build_element_from_tag(name: QualName,
prefix: Option<DOMString>,
document: JSRef<Document>) -> Temporary<Element> {
document: JSRef<Document>,
creator: ElementCreator) -> Temporary<Element> {
if name.ns != ns!(HTML) {
return Element::new(name.local.as_slice().to_string(), name.ns, None, document);
}
@ -233,7 +183,7 @@ pub fn build_element_from_tag(name: QualName,
atom!("ruby") => make!(HTMLElement),
atom!("s") => make!(HTMLElement),
atom!("samp") => make!(HTMLElement),
atom!("script") => make!(HTMLScriptElement, true),
atom!("script") => make!(HTMLScriptElement, creator),
atom!("section") => make!(HTMLElement),
atom!("select") => make!(HTMLSelectElement),
atom!("small") => make!(HTMLElement),
@ -307,7 +257,7 @@ impl<'a> TreeSink<TrustedNodeAddress> for servohtmlparser::Sink {
fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>)
-> TrustedNodeAddress {
let doc = self.document.root();
let elem = build_element_from_tag(name, None, *doc).root();
let elem = build_element_from_tag(name, None, *doc, ParserCreated).root();
for attr in attrs.into_iter() {
elem.set_attribute_from_parser(attr.name, attr.value, None);
@ -378,42 +328,16 @@ impl<'a> TreeSink<TrustedNodeAddress> for servohtmlparser::Sink {
error!("remove_from_parent not implemented!");
}
fn mark_script_already_started(&mut self, _node: TrustedNodeAddress) {
error!("mark_script_already_started not implemented!");
fn mark_script_already_started(&mut self, node: TrustedNodeAddress) {
let node: Root<Node> = unsafe { JS::from_trusted_node_address(node).root() };
let script: Option<JSRef<HTMLScriptElement>> = HTMLScriptElementCast::to_ref(*node);
script.map(|script| script.mark_already_started());
}
fn complete_script(&mut self, node: TrustedNodeAddress) {
let node: Root<Node> = unsafe { JS::from_trusted_node_address(node).root() };
let script: Option<JSRef<HTMLScriptElement>> =
HTMLScriptElementCast::to_ref(*node);
let script = match script {
Some(script) if script.is_javascript() => script,
_ => return,
};
let script_element: JSRef<Element> = ElementCast::from_ref(script);
match script_element.get_attribute(ns!(""), &atom!("src")).root() {
Some(src) => {
debug!("found script: {:s}", src.deref().Value());
let mut url_parser = UrlParser::new();
match self.base_url {
None => (),
Some(ref base_url) => {
url_parser.base_url(base_url);
}
};
match url_parser.parse(src.deref().value().as_slice()) {
Ok(new_url) => self.js_chan.send(JSTaskNewFile(new_url)),
Err(e) => debug!("Parsing url {:s} failed: {:?}", src.deref().Value(), e)
};
}
None => {
let scriptnode: JSRef<Node> = NodeCast::from_ref(script);
let data = Node::collect_text_contents(scriptnode.children());
debug!("script data = {:?}", data);
self.js_chan.send(JSTaskNewInlineScript(data, self.base_url.clone()));
}
}
let script: Option<JSRef<HTMLScriptElement>> = HTMLScriptElementCast::to_ref(*node);
script.map(|script| script.prepare());
}
}
@ -422,17 +346,7 @@ pub fn parse_html(page: &Page,
document: JSRef<Document>,
input: HTMLInput,
resource_task: ResourceTask,
msg_load_data: Option<MsgLoadData>)
-> HtmlParserResult {
// Spawn a JS parser to receive JavaScript.
let (discovery_chan, discovery_port) = channel();
let resource_task2 = resource_task.clone();
let js_result_chan = discovery_chan.clone();
let (js_chan, js_msg_port) = channel();
spawn_named("parse_html:js", proc() {
js_script_listener(js_result_chan, js_msg_port, resource_task2.clone());
});
msg_load_data: Option<MsgLoadData>) {
let (base_url, load_response) = match input {
InputUrl(ref url) => {
// Wait for the LoadResponse so that the parser knows the final URL.
@ -480,7 +394,7 @@ pub fn parse_html(page: &Page,
},
};
let parser = ServoHTMLParser::new(js_chan.clone(), base_url.clone(), document).root();
let parser = ServoHTMLParser::new(base_url.clone(), document).root();
let parser: JSRef<ServoHTMLParser> = *parser;
task_state::enter(InHTMLParser);
@ -520,9 +434,4 @@ pub fn parse_html(page: &Page,
task_state::exit(InHTMLParser);
debug!("finished parsing");
js_chan.send(JSTaskExit);
HtmlParserResult {
discovery_port: discovery_port,
}
}

View file

@ -16,7 +16,6 @@ use dom::bindings::conversions::{FromJSValConvertible, Empty};
use dom::bindings::global;
use dom::bindings::js::{JS, JSRef, RootCollection, Temporary, OptionalRootable};
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::Reflectable;
use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap};
use dom::document::{Document, HTMLDocument, DocumentHelpers, FromParser};
use dom::element::{Element, HTMLButtonElementTypeId, HTMLInputElementTypeId};
@ -29,7 +28,7 @@ use dom::node::{ElementNodeTypeId, Node, NodeHelpers};
use dom::window::{Window, WindowHelpers};
use dom::worker::{Worker, TrustedWorkerAddress};
use dom::xmlhttprequest::{TrustedXHRAddress, XMLHttpRequest, XHRProgress};
use parse::html::{InputString, InputUrl, HtmlParserResult, HtmlDiscoveredScript, parse_html};
use parse::html::{InputString, InputUrl, parse_html};
use layout_interface::{ScriptLayoutChan, LayoutChan, ReflowForDisplay};
use layout_interface;
use page::{Page, IterablePage, Frame};
@ -62,7 +61,6 @@ use js::jsapi::{JSContext, JSRuntime, JSTracer};
use js::jsapi::{JS_SetGCParameter, JSGC_MAX_BYTES};
use js::jsapi::{JS_SetGCCallback, JSGCStatus, JSGC_BEGIN, JSGC_END};
use js::rust::{Cx, RtUtils};
use js::rust::with_compartment;
use js;
use url::Url;
@ -797,13 +795,6 @@ impl ScriptTask {
InputString(strval.unwrap_or("".to_string()))
};
// Parse HTML.
//
// Note: We can parse the next document in parallel with any previous documents.
let HtmlParserResult { discovery_port }
= parse_html(&*page, *document, parser_input, self.resource_task.clone(),
Some(load_data));
{
// Create the root frame.
let mut frame = page.mut_frame();
@ -813,18 +804,9 @@ impl ScriptTask {
});
}
document.set_ready_state(DocumentReadyStateValues::Interactive);
parse_html(&*page, *document, parser_input, self.resource_task.clone(), Some(load_data));
let mut js_scripts = None;
loop {
match discovery_port.recv_opt() {
Ok(HtmlDiscoveredScript(scripts)) => {
assert!(js_scripts.is_none());
js_scripts = Some(scripts);
}
Err(()) => break
}
}
document.set_ready_state(DocumentReadyStateValues::Interactive);
// Kick off the initial reflow of the page.
debug!("kicking off initial reflow of {}", url);
@ -841,31 +823,6 @@ impl ScriptTask {
*page_url = Some((url.clone(), false));
}
// Receive the JavaScript scripts.
assert!(js_scripts.is_some());
let js_scripts = js_scripts.take().unwrap();
debug!("js_scripts: {:?}", js_scripts);
with_compartment((**cx).ptr, window.reflector().get_jsobject(), || {
// Evaluate every script in the document.
for file in js_scripts.iter() {
let global_obj = window.reflector().get_jsobject();
let filename = match file.url {
None => String::new(),
Some(ref url) => url.serialize(),
};
//FIXME: this should have some kind of error handling, or explicitly
// drop an exception on the floor.
match cx.evaluate_script(global_obj, file.data.clone(), filename, 1) {
Ok(_) => (),
Err(_) => println!("evaluate_script failed")
}
window.flush_layout(ReflowForDisplay);
}
});
// https://html.spec.whatwg.org/multipage/#the-end step 4
let event = Event::new(&global::Window(*window), "DOMContentLoaded".to_string(),
DoesNotBubble, NotCancelable).root();