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

Make external script sources load asynchronously, yet still block furthe... #5727

Merged
merged 3 commits into from May 21, 2015
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Next

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 May 20, 2015
commit 8082df7d0da97f1951ae125956b962b92c98e69f
@@ -132,8 +132,6 @@ pub struct PendingAsyncLoad {
resource_task: ResourceTask,
url: Url,
pipeline: Option<PipelineId>,
input_sender: Sender<LoadResponse>,
input_receiver: Receiver<LoadResponse>,
guard: PendingLoadGuard,
}

@@ -156,13 +154,10 @@ impl Drop for PendingLoadGuard {
impl PendingAsyncLoad {
pub fn new(resource_task: ResourceTask, url: Url, pipeline: Option<PipelineId>)
-> PendingAsyncLoad {
let (sender, receiver) = channel();
PendingAsyncLoad {
resource_task: resource_task,
url: url,
pipeline: pipeline,
input_sender: sender,
input_receiver: receiver,
guard: PendingLoadGuard { loaded: false, },
}
}
@@ -171,9 +166,18 @@ impl PendingAsyncLoad {
pub fn load(mut self) -> Receiver<LoadResponse> {
self.guard.neuter();
let load_data = LoadData::new(self.url, self.pipeline);
let consumer = LoadConsumer::Channel(self.input_sender);
let (sender, receiver) = channel();
let consumer = LoadConsumer::Channel(sender);
self.resource_task.send(ControlMsg::Load(load_data, consumer)).unwrap();
receiver
}

/// Initiate the network request associated with this pending load, using the provided target.
pub fn load_async(mut self, listener: Box<AsyncResponseTarget + Send>) {
self.guard.neuter();
let load_data = LoadData::new(self.url, self.pipeline);
let consumer = LoadConsumer::Listener(listener);
self.resource_task.send(ControlMsg::Load(load_data, consumer)).unwrap();
self.input_receiver
}
}

@@ -7,13 +7,12 @@

use script_task::{ScriptMsg, ScriptChan};
use msg::constellation_msg::{PipelineId};
use net_traits::{LoadResponse, Metadata, load_whole_resource, ResourceTask, PendingAsyncLoad};
use net_traits::{Metadata, load_whole_resource, ResourceTask, PendingAsyncLoad};
use net_traits::AsyncResponseTarget;
use url::Url;

use std::sync::mpsc::Receiver;

#[jstraceable]
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Debug)]
pub enum LoadType {
Image(Url),
Script(Url),
@@ -75,9 +74,9 @@ impl DocumentLoader {
}

/// Create and initiate a new network request.
pub fn load_async(&mut self, load: LoadType) -> Receiver<LoadResponse> {
pub fn load_async(&mut self, load: LoadType, listener: Box<AsyncResponseTarget + Send>) {
let pending = self.prepare_async_load(load);
pending.load()
pending.load_async(listener)
}

/// Create, initiate, and await the response for a new network request.
@@ -91,7 +90,7 @@ impl DocumentLoader {
/// Mark an in-progress network request complete.
pub fn finish_load(&mut self, load: LoadType) {
let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == load);
self.blocking_loads.remove(idx.expect("unknown completed load"));
self.blocking_loads.remove(idx.expect(&format!("unknown completed load {:?}", load)));

if let Some(NotifierData { ref script_chan, pipeline }) = self.notifier_data {
if !self.is_blocked() {
@@ -61,6 +61,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, ReflowReason};
@@ -73,7 +74,7 @@ use msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyState, KeyMod
use msg::constellation_msg::{SUPER, ALT, SHIFT, CONTROL};
use net_traits::CookieSource::NonHTTP;
use net_traits::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl};
use net_traits::{Metadata, LoadResponse, PendingAsyncLoad};
use net_traits::{Metadata, PendingAsyncLoad, AsyncResponseTarget};
use script_task::Runnable;
use script_traits::{MouseButton, UntrustedNodeAddress};
use util::opts;
@@ -96,7 +97,7 @@ use std::ascii::AsciiExt;
use std::cell::{Cell, Ref, RefMut, RefCell};
use std::default::Default;
use std::ptr;
use std::sync::mpsc::{Receiver, channel};
use std::sync::mpsc::channel;
use time;

#[derive(PartialEq)]
@@ -145,6 +146,8 @@ pub struct Document {
animation_frame_list: RefCell<HashMap<i32, Box<Fn(f64)>>>,
/// Tracks all outstanding loads related to this document.
loader: DOMRefCell<DocumentLoader>,
/// The current active HTML parser, to allow resuming after interruptions.
current_parser: MutNullableHeap<JS<ServoHTMLParser>>,
}

impl DocumentDerived for EventTarget {
@@ -263,9 +266,11 @@ pub trait DocumentHelpers<'a> {
/// http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm
fn invoke_animation_callbacks(self);
fn prepare_async_load(self, load: LoadType) -> PendingAsyncLoad;
fn load_async(self, load: LoadType) -> Receiver<LoadResponse>;
fn load_async(self, load: LoadType, listener: Box<AsyncResponseTarget + Send>);
fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String>;
fn finish_load(self, load: LoadType);
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> {
@@ -892,9 +897,9 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
loader.prepare_async_load(load)
}

fn load_async(self, load: LoadType) -> Receiver<LoadResponse> {
fn load_async(self, load: LoadType, listener: Box<AsyncResponseTarget + Send>) {
let mut loader = self.loader.borrow_mut();
loader.load_async(load)
loader.load_async(load, listener)
}

fn load_sync(self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {
@@ -906,6 +911,14 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
let mut loader = self.loader.borrow_mut();
loader.finish_load(load);
}

fn set_current_parser(self, script: Option<JSRef<ServoHTMLParser>>) {
self.current_parser.set(script.map(JS::from_rooted));
}

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

pub enum MouseEventType {
@@ -914,6 +927,7 @@ pub enum MouseEventType {
MouseUp,
}


#[derive(PartialEq)]
pub enum DocumentSource {
FromParser,
@@ -987,6 +1001,7 @@ impl Document {
animation_frame_ident: Cell::new(0),
animation_frame_list: RefCell::new(HashMap::new()),
loader: DOMRefCell::new(doc_loader),
current_parser: Default::default(),
}
}

@@ -15,7 +15,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::{ParseContext, parse_html};
use util::str::DOMString;

use std::borrow::ToOwned;
@@ -64,7 +64,7 @@ impl<'a> DOMParserMethods for JSRef<'a, DOMParser> {
None,
DocumentSource::FromParser,
loader).root();
parse_html(document.r(), HTMLInput::InputString(s), &url, None);
parse_html(document.r(), s, &url, ParseContext::Owner(None));
document.r().set_ready_state(DocumentReadyState::Complete);
Ok(Temporary::from_rooted(document.r()))
}
@@ -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) {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.