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 all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -55,7 +55,7 @@ use dom::range::Range;
use dom::treewalker::TreeWalker;
use dom::uievent::UIEvent;
use dom::window::{Window, WindowHelpers};
use parse::html::build_element_from_tag;
use parse::html::{build_element_from_tag, ScriptCreated};
use servo_util::namespace;
use servo_util::str::{DOMString, split_html_space_chars};

@@ -529,7 +529,7 @@ impl<'a> DocumentMethods for JSRef<'a, Document> {
}
let local_name = local_name.as_slice().to_ascii_lower();
let name = QualName::new(ns!(HTML), Atom::from_slice(local_name.as_slice()));
Ok(build_element_from_tag(name, None, self))
Ok(build_element_from_tag(name, None, self, ScriptCreated))
}

// http://dom.spec.whatwg.org/#dom-document-createelementns
@@ -574,7 +574,8 @@ impl<'a> DocumentMethods for JSRef<'a, Document> {

if ns == ns!(HTML) {
let name = QualName::new(ns!(HTML), Atom::from_slice(local_name_from_qname));
Ok(build_element_from_tag(name, prefix_from_qname.map(|s| s.to_string()), self))
Ok(build_element_from_tag(name, prefix_from_qname.map(|s| s.to_string()), self,
ScriptCreated))
} else {
Ok(Element::new(local_name_from_qname.to_string(), ns,
prefix_from_qname.map(|s| s.to_string()), self))
@@ -2,25 +2,51 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::attr::Attr;
use dom::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::HTMLScriptElementBinding;
use dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::InheritTypes::HTMLScriptElementDerived;
use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast};
use dom::bindings::codegen::InheritTypes::{HTMLScriptElementDerived, HTMLScriptElementCast};
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::bindings::utils::{Reflectable, Reflector};
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, CloneChildrenFlag};
use dom::virtualmethods::VirtualMethods;
use dom::window::WindowHelpers;

use encoding::all::UTF_8;
use encoding::types::{Encoding, DecodeReplace};
use parse::html::{ElementCreator, ParserCreated};
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,23 +56,34 @@ 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>,
creator: ElementCreator) -> 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(creator == ParserCreated),
non_blocking: Cell::new(creator != ParserCreated),
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>,
creator: ElementCreator) -> Temporary<HTMLScriptElement> {
let element = HTMLScriptElement::new_inherited(localName, prefix, document, creator);
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;

/// Set the "already started" flag (<https://whatwg.org/html/#already-started>)
fn mark_already_started(self);
}

/// Supported script types as defined by
@@ -71,6 +108,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()) {
@@ -104,6 +239,64 @@ impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
}
}
}

fn mark_already_started(self) {
self.already_started.set(true);
}
}

impl<'a> VirtualMethods for JSRef<'a, HTMLScriptElement> {
fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_borrowed_ref(self);
Some(htmlelement as &VirtualMethods)
}

fn after_set_attr(&self, attr: JSRef<Attr>) {
match self.super_type() {
Some(ref s) => s.after_set_attr(attr),
_ => (),
}
let node: JSRef<Node> = NodeCast::from_ref(*self);
if attr.local_name() == &atom!("src") && !self.parser_inserted.get() && node.is_in_doc() {
self.prepare();
}
}

fn child_inserted(&self, child: JSRef<Node>) {
match self.super_type() {
Some(ref s) => s.child_inserted(child),
_ => (),
}
let node: JSRef<Node> = NodeCast::from_ref(*self);
if !self.parser_inserted.get() && node.is_in_doc() {
self.prepare();
}
}

fn bind_to_tree(&self, tree_in_doc: bool) {
match self.super_type() {
Some(ref s) => s.bind_to_tree(tree_in_doc),
_ => ()
}

if tree_in_doc && !self.parser_inserted.get() {
self.prepare();
}
}

fn cloning_steps(&self, copy: JSRef<Node>, maybe_doc: Option<JSRef<Document>>,
clone_children: CloneChildrenFlag) {
match self.super_type() {
Some(ref s) => s.cloning_steps(copy, maybe_doc, clone_children),
_ => (),
}

// https://whatwg.org/html/#already-started
if self.already_started.get() {
let copy_elem: JSRef<HTMLScriptElement> = HTMLScriptElementCast::to_ref(copy).unwrap();
copy_elem.mark_already_started();
}
}
}

impl<'a> HTMLScriptElementMethods for JSRef<'a, HTMLScriptElement> {
@@ -45,7 +45,7 @@ use dom::text::Text;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use dom::window::Window;
use geom::rect::Rect;
use parse::html::build_element_from_tag;
use parse::html::{build_element_from_tag, ScriptCreated};
use layout_interface::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC,
LayoutChan, ReapLayoutDataMsg};
use devtools_traits::NodeInfo;
@@ -1521,7 +1521,7 @@ impl Node {
local: element.local_name().clone()
};
let element = build_element_from_tag(name,
Some(element.prefix().as_slice().to_string()), *document);
Some(element.prefix().as_slice().to_string()), *document, ScriptCreated);
NodeCast::from_temporary(element)
},
TextNodeTypeId => {
@@ -1572,6 +1572,7 @@ impl Node {
}

// Step 5: cloning steps.
vtable_for(&node).cloning_steps(*copy, maybe_doc, clone_children);

// Step 6.
if clone_children == CloneChildren {
@@ -13,7 +13,6 @@ use dom::bindings::js::{JS, JSRef, Temporary};
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::node::TrustedNodeAddress;
use dom::document::{Document, DocumentHelpers};
use parse::html::JSMessage;
use parse::Parser;

use servo_util::task_state;
@@ -28,7 +27,6 @@ use html5ever::tree_builder::{TreeBuilder, TreeBuilderOpts};
#[must_root]
#[jstraceable]
pub struct Sink {
pub js_chan: Sender<JSMessage>,
pub base_url: Option<Url>,
pub document: JS<Document>,
}
@@ -55,11 +53,9 @@ impl Parser for ServoHTMLParser{

impl ServoHTMLParser {
#[allow(unrooted_must_root)]
pub fn new(js_chan: Sender<JSMessage>, base_url: Option<Url>, document: JSRef<Document>)
-> Temporary<ServoHTMLParser> {
pub fn new(base_url: Option<Url>, document: JSRef<Document>) -> Temporary<ServoHTMLParser> {
let window = document.window().root();
let sink = Sink {
js_chan: js_chan,
base_url: base_url,
document: JS::from_rooted(document),
};
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.