Make external script sources load asynchronously, yet still block further parsing. Hook up document loading to async networking events.

This commit is contained in:
Josh Matthews 2015-03-11 10:44:59 -04:00
parent e52197d126
commit 8082df7d0d
32 changed files with 441 additions and 276 deletions

View file

@ -28,17 +28,21 @@ use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers};
use dom::element::ElementTypeId;
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node, CloneChildrenFlag};
use dom::servohtmlparser::ServoHTMLParserHelpers;
use dom::virtualmethods::VirtualMethods;
use dom::window::{WindowHelpers, ScriptHelpers};
use script_task::{ScriptMsg, Runnable};
use network_listener::{NetworkListener, PreInvoke};
use script_task::{ScriptChan, ScriptMsg, Runnable};
use encoding::all::UTF_8;
use encoding::label::encoding_from_whatwg_label;
use encoding::types::{Encoding, EncodingRef, DecoderTrap};
use net_traits::Metadata;
use net_traits::{Metadata, AsyncResponseListener};
use util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
use std::borrow::ToOwned;
use std::cell::Cell;
use html5ever::tree_builder::NextParserState;
use std::cell::{RefCell, Cell};
use std::mem;
use std::sync::{Arc, Mutex};
use string_cache::Atom;
use url::{Url, UrlParser};
@ -99,7 +103,7 @@ impl HTMLScriptElement {
pub trait HTMLScriptElementHelpers {
/// Prepare a script (<https://www.whatwg.org/html/#prepare-a-script>)
fn prepare(self);
fn prepare(self) -> NextParserState;
/// [Execute a script block]
/// (https://html.spec.whatwg.org/multipage/#execute-the-script-block)
@ -153,12 +157,52 @@ pub enum ScriptOrigin {
External(Result<(Metadata, Vec<u8>), String>),
}
/// The context required for asynchronously loading an external script source.
struct ScriptContext {
/// The element that initiated the request.
elem: Trusted<HTMLScriptElement>,
/// The response body received to date.
data: RefCell<Vec<u8>>,
/// The response metadata received to date.
metadata: RefCell<Option<Metadata>>,
/// Whether the owning document's parser should resume once the response completes.
resume_on_completion: bool,
}
impl AsyncResponseListener for ScriptContext {
fn headers_available(&self, metadata: Metadata) {
*self.metadata.borrow_mut() = Some(metadata);
}
fn data_available(&self, payload: Vec<u8>) {
self.data.borrow_mut().extend(payload.into_iter());
}
fn response_complete(&self, status: Result<(), String>) {
let load = status.map(|_| {
let data = mem::replace(&mut *self.data.borrow_mut(), vec!());
let metadata = self.metadata.borrow_mut().take().unwrap();
(metadata, data)
});
let elem = self.elem.to_temporary().root();
elem.r().execute(ScriptOrigin::External(load));
if self.resume_on_completion {
let document = document_from_node(elem.r()).root();
document.r().get_current_parser().unwrap().root().r().resume();
}
}
}
impl PreInvoke for ScriptContext {}
impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
fn prepare(self) {
fn prepare(self) -> NextParserState {
// https://html.spec.whatwg.org/multipage/#prepare-a-script
// Step 1.
if self.already_started.get() {
return;
return NextParserState::Continue;
}
// Step 2.
let was_parser_inserted = self.parser_inserted.get();
@ -172,16 +216,16 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
// Step 4.
let text = self.Text();
if text.len() == 0 && !element.has_attribute(&atom!("src")) {
return;
return NextParserState::Continue;
}
// Step 5.
let node: JSRef<Node> = NodeCast::from_ref(self);
if !node.is_in_doc() {
return;
return NextParserState::Continue;
}
// Step 6, 7.
if !self.is_javascript() {
return;
return NextParserState::Continue;
}
// Step 8.
if was_parser_inserted {
@ -195,12 +239,12 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
let document_from_node_ref = document_from_node(self).root();
let document_from_node_ref = document_from_node_ref.r();
if self.parser_inserted.get() && self.parser_document.root().r() != document_from_node_ref {
return;
return NextParserState::Continue;
}
// Step 11.
if !document_from_node_ref.is_scripting_enabled() {
return;
return NextParserState::Continue;
}
// Step 12.
@ -212,13 +256,13 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
.to_ascii_lowercase();
let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
if for_value != "window" {
return;
return NextParserState::Continue;
}
let event_value = event_attribute.Value().to_ascii_lowercase();
let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
if event_value != "onload" && event_value != "onload()" {
return;
return NextParserState::Continue;
}
},
(_, _) => (),
@ -245,7 +289,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
// Step 14.2
if src.is_empty() {
self.queue_error_event();
return;
return NextParserState::Continue;
}
// Step 14.3
@ -254,7 +298,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
// Step 14.4
error!("error parsing URL for script {}", src);
self.queue_error_event();
return;
return NextParserState::Continue;
}
Ok(url) => {
// Step 14.5
@ -263,8 +307,28 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
// the origin of the script element's node document, and the default origin
// behaviour set to taint.
let doc = document_from_node(self).root();
let contents = doc.r().load_sync(LoadType::Script(url));
ScriptOrigin::External(contents)
let script_chan = window.script_chan();
let elem = Trusted::new(window.get_cx(), self, script_chan.clone());
let context = Arc::new(Mutex::new(ScriptContext {
elem: elem,
data: RefCell::new(vec!()),
metadata: RefCell::new(None),
resume_on_completion: self.parser_inserted.get(),
}));
let listener = box NetworkListener {
context: context,
script_chan: script_chan,
};
doc.r().load_async(LoadType::Script(url), listener);
if self.parser_inserted.get() {
doc.r().get_current_parser().unwrap().root().r().suspend();
}
return NextParserState::Suspend;
}
}
},
@ -275,6 +339,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
// TODO: Add support for the `defer` and `async` attributes. (For now, we fetch all
// scripts synchronously and execute them immediately.)
self.execute(load);
NextParserState::Continue
}
fn execute(self, load: ScriptOrigin) {