Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement async fetching of extenal script sources via interruptible parsing. #5197

Closed
wants to merge 14 commits into from
Closed
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Make external script sources load asynchronously, yet still block fur…

…ther parsing. Hook up document loading to async networking events.
  • Loading branch information
jdm committed Mar 11, 2015
commit 1752c352ff001a2ca31c0d6c31a1dca0f2a16a24
@@ -54,6 +54,7 @@ use dom::nodelist::NodeList;
use dom::text::Text;
use dom::processinginstruction::ProcessingInstruction;
use dom::range::Range;
use dom::servohtmlparser::ServoHTMLParser;
use dom::treewalker::TreeWalker;
use dom::uievent::UIEvent;
use dom::window::{Window, WindowHelpers};
@@ -121,6 +122,8 @@ pub struct Document {
focused: MutNullableJS<Element>,
/// The script element that is currently executing.
current_script: MutNullableJS<HTMLScriptElement>,
/// The current active HTML parser, to allow resuming after interruptions.
current_parser: MutNullableJS<ServoHTMLParser>,
}

impl DocumentDerived for EventTarget {
@@ -218,6 +221,8 @@ pub trait DocumentHelpers<'a> {
fn handle_mouse_move_event(self, js_runtime: *mut JSRuntime, point: Point2D<f32>,
prev_mouse_over_targets: &mut Vec<JS<Node>>) -> bool;
fn set_current_script(self, script: Option<JSRef<HTMLScriptElement>>);
fn set_current_parser(self, script: Option<JSRef<ServoHTMLParser>>);
fn get_current_parser(self) -> Option<Temporary<ServoHTMLParser>>;
}

impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
@@ -670,6 +675,14 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
fn set_current_script(self, script: Option<JSRef<HTMLScriptElement>>) {
self.current_script.assign(script);
}

fn set_current_parser(self, script: Option<JSRef<ServoHTMLParser>>) {
self.current_parser.assign(script);
}

fn get_current_parser(self) -> Option<Temporary<ServoHTMLParser>> {
self.current_parser.get()
}
}

#[derive(PartialEq)]
@@ -737,6 +750,7 @@ impl Document {
possibly_focused: Default::default(),
focused: Default::default(),
current_script: Default::default(),
current_parser: Default::default(),
}
}

@@ -13,7 +13,7 @@ use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::document::{Document, DocumentHelpers, IsHTMLDocument};
use dom::document::DocumentSource;
use dom::window::{Window, WindowHelpers};
use parse::html::{HTMLInput, parse_html};
use parse::html::parse_html;
use util::str::DOMString;

use std::borrow::ToOwned;
@@ -57,7 +57,7 @@ impl<'a> DOMParserMethods for JSRef<'a, DOMParser> {
IsHTMLDocument::HTMLDocument,
Some(content_type),
DocumentSource::FromParser).root();
parse_html(document.r(), HTMLInput::InputString(s), &url);
parse_html(None, document.r(), s, &url);
document.r().set_ready_state(DocumentReadyState::Complete);
Ok(Temporary::from_rooted(document.r()))
}
@@ -24,16 +24,20 @@ 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::types::{Encoding, DecoderTrap};
use net::resource_task::{load_whole_resource, Metadata};
use net::resource_task::{AsyncResponseListener, Metadata, ControlMsg, LoadConsumer, LoadData};
use util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
use html5ever::tree_builder::NextParserState;
use std::borrow::ToOwned;
use std::cell::Cell;
use std::cell::{RefCell, Cell};
use std::sync::{Arc, Mutex};
use string_cache::Atom;
use url::{Url, UrlParser};

@@ -86,7 +90,7 @@ impl HTMLScriptElement {

pub trait HTMLScriptElementHelpers {
/// Prepare a script (<http://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)
@@ -140,12 +144,53 @@ 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<Option<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);
*self.data.borrow_mut() = Some(vec!());
}

fn data_available(&self, payload: Vec<u8>) {
self.data.borrow_mut().as_mut().unwrap().extend(payload.into_iter());
}

fn response_complete(&self, status: Result<(), String>) {
let load = status.map(|_| {
let data = self.data.borrow_mut().take().unwrap();
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/scripting.html#prepare-a-script
// Step 1.
if self.already_started.get() {
return;
return NextParserState::Continue;
}
// Step 2.
let was_parser_inserted = self.parser_inserted.get();
@@ -159,16 +204,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 {
@@ -190,7 +235,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
match element.get_attribute(ns!(""), &atom!("for")).root() {
Some(for_script) => {
if for_script.r().Value().to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) != "window" {
return;
return NextParserState::Continue;
}
}
_ => { }
@@ -201,7 +246,7 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
let event = event.r().Value().to_ascii_lowercase();
let event = event.trim_matches(HTML_SPACE_CHARACTERS);
if event != "onload" && event != "onload()" {
return;
return NextParserState::Continue;
}
}
_ => { }
@@ -225,7 +270,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
@@ -234,15 +279,38 @@ 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
// TODO: Do a potentially CORS-enabled fetch with the mode being the current
// state of the element's `crossorigin` content attribute, the origin being
// the origin of the script element's node document, and the default origin
// behaviour set to taint.
ScriptOrigin::External(load_whole_resource(&window.resource_task(), url))
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(None),
metadata: RefCell::new(None),
resume_on_completion: self.parser_inserted.get(),
}));

let listener = box NetworkListener {
context: context,
script_chan: script_chan,
};

let load_data = LoadData::new(url);
window.resource_task().send(
ControlMsg::Load(load_data, LoadConsumer::Listener(listener))).unwrap();

let document = document_from_node(self).root();
if self.parser_inserted.get() {
document.r().get_current_parser().unwrap().root().r().suspend();
}
return NextParserState::Suspend;
}
}
},
@@ -253,6 +321,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) {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.