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

Synchronous script loading during HTML parsing #3721

Merged
merged 5 commits into from Oct 29, 2014
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

Infrastructure for synchronous script loading

This implements the parts of the "prepare a script element" algorithm that are
required for synchronous scripts.  It also adds some infrastructure for future
support of the `async` and `defer` attributes.
  • Loading branch information
mbrubeck committed Oct 29, 2014
commit 65a0d1fe9a14e09b675cc24cb59c9aebf1615020
@@ -14,13 +14,35 @@ use dom::document::Document;
use dom::element::{HTMLScriptElementTypeId, Element, AttributeHandlers};
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
use dom::window::WindowHelpers;

use encoding::all::UTF_8;
use encoding::types::{Encoding, DecodeReplace};
use servo_net::resource_task::load_whole_resource;
use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
use std::cell::Cell;
use url::UrlParser;

#[dom_struct]
pub struct HTMLScriptElement {
htmlelement: HTMLElement,

/// https://html.spec.whatwg.org/multipage/scripting.html#already-started
already_started: Cell<bool>,

/// https://html.spec.whatwg.org/multipage/scripting.html#parser-inserted
parser_inserted: Cell<bool>,

/// https://html.spec.whatwg.org/multipage/scripting.html#non-blocking
///
/// (currently unused)
non_blocking: Cell<bool>,

/// https://html.spec.whatwg.org/multipage/scripting.html#ready-to-be-parser-executed
///
/// (currently unused)
ready_to_be_parser_executed: Cell<bool>,
}

impl HTMLScriptElementDerived for EventTarget {
@@ -30,22 +52,30 @@ impl HTMLScriptElementDerived for EventTarget {
}

impl HTMLScriptElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLScriptElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>,
parser_inserted: bool) -> HTMLScriptElement {
HTMLScriptElement {
htmlelement: HTMLElement::new_inherited(HTMLScriptElementTypeId, localName, prefix, document)
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),
ready_to_be_parser_executed: Cell::new(false),
}
}

#[allow(unrooted_must_root)]
pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLScriptElement> {
let element = HTMLScriptElement::new_inherited(localName, prefix, document);
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);
Node::reflect_node(box element, document, HTMLScriptElementBinding::Wrap)
}
}

pub trait HTMLScriptElementHelpers {
/// Prepare a script (<http://www.whatwg.org/html/#prepare-a-script>),
/// steps 6 and 7.
/// Prepare a script (<http://www.whatwg.org/html/#prepare-a-script>)
fn prepare(self);

/// Prepare a script, steps 6 and 7.
fn is_javascript(self) -> bool;
}

@@ -71,6 +101,104 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[
];

impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
fn prepare(self) {
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
// Step 1.
if self.already_started.get() {
return;
}
// Step 2.
let was_parser_inserted = self.parser_inserted.get();
self.parser_inserted.set(false);

// Step 3.
let element: JSRef<Element> = ElementCast::from_ref(self);
if was_parser_inserted && element.has_attribute(&atom!("async")) {
self.non_blocking.set(true);
}
// Step 4.
let text = self.Text();
if text.len() == 0 && !element.has_attribute(&atom!("src")) {
return;
}
// Step 5.
let node: JSRef<Node> = NodeCast::from_ref(self);
if !node.is_in_doc() {
return;
}
// Step 6, 7.
if !self.is_javascript() {
return;
}
// Step 8.
if was_parser_inserted {
self.parser_inserted.set(true);
self.non_blocking.set(false);
}
// Step 9.
self.already_started.set(true);

// Step 10.
// TODO: If the element is flagged as "parser-inserted", but the element's node document is
// not the Document of the parser that created the element, then abort these steps.

// Step 11.
// TODO: If scripting is disabled for the script element, then the user agent must abort
// these steps at this point. The script is not executed.

// Step 12.
// TODO: If the script element has an `event` attribute and a `for` attribute, then run
// these substeps...

// Step 13.
// TODO: If the script element has a `charset` attribute, then let the script block's
// character encoding for this script element be the result of getting an encoding from the
// value of the `charset` attribute.

// Step 14 and 15.
// TODO: Add support for the `defer` and `async` attributes. (For now, we fetch all
// scripts synchronously and execute them immediately.)
let window = window_from_node(self).root();
let page = window.page();
let base_url = page.get_url();

let (source, url) = match element.get_attribute(ns!(""), &atom!("src")).root() {
Some(src) => {
if src.deref().Value().is_empty() {
// TODO: queue a task to fire a simple event named `error` at the element
return;
}
match UrlParser::new().base_url(&base_url).parse(src.deref().Value().as_slice()) {
Ok(url) => {
// 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.
match load_whole_resource(&page.resource_task, url) {
Ok((metadata, bytes)) => {

This comment has been minimized.

@hsivonen

hsivonen Nov 24, 2014

Contributor

Does the JS event loop get to spin while this happens?

This comment has been minimized.

@mbrubeck

mbrubeck Nov 24, 2014

Author Contributor

No, this blocks the script thread. We need to fix this.

This comment has been minimized.

@mbrubeck

mbrubeck Nov 24, 2014

Author Contributor

Filed #4088.

// TODO: use the charset from step 13.
let source = UTF_8.decode(bytes.as_slice(), DecodeReplace).unwrap();
(source, metadata.final_url)
}
Err(_) => {
error!("error loading script {}", src.deref().Value());
return;
}
}
}
Err(_) => {
// TODO: queue a task to fire a simple event named `error` at the element
error!("error parsing URL for script {}", src.deref().Value());
return;
}
}
}
None => (text, base_url)
};

window.evaluate_script_with_result(source.as_slice(), url.serialize().as_slice());
}

fn is_javascript(self) -> bool {
let element: JSRef<Element> = ElementCast::from_ref(self);
match element.get_attribute(ns!(""), &atom!("type")).root().map(|s| s.Value()) {
@@ -318,15 +318,20 @@ pub trait WindowHelpers {
fn load_url(self, href: DOMString);
fn handle_fire_timer(self, timer_id: TimerId, cx: *mut JSContext);
fn evaluate_js_with_result(self, code: &str) -> JSVal;
fn evaluate_script_with_result(self, code: &str, filename: &str) -> JSVal;
}


impl<'a> WindowHelpers for JSRef<'a, Window> {
fn evaluate_js_with_result(self, code: &str) -> JSVal {
self.evaluate_script_with_result(code, "")
}

fn evaluate_script_with_result(self, code: &str, filename: &str) -> JSVal {
let global = self.reflector().get_jsobject();
let code: Vec<u16> = code.as_slice().utf16_units().collect();
let mut rval = UndefinedValue();
let filename = "".to_c_str();
let filename = filename.to_c_str();
let cx = self.get_cx();

with_compartment(cx, global, || {
@@ -233,7 +233,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),
atom!("script") => make!(HTMLScriptElement, true),
atom!("section") => make!(HTMLElement),
atom!("select") => make!(HTMLSelectElement),
atom!("small") => make!(HTMLElement),
@@ -815,11 +815,6 @@ impl ScriptTask {

document.set_ready_state(DocumentReadyStateValues::Interactive);

// Send style sheets over to layout.
//
// FIXME: These should be streamed to layout as they're parsed. We don't need to stop here
// in the script task.

let mut js_scripts = None;
loop {
match discovery_port.recv_opt() {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.