diff options
author | Josh Matthews <josh@joshmatthews.net> | 2015-03-11 10:44:59 -0400 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2015-05-20 14:22:09 -0400 |
commit | 8082df7d0da97f1951ae125956b962b92c98e69f (patch) | |
tree | 86131e200a39a6f85afcfff7fa8f6f904e94cc0b /components/script/dom/htmlscriptelement.rs | |
parent | e52197d1261055527a838f74b353a1124d6b077a (diff) | |
download | servo-8082df7d0da97f1951ae125956b962b92c98e69f.tar.gz servo-8082df7d0da97f1951ae125956b962b92c98e69f.zip |
Make external script sources load asynchronously, yet still block further parsing. Hook up document loading to async networking events.
Diffstat (limited to 'components/script/dom/htmlscriptelement.rs')
-rw-r--r-- | components/script/dom/htmlscriptelement.rs | 101 |
1 files changed, 83 insertions, 18 deletions
diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 2f7a52ec272..365d1a75e85 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -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) { |