Rewrite how parser handles script scheduling

This commit is contained in:
Anthony Ramine 2016-11-20 01:33:21 +01:00
parent e1eff691f8
commit c801327eab
10 changed files with 54 additions and 100 deletions

View file

@ -142,12 +142,6 @@ pub enum IsHTMLDocument {
NonHTMLDocument, NonHTMLDocument,
} }
#[derive(PartialEq)]
enum ParserBlockedByScript {
Blocked,
Unblocked,
}
#[derive(JSTraceable, HeapSizeOf)] #[derive(JSTraceable, HeapSizeOf)]
#[must_root] #[must_root]
pub struct StylesheetInDocument { pub struct StylesheetInDocument {
@ -1546,15 +1540,13 @@ impl Document {
self.process_asap_scripts(); self.process_asap_scripts();
} }
if self.maybe_execute_parser_blocking_script() == ParserBlockedByScript::Blocked { if let Some(parser) = self.get_current_parser() {
if let Some(script) = self.pending_parsing_blocking_script.get() {
if self.script_blocking_stylesheets_count.get() > 0 || !script.is_ready_to_be_executed() {
return; return;
} }
self.pending_parsing_blocking_script.set(None);
// A finished resource load can potentially unblock parsing. In that case, resume the parser.resume_with_pending_parsing_blocking_script(&script);
// parser so its loop can find out.
if let Some(parser) = self.get_current_parser() {
if parser.is_suspended() {
parser.resume();
} }
} else if self.reflow_timeout.get().is_none() { } else if self.reflow_timeout.get().is_none() {
// If we don't have a parser, and the reflow timer has been reset, explicitly // If we don't have a parser, and the reflow timer has been reset, explicitly
@ -1577,23 +1569,6 @@ impl Document {
} }
} }
/// 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() {
None => return ParserBlockedByScript::Unblocked,
Some(script) => script,
};
if self.script_blocking_stylesheets_count.get() == 0 && script.is_ready_to_be_executed() {
self.pending_parsing_blocking_script.set(None);
script.execute();
return ParserBlockedByScript::Unblocked;
}
ParserBlockedByScript::Blocked
}
/// https://html.spec.whatwg.org/multipage/#the-end step 3 /// https://html.spec.whatwg.org/multipage/#the-end step 3
pub fn process_deferred_scripts(&self) { pub fn process_deferred_scripts(&self) {
if self.ready_state.get() != DocumentReadyState::Interactive { if self.ready_state.get() != DocumentReadyState::Interactive {

View file

@ -274,12 +274,10 @@ fn fetch_a_classic_script(script: &HTMLScriptElement,
impl HTMLScriptElement { impl HTMLScriptElement {
/// https://html.spec.whatwg.org/multipage/#prepare-a-script /// https://html.spec.whatwg.org/multipage/#prepare-a-script
/// pub fn prepare(&self) {
/// Returns true if tokenization should continue, false otherwise.
pub fn prepare(&self) -> bool {
// Step 1. // Step 1.
if self.already_started.get() { if self.already_started.get() {
return true; return;
} }
// Step 2. // Step 2.
@ -297,17 +295,17 @@ impl HTMLScriptElement {
// Step 4. // Step 4.
let text = self.Text(); let text = self.Text();
if text.is_empty() && !element.has_attribute(&local_name!("src")) { if text.is_empty() && !element.has_attribute(&local_name!("src")) {
return true; return;
} }
// Step 5. // Step 5.
if !self.upcast::<Node>().is_in_doc() { if !self.upcast::<Node>().is_in_doc() {
return true; return;
} }
// Step 6. // Step 6.
if !self.is_javascript() { if !self.is_javascript() {
return true; return;
} }
// Step 7. // Step 7.
@ -322,12 +320,12 @@ impl HTMLScriptElement {
// Step 9. // Step 9.
let doc = document_from_node(self); let doc = document_from_node(self);
if self.parser_inserted.get() && &*self.parser_document != &*doc { if self.parser_inserted.get() && &*self.parser_document != &*doc {
return true; return;
} }
// Step 10. // Step 10.
if !doc.is_scripting_enabled() { if !doc.is_scripting_enabled() {
return true; return;
} }
// TODO(#4577): Step 11: CSP. // TODO(#4577): Step 11: CSP.
@ -340,13 +338,13 @@ impl HTMLScriptElement {
let for_value = for_attribute.value().to_ascii_lowercase(); let for_value = for_attribute.value().to_ascii_lowercase();
let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS); let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
if for_value != "window" { if for_value != "window" {
return true; return;
} }
let event_value = event_attribute.value().to_ascii_lowercase(); let event_value = event_attribute.value().to_ascii_lowercase();
let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS); let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
if event_value != "onload" && event_value != "onload()" { if event_value != "onload" && event_value != "onload()" {
return true; return;
} }
}, },
(_, _) => (), (_, _) => (),
@ -381,7 +379,7 @@ impl HTMLScriptElement {
// Step 18.2. // Step 18.2.
if src.is_empty() { if src.is_empty() {
self.queue_error_event(); self.queue_error_event();
return true; return;
} }
// Step 18.4-18.5. // Step 18.4-18.5.
@ -389,7 +387,7 @@ impl HTMLScriptElement {
Err(_) => { Err(_) => {
warn!("error parsing URL for script {}", &**src); warn!("error parsing URL for script {}", &**src);
self.queue_error_event(); self.queue_error_event();
return true; return;
} }
Ok(url) => url, Ok(url) => url,
}; };
@ -412,7 +410,6 @@ impl HTMLScriptElement {
!async { !async {
doc.add_deferred_script(self); doc.add_deferred_script(self);
// Second part implemented in Document::process_deferred_scripts. // Second part implemented in Document::process_deferred_scripts.
return true;
// Step 20.b: classic, has src, was parser-inserted, is not async. // Step 20.b: classic, has src, was parser-inserted, is not async.
} else if is_external && } else if is_external &&
was_parser_inserted && was_parser_inserted &&
@ -432,7 +429,7 @@ impl HTMLScriptElement {
// Step 20.e: doesn't have src, was parser-inserted, is blocked on stylesheet. // Step 20.e: doesn't have src, was parser-inserted, is blocked on stylesheet.
} else if !is_external && } else if !is_external &&
was_parser_inserted && was_parser_inserted &&
// TODO: check for script nesting levels. doc.get_current_parser().map_or(false, |parser| parser.script_nesting_level() <= 1) &&
doc.get_script_blocking_stylesheets_count() > 0 { doc.get_script_blocking_stylesheets_count() > 0 {
doc.set_pending_parsing_blocking_script(Some(self)); doc.set_pending_parsing_blocking_script(Some(self));
*self.load.borrow_mut() = Some(Ok(ScriptOrigin::internal(text, base_url))); *self.load.borrow_mut() = Some(Ok(ScriptOrigin::internal(text, base_url)));
@ -443,16 +440,7 @@ impl HTMLScriptElement {
self.ready_to_be_parser_executed.set(true); self.ready_to_be_parser_executed.set(true);
*self.load.borrow_mut() = Some(Ok(ScriptOrigin::internal(text, base_url))); *self.load.borrow_mut() = Some(Ok(ScriptOrigin::internal(text, base_url)));
self.execute(); self.execute();
return true;
} }
// TODO: make this suspension happen automatically.
if was_parser_inserted {
if let Some(parser) = doc.get_current_parser() {
parser.suspend();
}
}
false
} }
pub fn is_ready_to_be_executed(&self) -> bool { pub fn is_ready_to_be_executed(&self) -> bool {

View file

@ -55,6 +55,8 @@ pub struct ServoParser {
last_chunk_received: Cell<bool>, last_chunk_received: Cell<bool>,
/// Whether this parser should avoid passing any further data to the tokenizer. /// Whether this parser should avoid passing any further data to the tokenizer.
suspended: Cell<bool>, suspended: Cell<bool>,
/// https://html.spec.whatwg.org/multipage/#script-nesting-level
script_nesting_level: Cell<usize>,
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -134,6 +136,10 @@ impl ServoParser {
parser.parse_chunk(String::from(input)); parser.parse_chunk(String::from(input));
} }
pub fn script_nesting_level(&self) -> usize {
self.script_nesting_level.get()
}
#[allow(unrooted_must_root)] #[allow(unrooted_must_root)]
fn new_inherited( fn new_inherited(
document: &Document, document: &Document,
@ -149,6 +155,7 @@ impl ServoParser {
tokenizer: DOMRefCell::new(tokenizer), tokenizer: DOMRefCell::new(tokenizer),
last_chunk_received: Cell::new(last_chunk_state == LastChunkState::Received), last_chunk_received: Cell::new(last_chunk_state == LastChunkState::Received),
suspended: Default::default(), suspended: Default::default(),
script_nesting_level: Default::default(),
} }
} }
@ -208,6 +215,22 @@ impl ServoParser {
self.suspended.get() self.suspended.get()
} }
pub fn resume_with_pending_parsing_blocking_script(&self, script: &HTMLScriptElement) {
assert!(self.suspended.get());
self.suspended.set(false);
let script_nesting_level = self.script_nesting_level.get();
assert_eq!(script_nesting_level, 0);
self.script_nesting_level.set(script_nesting_level + 1);
script.execute();
self.script_nesting_level.set(script_nesting_level);
if !self.suspended.get() {
self.parse_sync();
}
}
fn parse_sync(&self) { fn parse_sync(&self) {
let metadata = TimerMetadata { let metadata = TimerMetadata {
url: self.document().url().as_str().into(), url: self.document().url().as_str().into(),
@ -226,20 +249,23 @@ impl ServoParser {
// the parser remains unsuspended. // the parser remains unsuspended.
loop { loop {
self.document().reflow_if_reflow_timer_expired(); self.document().reflow_if_reflow_timer_expired();
if let Err(script) = self.tokenizer.borrow_mut().feed(&mut *self.pending_input.borrow_mut()) { let script = match self.tokenizer.borrow_mut().feed(&mut *self.pending_input.borrow_mut()) {
if script.prepare() { Ok(()) => break,
continue; Err(script) => script,
} };
}
// Document parsing is blocked on an external resource. let script_nesting_level = self.script_nesting_level.get();
if self.suspended.get() {
self.script_nesting_level.set(script_nesting_level + 1);
script.prepare();
self.script_nesting_level.set(script_nesting_level);
if self.document.get_pending_parsing_blocking_script().is_some() {
self.suspend();
return; return;
} }
if !self.has_pending_input() { assert!(!self.suspended.get());
break;
}
} }
if self.last_chunk_received() { if self.last_chunk_received() {

View file

@ -1,5 +0,0 @@
[async_003.htm]
type: testharness
[An async script does not block the parser while downloading]
expected: FAIL

View file

@ -1,5 +0,0 @@
[async_004.htm]
type: testharness
[async script executes as soon as possible after a download is complete]
expected: FAIL

View file

@ -1,5 +0,0 @@
[async_005.htm]
type: testharness
[A script element with both async and defer set should execute asynchronously]
expected: FAIL

View file

@ -1,5 +0,0 @@
[086.html]
type: testharness
[ scheduler: async script and slow-loading async script]
expected: FAIL

View file

@ -1,5 +0,0 @@
[088.html]
type: testharness
[ scheduler: multiple scripts with defer and async attributes]
expected: FAIL

View file

@ -1,5 +0,0 @@
[111.html]
type: testharness
[ scheduler: removing async attribute at runtime]
expected: FAIL

View file

@ -1,5 +0,0 @@
[112.html]
type: testharness
[ scheduler: removing async attribute at runtime, script also has defer attribute]
expected: FAIL