mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01: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
|
@ -81,9 +81,9 @@ use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER};
|
|||
use msg::constellation_msg::{ConstellationChan, FocusType, Key, KeyModifiers, KeyState, MozBrowserEvent, SubpageId};
|
||||
use net_traits::ControlMsg::{GetCookiesForUrl, SetCookiesForUrl};
|
||||
use net_traits::CookieSource::NonHTTP;
|
||||
use net_traits::{AsyncResponseTarget, Metadata, PendingAsyncLoad};
|
||||
use net_traits::{AsyncResponseTarget, PendingAsyncLoad};
|
||||
use num::ToPrimitive;
|
||||
use script_task::Runnable;
|
||||
use script_task::{MainThreadScriptMsg, Runnable};
|
||||
use script_traits::{MouseButton, UntrustedNodeAddress};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::ToOwned;
|
||||
|
@ -107,6 +107,12 @@ pub enum IsHTMLDocument {
|
|||
NonHTMLDocument,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum ParserBlockedByScript {
|
||||
Blocked,
|
||||
Unblocked,
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#document
|
||||
#[dom_struct]
|
||||
pub struct Document {
|
||||
|
@ -129,12 +135,24 @@ pub struct Document {
|
|||
anchors: MutNullableHeap<JS<HTMLCollection>>,
|
||||
applets: MutNullableHeap<JS<HTMLCollection>>,
|
||||
ready_state: Cell<DocumentReadyState>,
|
||||
/// Whether the DOMContentLoaded event has already been dispatched.
|
||||
domcontentloaded_dispatched: Cell<bool>,
|
||||
/// The element that has most recently requested focus for itself.
|
||||
possibly_focused: MutNullableHeap<JS<Element>>,
|
||||
/// The element that currently has the document focus context.
|
||||
focused: MutNullableHeap<JS<Element>>,
|
||||
/// The script element that is currently executing.
|
||||
current_script: MutNullableHeap<JS<HTMLScriptElement>>,
|
||||
/// https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script
|
||||
pending_parsing_blocking_script: MutNullableHeap<JS<HTMLScriptElement>>,
|
||||
/// Number of stylesheets that block executing the next parser-inserted script
|
||||
script_blocking_stylesheets_count: Cell<u32>,
|
||||
/// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-when-the-document-has-finished-parsing
|
||||
deferred_scripts: DOMRefCell<Vec<JS<HTMLScriptElement>>>,
|
||||
/// https://html.spec.whatwg.org/multipage/#list-of-scripts-that-will-execute-in-order-as-soon-as-possible
|
||||
asap_in_order_scripts_list: DOMRefCell<Vec<JS<HTMLScriptElement>>>,
|
||||
/// https://html.spec.whatwg.org/multipage/#set-of-scripts-that-will-execute-as-soon-as-possible
|
||||
asap_scripts_set: DOMRefCell<Vec<JS<HTMLScriptElement>>>,
|
||||
/// https://html.spec.whatwg.org/multipage/#concept-n-noscript
|
||||
/// True if scripting is enabled for all scripts in this document
|
||||
scripting_enabled: Cell<bool>,
|
||||
|
@ -892,6 +910,42 @@ impl Document {
|
|||
self.current_script.set(script);
|
||||
}
|
||||
|
||||
pub fn get_script_blocking_stylesheets_count(&self) -> u32 {
|
||||
self.script_blocking_stylesheets_count.get()
|
||||
}
|
||||
|
||||
pub fn increment_script_blocking_stylesheet_count(&self) {
|
||||
let count_cell = &self.script_blocking_stylesheets_count;
|
||||
count_cell.set(count_cell.get() + 1);
|
||||
}
|
||||
|
||||
pub fn decrement_script_blocking_stylesheet_count(&self) {
|
||||
let count_cell = &self.script_blocking_stylesheets_count;
|
||||
assert!(count_cell.get() > 0);
|
||||
count_cell.set(count_cell.get() - 1);
|
||||
}
|
||||
|
||||
pub fn set_pending_parsing_blocking_script(&self, script: Option<&HTMLScriptElement>) {
|
||||
assert!(self.get_pending_parsing_blocking_script().is_none() || script.is_none());
|
||||
self.pending_parsing_blocking_script.set(script);
|
||||
}
|
||||
|
||||
pub fn get_pending_parsing_blocking_script(&self) -> Option<Root<HTMLScriptElement>> {
|
||||
self.pending_parsing_blocking_script.get()
|
||||
}
|
||||
|
||||
pub fn add_deferred_script(&self, script: &HTMLScriptElement) {
|
||||
self.deferred_scripts.borrow_mut().push(JS::from_ref(script));
|
||||
}
|
||||
|
||||
pub fn add_asap_script(&self, script: &HTMLScriptElement) {
|
||||
self.asap_scripts_set.borrow_mut().push(JS::from_ref(script));
|
||||
}
|
||||
|
||||
pub fn push_asap_in_order_script(&self, script: &HTMLScriptElement) {
|
||||
self.asap_in_order_scripts_list.borrow_mut().push(JS::from_ref(script));
|
||||
}
|
||||
|
||||
pub fn trigger_mozbrowser_event(&self, event: MozBrowserEvent) {
|
||||
if htmliframeelement::mozbrowser_enabled() {
|
||||
if let Some((containing_pipeline_id, subpage_id)) = self.window.parent_info() {
|
||||
|
@ -966,14 +1020,120 @@ impl Document {
|
|||
loader.load_async(load, listener)
|
||||
}
|
||||
|
||||
pub fn load_sync(&self, load: LoadType) -> Result<(Metadata, Vec<u8>), String> {
|
||||
let mut loader = self.loader.borrow_mut();
|
||||
loader.load_sync(load)
|
||||
pub fn finish_load(&self, load: LoadType) {
|
||||
// The parser might need the loader, so restrict the lifetime of the borrow.
|
||||
{
|
||||
let mut loader = self.loader.borrow_mut();
|
||||
loader.finish_load(load.clone());
|
||||
}
|
||||
|
||||
if let LoadType::Script(_) = load {
|
||||
self.process_deferred_scripts();
|
||||
self.process_asap_scripts();
|
||||
}
|
||||
|
||||
if self.maybe_execute_parser_blocking_script() == ParserBlockedByScript::Blocked {
|
||||
return;
|
||||
}
|
||||
|
||||
// A finished resource load can potentially unblock parsing. In that case, resume the
|
||||
// parser so its loop can find out.
|
||||
if let Some(parser) = self.current_parser.get_rooted() {
|
||||
if parser.is_suspended() {
|
||||
parser.resume();
|
||||
}
|
||||
}
|
||||
|
||||
let loader = self.loader.borrow();
|
||||
if !loader.is_blocked() && !loader.events_inhibited() {
|
||||
let win = self.window();
|
||||
let msg = MainThreadScriptMsg::DocumentLoadsComplete(win.pipeline());
|
||||
win.main_thread_script_chan().send(msg).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_load(&self, load: LoadType) {
|
||||
let mut loader = self.loader.borrow_mut();
|
||||
loader.finish_load(load);
|
||||
/// If document parsing is blocked on a script, and that script is ready to run,
|
||||
/// execute it.
|
||||
/// https://html.spec.whatwg.org/multipage/#ready-to-be-parser-executed
|
||||
fn maybe_execute_parser_blocking_script(&self) -> ParserBlockedByScript {
|
||||
let script = match self.pending_parsing_blocking_script.get_rooted() {
|
||||
None => return ParserBlockedByScript::Unblocked,
|
||||
Some(script) => script,
|
||||
};
|
||||
|
||||
if self.script_blocking_stylesheets_count.get() == 0 &&
|
||||
script.r().is_ready_to_be_executed() {
|
||||
script.r().execute();
|
||||
self.pending_parsing_blocking_script.set(None);
|
||||
return ParserBlockedByScript::Unblocked;
|
||||
}
|
||||
ParserBlockedByScript::Blocked
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#the-end step 3
|
||||
pub fn process_deferred_scripts(&self) {
|
||||
if self.ready_state.get() != DocumentReadyState::Interactive {
|
||||
return;
|
||||
}
|
||||
// Part of substep 1.
|
||||
if self.script_blocking_stylesheets_count.get() > 0 {
|
||||
return;
|
||||
}
|
||||
let mut deferred_scripts = self.deferred_scripts.borrow_mut();
|
||||
while !deferred_scripts.is_empty() {
|
||||
let script = deferred_scripts[0].root();
|
||||
// Part of substep 1.
|
||||
if !script.is_ready_to_be_executed() {
|
||||
return;
|
||||
}
|
||||
// Substep 2.
|
||||
script.execute();
|
||||
// Substep 3.
|
||||
deferred_scripts.remove(0);
|
||||
// Substep 4 (implicit).
|
||||
}
|
||||
// https://html.spec.whatwg.org/multipage/#the-end step 4.
|
||||
self.maybe_dispatch_dom_content_loaded();
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#the-end step 5 and the latter parts of
|
||||
/// https://html.spec.whatwg.org/multipage/#prepare-a-script 15.d and 15.e.
|
||||
pub fn process_asap_scripts(&self) {
|
||||
// Execute the first in-order asap-executed script if it's ready, repeat as required.
|
||||
// Re-borrowing the list for each step because it can also be borrowed under execute.
|
||||
while self.asap_in_order_scripts_list.borrow().len() > 0 {
|
||||
let script = self.asap_in_order_scripts_list.borrow()[0].root();
|
||||
if !script.r().is_ready_to_be_executed() {
|
||||
break;
|
||||
}
|
||||
script.r().execute();
|
||||
self.asap_in_order_scripts_list.borrow_mut().remove(0);
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
// Re-borrowing the set for each step because it can also be borrowed under execute.
|
||||
while idx < self.asap_scripts_set.borrow().len() {
|
||||
let script = self.asap_scripts_set.borrow()[idx].root();
|
||||
if !script.r().is_ready_to_be_executed() {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
script.r().execute();
|
||||
self.asap_scripts_set.borrow_mut().swap_remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_dispatch_dom_content_loaded(&self) {
|
||||
if self.domcontentloaded_dispatched.get() {
|
||||
return;
|
||||
}
|
||||
self.domcontentloaded_dispatched.set(true);
|
||||
let event = Event::new(GlobalRef::Window(self.window()), "DOMContentLoaded".to_owned(),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable);
|
||||
let doctarget = self.upcast::<EventTarget>();
|
||||
let _ = doctarget.DispatchEvent(event.r());
|
||||
self.window().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::DOMContentLoaded);
|
||||
}
|
||||
|
||||
pub fn notify_constellation_load(&self) {
|
||||
|
@ -1039,10 +1199,10 @@ impl Document {
|
|||
doc_loader: DocumentLoader) -> Document {
|
||||
let url = url.unwrap_or_else(|| Url::parse("about:blank").unwrap());
|
||||
|
||||
let ready_state = if source == DocumentSource::FromParser {
|
||||
DocumentReadyState::Loading
|
||||
let (ready_state, domcontentloaded_dispatched) = if source == DocumentSource::FromParser {
|
||||
(DocumentReadyState::Loading, false)
|
||||
} else {
|
||||
DocumentReadyState::Complete
|
||||
(DocumentReadyState::Complete, true)
|
||||
};
|
||||
|
||||
Document {
|
||||
|
@ -1075,9 +1235,15 @@ impl Document {
|
|||
anchors: Default::default(),
|
||||
applets: Default::default(),
|
||||
ready_state: Cell::new(ready_state),
|
||||
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
|
||||
possibly_focused: Default::default(),
|
||||
focused: Default::default(),
|
||||
current_script: Default::default(),
|
||||
pending_parsing_blocking_script: Default::default(),
|
||||
script_blocking_stylesheets_count: Cell::new(0u32),
|
||||
deferred_scripts: DOMRefCell::new(vec!()),
|
||||
asap_in_order_scripts_list: DOMRefCell::new(vec!()),
|
||||
asap_scripts_set: DOMRefCell::new(vec!()),
|
||||
scripting_enabled: Cell::new(true),
|
||||
animation_frame_ident: Cell::new(0),
|
||||
animation_frame_list: RefCell::new(HashMap::new()),
|
||||
|
@ -1875,7 +2041,6 @@ fn is_scheme_host_port_tuple(url: &Url) -> bool {
|
|||
|
||||
#[derive(HeapSizeOf)]
|
||||
pub enum DocumentProgressTask {
|
||||
DOMContentLoaded,
|
||||
Load,
|
||||
}
|
||||
|
||||
|
@ -1892,20 +2057,6 @@ impl DocumentProgressHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn dispatch_dom_content_loaded(&self) {
|
||||
let document = self.addr.root();
|
||||
let window = document.r().window();
|
||||
let event = Event::new(GlobalRef::Window(window), "DOMContentLoaded".to_owned(),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable);
|
||||
let doctarget = document.upcast::<EventTarget>();
|
||||
let _ = doctarget.DispatchEvent(event.r());
|
||||
|
||||
window.reflow(ReflowGoal::ForDisplay,
|
||||
ReflowQueryType::NoQuery,
|
||||
ReflowReason::DOMContentLoaded);
|
||||
}
|
||||
|
||||
fn set_ready_state_complete(&self) {
|
||||
let document = self.addr.root();
|
||||
document.r().set_ready_state(DocumentReadyState::Complete);
|
||||
|
@ -1949,9 +2100,6 @@ impl Runnable for DocumentProgressHandler {
|
|||
let window = document.r().window();
|
||||
if window.is_alive() {
|
||||
match self.task {
|
||||
DocumentProgressTask::DOMContentLoaded => {
|
||||
self.dispatch_dom_content_loaded();
|
||||
}
|
||||
DocumentProgressTask::Load => {
|
||||
self.set_ready_state_complete();
|
||||
self.dispatch_load();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue