mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
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.
This commit is contained in:
parent
5858fccf87
commit
65a0d1fe9a
4 changed files with 142 additions and 14 deletions
|
@ -14,13 +14,35 @@ use dom::document::Document;
|
||||||
use dom::element::{HTMLScriptElementTypeId, Element, AttributeHandlers};
|
use dom::element::{HTMLScriptElementTypeId, Element, AttributeHandlers};
|
||||||
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
|
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
|
||||||
use dom::htmlelement::HTMLElement;
|
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 servo_util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
|
||||||
|
use std::cell::Cell;
|
||||||
|
use url::UrlParser;
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct HTMLScriptElement {
|
pub struct HTMLScriptElement {
|
||||||
htmlelement: HTMLElement,
|
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 {
|
impl HTMLScriptElementDerived for EventTarget {
|
||||||
|
@ -30,22 +52,30 @@ impl HTMLScriptElementDerived for EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HTMLScriptElement {
|
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 {
|
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)]
|
#[allow(unrooted_must_root)]
|
||||||
pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLScriptElement> {
|
pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>,
|
||||||
let element = HTMLScriptElement::new_inherited(localName, prefix, document);
|
parser_inserted: bool) -> Temporary<HTMLScriptElement> {
|
||||||
|
let element = HTMLScriptElement::new_inherited(localName, prefix, document, parser_inserted);
|
||||||
Node::reflect_node(box element, document, HTMLScriptElementBinding::Wrap)
|
Node::reflect_node(box element, document, HTMLScriptElementBinding::Wrap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HTMLScriptElementHelpers {
|
pub trait HTMLScriptElementHelpers {
|
||||||
/// Prepare a script (<http://www.whatwg.org/html/#prepare-a-script>),
|
/// Prepare a script (<http://www.whatwg.org/html/#prepare-a-script>)
|
||||||
/// steps 6 and 7.
|
fn prepare(self);
|
||||||
|
|
||||||
|
/// Prepare a script, steps 6 and 7.
|
||||||
fn is_javascript(self) -> bool;
|
fn is_javascript(self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +101,104 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[
|
||||||
];
|
];
|
||||||
|
|
||||||
impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
|
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)) => {
|
||||||
|
// 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 {
|
fn is_javascript(self) -> bool {
|
||||||
let element: JSRef<Element> = ElementCast::from_ref(self);
|
let element: JSRef<Element> = ElementCast::from_ref(self);
|
||||||
match element.get_attribute(ns!(""), &atom!("type")).root().map(|s| s.Value()) {
|
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 load_url(self, href: DOMString);
|
||||||
fn handle_fire_timer(self, timer_id: TimerId, cx: *mut JSContext);
|
fn handle_fire_timer(self, timer_id: TimerId, cx: *mut JSContext);
|
||||||
fn evaluate_js_with_result(self, code: &str) -> JSVal;
|
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> {
|
impl<'a> WindowHelpers for JSRef<'a, Window> {
|
||||||
fn evaluate_js_with_result(self, code: &str) -> JSVal {
|
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 global = self.reflector().get_jsobject();
|
||||||
let code: Vec<u16> = code.as_slice().utf16_units().collect();
|
let code: Vec<u16> = code.as_slice().utf16_units().collect();
|
||||||
let mut rval = UndefinedValue();
|
let mut rval = UndefinedValue();
|
||||||
let filename = "".to_c_str();
|
let filename = filename.to_c_str();
|
||||||
let cx = self.get_cx();
|
let cx = self.get_cx();
|
||||||
|
|
||||||
with_compartment(cx, global, || {
|
with_compartment(cx, global, || {
|
||||||
|
|
|
@ -233,7 +233,7 @@ pub fn build_element_from_tag(name: QualName,
|
||||||
atom!("ruby") => make!(HTMLElement),
|
atom!("ruby") => make!(HTMLElement),
|
||||||
atom!("s") => make!(HTMLElement),
|
atom!("s") => make!(HTMLElement),
|
||||||
atom!("samp") => make!(HTMLElement),
|
atom!("samp") => make!(HTMLElement),
|
||||||
atom!("script") => make!(HTMLScriptElement),
|
atom!("script") => make!(HTMLScriptElement, true),
|
||||||
atom!("section") => make!(HTMLElement),
|
atom!("section") => make!(HTMLElement),
|
||||||
atom!("select") => make!(HTMLSelectElement),
|
atom!("select") => make!(HTMLSelectElement),
|
||||||
atom!("small") => make!(HTMLElement),
|
atom!("small") => make!(HTMLElement),
|
||||||
|
|
|
@ -815,11 +815,6 @@ impl ScriptTask {
|
||||||
|
|
||||||
document.set_ready_state(DocumentReadyStateValues::Interactive);
|
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;
|
let mut js_scripts = None;
|
||||||
loop {
|
loop {
|
||||||
match discovery_port.recv_opt() {
|
match discovery_port.recv_opt() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue