mirror of
https://github.com/servo/servo.git
synced 2025-06-14 19:34:29 +00:00
Improve spec-compliance of script loading and execution during document startup
Including proper support for async and deferred scripts.
This commit is contained in:
parent
6b95c3957b
commit
a0c5d47910
9 changed files with 298 additions and 128 deletions
|
@ -37,7 +37,7 @@ use network_listener::{NetworkListener, PreInvoke};
|
|||
use script_task::ScriptTaskEventCategory::ScriptEvent;
|
||||
use script_task::{CommonScriptMsg, Runnable, ScriptChan};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use url::{Url, UrlParser};
|
||||
|
@ -59,13 +59,14 @@ pub struct HTMLScriptElement {
|
|||
non_blocking: Cell<bool>,
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#ready-to-be-parser-executed
|
||||
///
|
||||
/// (currently unused)
|
||||
ready_to_be_parser_executed: Cell<bool>,
|
||||
|
||||
/// Document of the parser that created this element
|
||||
parser_document: JS<Document>,
|
||||
|
||||
/// The source this script was loaded from
|
||||
load: RefCell<Option<ScriptOrigin>>,
|
||||
|
||||
#[ignore_heap_size_of = "Defined in rust-encoding"]
|
||||
/// https://html.spec.whatwg.org/multipage/#concept-script-encoding
|
||||
block_character_encoding: DOMRefCell<EncodingRef>,
|
||||
|
@ -82,6 +83,7 @@ impl HTMLScriptElement {
|
|||
non_blocking: Cell::new(creator != ElementCreator::ParserCreated),
|
||||
ready_to_be_parser_executed: Cell::new(false),
|
||||
parser_document: JS::from_ref(document),
|
||||
load: RefCell::new(None),
|
||||
block_character_encoding: DOMRefCell::new(UTF_8 as EncodingRef),
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +118,7 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[
|
|||
"text/x-javascript",
|
||||
];
|
||||
|
||||
#[derive(HeapSizeOf)]
|
||||
#[derive(HeapSizeOf, JSTraceable)]
|
||||
pub enum ScriptOrigin {
|
||||
Internal(String, Url),
|
||||
External(Result<(Metadata, Vec<u8>), String>),
|
||||
|
@ -130,8 +132,6 @@ struct ScriptContext {
|
|||
data: Vec<u8>,
|
||||
/// The response metadata received to date.
|
||||
metadata: Option<Metadata>,
|
||||
/// Whether the owning document's parser should resume once the response completes.
|
||||
resume_on_completion: bool,
|
||||
/// The initial URL requested.
|
||||
url: Url,
|
||||
}
|
||||
|
@ -153,23 +153,20 @@ impl AsyncResponseListener for ScriptContext {
|
|||
(metadata, data)
|
||||
});
|
||||
let elem = self.elem.root();
|
||||
|
||||
elem.r().execute(ScriptOrigin::External(load));
|
||||
// TODO: maybe set this to None again after script execution to save memory.
|
||||
*elem.r().load.borrow_mut() = Some(ScriptOrigin::External(load));
|
||||
elem.ready_to_be_parser_executed.set(true);
|
||||
|
||||
let document = document_from_node(elem.r());
|
||||
document.r().finish_load(LoadType::Script(self.url.clone()));
|
||||
|
||||
if self.resume_on_completion {
|
||||
document.r().get_current_parser().unwrap().r().resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PreInvoke for ScriptContext {}
|
||||
|
||||
impl HTMLScriptElement {
|
||||
/// https://html.spec.whatwg.org/multipage/#prepare-a-script
|
||||
pub fn prepare(&self) -> NextParserState {
|
||||
// https://html.spec.whatwg.org/multipage/#prepare-a-script
|
||||
// Step 1.
|
||||
if self.already_started.get() {
|
||||
return NextParserState::Continue;
|
||||
|
@ -180,7 +177,9 @@ impl HTMLScriptElement {
|
|||
|
||||
// Step 3.
|
||||
let element = self.upcast::<Element>();
|
||||
if was_parser_inserted && element.has_attribute(&atom!("async")) {
|
||||
let async = element.has_attribute(&atom!("async"));
|
||||
// Note: confusingly, this is done if the element does *not* have an "async" attribute.
|
||||
if was_parser_inserted && !async {
|
||||
self.non_blocking.set(true);
|
||||
}
|
||||
// Step 4.
|
||||
|
@ -205,8 +204,8 @@ impl HTMLScriptElement {
|
|||
self.already_started.set(true);
|
||||
|
||||
// Step 10.
|
||||
let document_from_node_ref = document_from_node(self);
|
||||
let document_from_node_ref = document_from_node_ref.r();
|
||||
let doc = document_from_node(self);
|
||||
let document_from_node_ref = doc.r();
|
||||
if self.parser_inserted.get() && &*self.parser_document != document_from_node_ref {
|
||||
return NextParserState::Continue;
|
||||
}
|
||||
|
@ -247,8 +246,9 @@ impl HTMLScriptElement {
|
|||
let window = window_from_node(self);
|
||||
let window = window.r();
|
||||
let base_url = window.get_url();
|
||||
let deferred = element.has_attribute(&atom!("defer"));
|
||||
|
||||
let load = match element.get_attribute(&ns!(""), &atom!("src")) {
|
||||
let is_external = match element.get_attribute(&ns!(""), &atom!("src")) {
|
||||
// Step 14.
|
||||
Some(ref src) => {
|
||||
// Step 14.1
|
||||
|
@ -274,8 +274,6 @@ impl HTMLScriptElement {
|
|||
// 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.
|
||||
let doc = document_from_node(self);
|
||||
|
||||
let script_chan = window.script_chan();
|
||||
let elem = Trusted::new(window.get_cx(), self, script_chan.clone());
|
||||
|
||||
|
@ -283,7 +281,6 @@ impl HTMLScriptElement {
|
|||
elem: elem,
|
||||
data: vec!(),
|
||||
metadata: None,
|
||||
resume_on_completion: self.parser_inserted.get(),
|
||||
url: url.clone(),
|
||||
}));
|
||||
|
||||
|
@ -300,36 +297,85 @@ impl HTMLScriptElement {
|
|||
});
|
||||
|
||||
doc.r().load_async(LoadType::Script(url), response_target);
|
||||
|
||||
if self.parser_inserted.get() {
|
||||
doc.r().get_current_parser().unwrap().r().suspend();
|
||||
}
|
||||
return NextParserState::Suspend;
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
None => ScriptOrigin::Internal(text, base_url),
|
||||
None => false,
|
||||
};
|
||||
|
||||
// Step 15.
|
||||
// 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
|
||||
// Step 15.a, has src, has defer, was parser-inserted, is not async.
|
||||
if is_external &&
|
||||
deferred &&
|
||||
was_parser_inserted &&
|
||||
!async {
|
||||
doc.r().add_deferred_script(self);
|
||||
// Second part implemented in Document::process_deferred_scripts.
|
||||
return NextParserState::Continue;
|
||||
// Step 15.b, has src, was parser-inserted, is not async.
|
||||
} else if is_external &&
|
||||
was_parser_inserted &&
|
||||
!async {
|
||||
doc.r().set_pending_parsing_blocking_script(Some(self));
|
||||
// Second part implemented in the load result handler.
|
||||
// Step 15.c, doesn't have src, was parser-inserted, is blocked on stylesheet.
|
||||
} else if !is_external &&
|
||||
was_parser_inserted &&
|
||||
// TODO: check for script nesting levels.
|
||||
doc.r().get_script_blocking_stylesheets_count() > 0 {
|
||||
doc.r().set_pending_parsing_blocking_script(Some(self));
|
||||
*self.load.borrow_mut() = Some(ScriptOrigin::Internal(text, base_url));
|
||||
self.ready_to_be_parser_executed.set(true);
|
||||
// Step 15.d, has src, isn't async, isn't non-blocking.
|
||||
} else if is_external &&
|
||||
!async &&
|
||||
!self.non_blocking.get() {
|
||||
doc.r().push_asap_in_order_script(self);
|
||||
// Second part implemented in Document::process_asap_scripts.
|
||||
// Step 15.e, has src.
|
||||
} else if is_external {
|
||||
doc.r().add_asap_script(self);
|
||||
// Second part implemented in Document::process_asap_scripts.
|
||||
// Step 15.f, otherwise.
|
||||
} else {
|
||||
assert!(!text.is_empty());
|
||||
self.ready_to_be_parser_executed.set(true);
|
||||
*self.load.borrow_mut() = Some(ScriptOrigin::Internal(text, base_url));
|
||||
self.execute();
|
||||
return NextParserState::Continue;
|
||||
}
|
||||
// TODO: make this suspension happen automatically.
|
||||
if was_parser_inserted {
|
||||
if let Some(parser) = doc.r().get_current_parser() {
|
||||
parser.r().suspend();
|
||||
}
|
||||
}
|
||||
return NextParserState::Suspend;
|
||||
}
|
||||
|
||||
pub fn execute(&self, load: ScriptOrigin) {
|
||||
pub fn is_ready_to_be_executed(&self) -> bool {
|
||||
self.ready_to_be_parser_executed.get()
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#execute-the-script-block
|
||||
pub fn execute(&self) {
|
||||
assert!(self.ready_to_be_parser_executed.get());
|
||||
|
||||
// Step 1.
|
||||
// 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.
|
||||
let doc = document_from_node(self);
|
||||
if self.parser_inserted.get() && doc.r() != self.parser_document.root().r() {
|
||||
return;
|
||||
}
|
||||
|
||||
let load = self.load.borrow_mut().take().unwrap();
|
||||
|
||||
// Step 2.
|
||||
let (source, external, url) = match load {
|
||||
// Step 2.a.
|
||||
ScriptOrigin::External(Err(e)) => {
|
||||
error!("error loading script {}", e);
|
||||
self.queue_error_event();
|
||||
self.dispatch_error_event();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue