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,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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