mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Rewrite how parser handles script scheduling
This commit is contained in:
parent
e1eff691f8
commit
c801327eab
10 changed files with 54 additions and 100 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[async_003.htm]
|
|
||||||
type: testharness
|
|
||||||
[An async script does not block the parser while downloading]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[async_004.htm]
|
|
||||||
type: testharness
|
|
||||||
[async script executes as soon as possible after a download is complete]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[async_005.htm]
|
|
||||||
type: testharness
|
|
||||||
[A script element with both async and defer set should execute asynchronously]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[086.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: async script and slow-loading async script]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[088.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: multiple scripts with defer and async attributes]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[111.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: removing async attribute at runtime]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[112.html]
|
|
||||||
type: testharness
|
|
||||||
[ scheduler: removing async attribute at runtime, script also has defer attribute]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue