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,
}
#[derive(PartialEq)]
enum ParserBlockedByScript {
Blocked,
Unblocked,
}
#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub struct StylesheetInDocument {
@ -1546,15 +1540,13 @@ impl Document {
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;
}
// 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.get_current_parser() {
if parser.is_suspended() {
parser.resume();
self.pending_parsing_blocking_script.set(None);
parser.resume_with_pending_parsing_blocking_script(&script);
}
} else if self.reflow_timeout.get().is_none() {
// 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
pub fn process_deferred_scripts(&self) {
if self.ready_state.get() != DocumentReadyState::Interactive {

View file

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

View file

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