diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index b4273233c37..6a30721166a 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -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 { - 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(); + 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; + } + 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 { diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 6dd6349b185..18a9a89b433 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -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::().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 { diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index fe650477123..895c8a73b08 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -55,6 +55,8 @@ pub struct ServoParser { last_chunk_received: Cell, /// Whether this parser should avoid passing any further data to the tokenizer. suspended: Cell, + /// https://html.spec.whatwg.org/multipage/#script-nesting-level + script_nesting_level: Cell, } #[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() { diff --git a/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_003.htm.ini b/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_003.htm.ini deleted file mode 100644 index 781c03c7a45..00000000000 --- a/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_003.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[async_003.htm] - type: testharness - [An async script does not block the parser while downloading] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_004.htm.ini b/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_004.htm.ini deleted file mode 100644 index 1dd50d3c085..00000000000 --- a/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_004.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[async_004.htm] - type: testharness - [async script executes as soon as possible after a download is complete] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_005.htm.ini b/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_005.htm.ini deleted file mode 100644 index a54cf3f64e2..00000000000 --- a/tests/wpt/metadata/html/semantics/scripting-1/the-script-element/async_005.htm.ini +++ /dev/null @@ -1,5 +0,0 @@ -[async_005.htm] - type: testharness - [A script element with both async and defer set should execute asynchronously] - expected: FAIL - diff --git a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/086.html.ini b/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/086.html.ini deleted file mode 100644 index cd50dc032c5..00000000000 --- a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/086.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[086.html] - type: testharness - [ scheduler: async script and slow-loading async script] - expected: FAIL - diff --git a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/088.html.ini b/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/088.html.ini deleted file mode 100644 index aee34579ac2..00000000000 --- a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/088.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[088.html] - type: testharness - [ scheduler: multiple scripts with defer and async attributes] - expected: FAIL - diff --git a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/111.html.ini b/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/111.html.ini deleted file mode 100644 index b48570d329b..00000000000 --- a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/111.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[111.html] - type: testharness - [ scheduler: removing async attribute at runtime] - expected: FAIL - diff --git a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/112.html.ini b/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/112.html.ini deleted file mode 100644 index ae9b23716a9..00000000000 --- a/tests/wpt/metadata/old-tests/submission/Opera/script_scheduling/112.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[112.html] - type: testharness - [ scheduler: removing async attribute at runtime, script also has defer attribute] - expected: FAIL -