diff --git a/moli-renderer-v8/src/document_runtime.rs b/moli-renderer-v8/src/document_runtime.rs index bf360f5086..1405d4686d 100644 --- a/moli-renderer-v8/src/document_runtime.rs +++ b/moli-renderer-v8/src/document_runtime.rs @@ -61,7 +61,8 @@ use crate::{ frame_owner_model::FrameDocumentTaskOwner, live_document_parser::{ DocumentParserLifetime, DocumentParserRunState, DocumentParserSession, - DocumentParserSessionControlHandle, ParserResumePermit, ParserSuspensionCause, + DocumentParserSessionControlHandle, ParserResumeOwner, ParserResumePermit, + ParserSuspensionCause, }, module_runtime::ModuleMapKey, network::ResourceRequestClient, @@ -294,11 +295,13 @@ impl ParserConnectedScriptBridge { } pub(crate) fn suspend(&self, cause: ParserSuspensionCause) -> ParserResumePermit { - self.parser_control.suspend(cause) + self.parser_control + .suspend(cause, ParserResumeOwner::DocumentWrite) } pub(crate) fn resume(&self, permit: ParserResumePermit) -> bool { - self.parser_control.resume(permit) + self.parser_control + .resume(permit, ParserResumeOwner::DocumentWrite) } pub(crate) fn begin_pump(&self) -> crate::live_document_parser::DocumentParserPumpGuard { diff --git a/moli-renderer-v8/src/document_runtime/document_write.rs b/moli-renderer-v8/src/document_runtime/document_write.rs index 151e3f09da..8a6d26e9f6 100644 --- a/moli-renderer-v8/src/document_runtime/document_write.rs +++ b/moli-renderer-v8/src/document_runtime/document_write.rs @@ -1867,7 +1867,7 @@ impl DocumentRuntime { let insertion = self.take_suspended_document_write_insertion( document_handle, parser_bridge, - ParserSuspensionCause::DocumentWriteExternalScript { script: node }, + ParserSuspensionCause::ParserClassicSource { script: node }, ); self.start_document_write_external_script_load( scope, @@ -1958,7 +1958,7 @@ impl DocumentRuntime { DocumentWriteScriptRunOutcome::Suspend(start) => { Self::resuspend_document_write_insertion( &mut insertion, - ParserSuspensionCause::DocumentWriteExternalScript { script: node }, + ParserSuspensionCause::ParserClassicSource { script: node }, ); self.start_document_write_external_script_load( scope, @@ -2184,7 +2184,7 @@ impl DocumentRuntime { let insertion = self.take_suspended_document_write_insertion( document_handle, parser_bridge, - ParserSuspensionCause::DocumentWriteExternalScript { script }, + ParserSuspensionCause::ParserClassicSource { script }, ); self.start_document_write_external_script_load( scope, diff --git a/moli-renderer-v8/src/live_document_parser.rs b/moli-renderer-v8/src/live_document_parser.rs index 9eacbd7037..1c513162cc 100644 --- a/moli-renderer-v8/src/live_document_parser.rs +++ b/moli-renderer-v8/src/live_document_parser.rs @@ -206,13 +206,23 @@ pub(crate) enum ParserSuspensionCause { ParserClassicSource { script: NativeNodeId }, ParserClassicStylesheets { script: NativeNodeId }, ParserCreatedStylesheet { owner: NativeNodeId }, - DocumentWriteExternalScript { script: NativeNodeId }, +} + +/// The component retaining the continuation for one exact parser suspension. +/// The same script or stylesheet blocker can be reached by either component. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ParserResumeOwner { + /// The main or child document's ordinary parser driver. + ParserDriver, + /// A suspended insertion retained by `DocumentRuntime`. + DocumentWrite, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct ParserSuspension { id: ParserSuspensionId, cause: ParserSuspensionCause, + resume_owner: ParserResumeOwner, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -237,6 +247,7 @@ pub(crate) enum DocumentParserRunState { Suspended { id: ParserSuspensionId, cause: ParserSuspensionCause, + resume_owner: ParserResumeOwner, }, Finishing, Finished, @@ -272,6 +283,7 @@ enum DocumentParserLifecycleState { Suspended { id: ParserSuspensionId, cause: ParserSuspensionCause, + resume_owner: ParserResumeOwner, }, Finishing, Finished, @@ -283,6 +295,7 @@ impl From for DocumentParserLifecycleState { Self::Suspended { id: suspension.id, cause: suspension.cause, + resume_owner: suspension.resume_owner, } } } @@ -290,7 +303,15 @@ impl From for DocumentParserLifecycleState { impl DocumentParserLifecycleState { fn suspension(self) -> Option { match self { - Self::Suspended { id, cause } => Some(ParserSuspension { id, cause }), + Self::Suspended { + id, + cause, + resume_owner, + } => Some(ParserSuspension { + id, + cause, + resume_owner, + }), _ => None, } } @@ -299,7 +320,18 @@ impl DocumentParserLifecycleState { match (self, pump_session_nesting_level) { (Self::Ready, nesting_level @ 1..) => DocumentParserRunState::Pumping { nesting_level }, (Self::Ready, 0) => DocumentParserRunState::Ready, - (Self::Suspended { id, cause }, _) => DocumentParserRunState::Suspended { id, cause }, + ( + Self::Suspended { + id, + cause, + resume_owner, + }, + _, + ) => DocumentParserRunState::Suspended { + id, + cause, + resume_owner, + }, (Self::Finishing, _) => DocumentParserRunState::Finishing, (Self::Finished, _) => DocumentParserRunState::Finished, (Self::Stopped(reason), _) => DocumentParserRunState::Stopped(reason), @@ -439,7 +471,11 @@ impl DocumentParserSessionControlHandle { true } - pub(crate) fn suspend(&self, cause: ParserSuspensionCause) -> ParserResumePermit { + pub(crate) fn suspend( + &self, + cause: ParserSuspensionCause, + resume_owner: ParserResumeOwner, + ) -> ParserResumePermit { let mut control = self.0.borrow_mut(); assert_eq!( control.lifecycle_state, @@ -454,6 +490,7 @@ impl DocumentParserSessionControlHandle { let suspension = ParserSuspension { id: suspension_id, cause, + resume_owner, }; if matches!( control.finish_request_state, @@ -478,7 +515,11 @@ impl DocumentParserSessionControlHandle { }) } - pub(crate) fn resume(&self, permit: ParserResumePermit) -> bool { + pub(crate) fn resume( + &self, + permit: ParserResumePermit, + resume_owner: ParserResumeOwner, + ) -> bool { let mut control = self.0.borrow_mut(); if permit.session_id != control.session_id { return false; @@ -486,7 +527,7 @@ impl DocumentParserSessionControlHandle { let Some(suspension) = control.lifecycle_state.suspension() else { return false; }; - if suspension.id != permit.suspension_id { + if suspension.id != permit.suspension_id || suspension.resume_owner != resume_owner { return false; } control.lifecycle_state = DocumentParserLifecycleState::Ready; @@ -809,7 +850,7 @@ impl DocumentParserSession { } pub(crate) fn suspend(&mut self, cause: ParserSuspensionCause) -> ParserResumePermit { - self.control.suspend(cause) + self.control.suspend(cause, ParserResumeOwner::ParserDriver) } pub(crate) fn current_resume_permit(&self) -> Option { @@ -817,7 +858,7 @@ impl DocumentParserSession { } pub(crate) fn resume(&mut self, permit: ParserResumePermit) -> bool { - self.control.resume(permit) + self.control.resume(permit, ParserResumeOwner::ParserDriver) } pub(crate) fn stop(&mut self, reason: ParserStopReason) { @@ -1228,13 +1269,52 @@ mod session_state_tests { assert!(!other.resume(first)); assert!(parser.resume(first)); - let second = parser.suspend(ParserSuspensionCause::DocumentWriteExternalScript { + let second = parser.suspend(ParserSuspensionCause::ParserClassicSource { script: NativeNodeId::new(6), }); assert!(!parser.resume(first)); assert!(parser.resume(second)); } + #[test] + fn parser_resume_permits_cannot_cross_continuation_owners() { + let mut parser = session(); + let bridge = crate::document_runtime::ParserConnectedScriptBridge::for_session(&parser) + .expect("HTML parser bridge"); + for cause in [ + ParserSuspensionCause::ParserClassicSource { + script: NativeNodeId::new(8), + }, + ParserSuspensionCause::ParserClassicStylesheets { + script: NativeNodeId::new(8), + }, + ParserSuspensionCause::ParserCreatedStylesheet { + owner: NativeNodeId::new(9), + }, + ] { + let driver_permit = parser.suspend(cause); + let driver_state = parser.run_state(); + assert!(!bridge.resume(driver_permit)); + assert_eq!(parser.run_state(), driver_state); + assert!(parser.resume(driver_permit)); + + let insertion_permit = bridge.suspend(cause); + let insertion_state = parser.run_state(); + assert!(matches!( + insertion_state, + DocumentParserRunState::Suspended { + resume_owner: ParserResumeOwner::DocumentWrite, + .. + } + )); + assert!(!parser.resume(insertion_permit)); + assert!(!bridge.resume(driver_permit)); + assert_eq!(parser.run_state(), insertion_state); + assert!(bridge.resume(insertion_permit)); + assert!(!bridge.resume(insertion_permit)); + } + } + #[test] fn close_defers_without_consuming_the_active_parser_suspension() { let mut parser = DocumentParserSession::start_open_live_document( diff --git a/moli-renderer-v8/src/live_document_parser/insertion.rs b/moli-renderer-v8/src/live_document_parser/insertion.rs index 7bcffdaa1d..658cedc76f 100644 --- a/moli-renderer-v8/src/live_document_parser/insertion.rs +++ b/moli-renderer-v8/src/live_document_parser/insertion.rs @@ -1,7 +1,7 @@ use super::{ DocumentParserCloseDisposition, DocumentParserLifetime, DocumentParserRunState, DocumentParserSessionControlHandle, LiveDocumentParserDiscoverySignals, - LiveDocumentParserOwner, LiveDocumentParserStepOutcome, ParserStopReason, + LiveDocumentParserOwner, LiveDocumentParserStepOutcome, ParserResumeOwner, ParserStopReason, ParserSuspensionCause, advance_next_live_document_parser_step, }; use std::{cell::RefCell, rc::Rc}; @@ -38,7 +38,7 @@ impl ParserInsertionHandle { } pub(crate) fn suspend(&self, cause: ParserSuspensionCause) { - let _ = self.control.suspend(cause); + let _ = self.control.suspend(cause, ParserResumeOwner::ParserDriver); } pub(crate) fn stop(&self, reason: ParserStopReason) { diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_documents/parser_store.rs b/moli-renderer-v8/src/native_bridge/context_host/child_documents/parser_store.rs index 1ba6ca0e13..d667ea9960 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_documents/parser_store.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_documents/parser_store.rs @@ -4,7 +4,8 @@ use crate::{ frame_owner_model::FrameDocumentOwner, live_document_parser::{ DocumentParserRunState, DocumentParserSession, DocumentParserSessionControlHandle, - ParserInsertionHandle, ParserResumePermit, ParserStopReason, ParserSuspensionCause, + ParserInsertionHandle, ParserResumeOwner, ParserResumePermit, ParserStopReason, + ParserSuspensionCause, }, }; use std::{cell::RefCell, collections::HashMap, rc::Rc}; @@ -153,7 +154,7 @@ impl ChildDocumentParserStore { permit: ParserResumePermit, ) -> Option { self.control_handle(owner) - .map(|parser| parser.resume(permit)) + .map(|parser| parser.resume(permit, ParserResumeOwner::ParserDriver)) } pub(in crate::native_bridge::context_host) fn is_suspended_on_parser_created_stylesheet( diff --git a/moli-renderer-v8/src/runtime/phase_one/document_write_resource_tests.rs b/moli-renderer-v8/src/runtime/phase_one/document_write_resource_tests.rs index 1c21011950..394b0f4000 100644 --- a/moli-renderer-v8/src/runtime/phase_one/document_write_resource_tests.rs +++ b/moli-renderer-v8/src/runtime/phase_one/document_write_resource_tests.rs @@ -7,6 +7,9 @@ use tokio::net::TcpListener; use tokio::task::JoinHandle; use url::Url; +use crate::live_document_parser::{ + DocumentParserRunState, ParserResumeOwner, ParserSuspensionCause, +}; use crate::page_resource_completion::{ PageResourceCompletionDocumentEffect, PageResourceCompletionOutputEffect, RendererPageResourceCompletion, RendererPageResourceCompletionOwner, @@ -277,6 +280,30 @@ async fn start_standalone_document_write_page_for_page_id( page_id: PageId, html: String, document_url: Url, +) -> PendingStandaloneDocumentWritePage { + let pending = start_standalone_parser_page(page_id, html, document_url).await; + let target = pending + .runtime + .page_vm + .vm() + .current_document_write_external_script_fetch_target() + .expect("pending document.write load should retain its exact target"); + assert_eq!( + pending + .runtime + .page_vm + .vm() + .current_main_document_task_owner(), + Some(target.task_owner()), + "producer target must capture the current main Document owner" + ); + pending +} + +async fn start_standalone_parser_page( + page_id: PageId, + html: String, + document_url: Url, ) -> PendingStandaloneDocumentWritePage { let loader_owner = ResourceRequestClient::new(&FetchConfig::default()).expect("document.write test loader"); @@ -328,16 +355,6 @@ async fn start_standalone_document_write_page_for_page_id( runtime .page_vm .retain_standalone_request_client_owner_for_test(loader_owner); - let target = runtime - .page_vm - .vm() - .current_document_write_external_script_fetch_target() - .expect("pending document.write load should retain its exact target"); - assert_eq!( - runtime.page_vm.vm().current_main_document_task_owner(), - Some(target.task_owner()), - "producer target must capture the current main Document owner" - ); PendingStandaloneDocumentWritePage { runtime, started, @@ -633,6 +650,125 @@ async fn evaluate_pending_on_owner_local_task( .expect("pending document.write result should evaluate") } +#[test] +fn parser_driver_retains_stylesheet_suspension_until_its_continuation() { + assert_stylesheet_suspension_resume_owner(ParserResumeOwner::ParserDriver); +} + +#[test] +fn document_write_retains_stylesheet_suspension_until_its_continuation() { + assert_stylesheet_suspension_resume_owner(ParserResumeOwner::DocumentWrite); +} + +fn assert_stylesheet_suspension_resume_owner(resume_owner: ParserResumeOwner) { + super::tests::run_phase_one_large_stack_test("stylesheet-suspension-owner", move || { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("current-thread runtime"); + runtime.block_on(tokio::task::LocalSet::new().run_until(async move { + let _js_runtime = crate::JsRuntime::initialize(); + let listener = TcpListener::bind("127.0.0.1:0").await.expect("stylesheet server"); + let address = listener.local_addr().expect("server address"); + let document_url = Url::parse(&format!("http://{address}/page.html")).unwrap(); + let (release_tx, release_rx) = tokio::sync::oneshot::channel(); + let server = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await.expect("stylesheet request"); + let mut request = vec![0; 4096]; + let read = stream.read(&mut request).await.expect("read stylesheet request"); + assert!(String::from_utf8_lossy(&request[..read]).starts_with("GET /slow.css ")); + release_rx.await.expect("release stylesheet response"); + let css = "html { color: rgb(1, 2, 3); }"; + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: text/css\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{css}", + css.len(), + ); + stream.write_all(response.as_bytes()).await.expect("stylesheet response"); + stream.shutdown().await.expect("stylesheet response closed"); + }); + let blocked_html = r#""#; + let html = match resume_owner { + ParserResumeOwner::ParserDriver => format!( + "{blocked_html}

tail

", + ), + ParserResumeOwner::DocumentWrite => format!( + "

tail

", + blocked_html.replace("", "<\\/script>"), + ), + }; + let mut pending = start_standalone_parser_page( + PageId::new_for_testing(902), html, document_url, + ).await; + let suspended_state = pending.runtime.state.parser_session.run_state(); + assert!(matches!(suspended_state, DocumentParserRunState::Suspended { + cause: ParserSuspensionCause::ParserClassicStylesheets { .. }, + resume_owner: actual_owner, .. + } if actual_owner == resume_owner)); + let (next_pending, result) = evaluate_pending_on_owner_local_task( + pending, + "JSON.stringify({events: __resumeEvents, tail: !!document.getElementById('parser-tail')})", + ).await; + pending = next_pending; + let expected_before = match resume_owner { + ParserResumeOwner::ParserDriver => r#"{"events":["outer"],"tail":false}"#, + ParserResumeOwner::DocumentWrite => r#"{"events":["outer","outer-done"],"tail":false}"#, + }; + assert_eq!(result.get("value").and_then(serde_json::Value::as_str), Some(expected_before)); + + // A fresh parser turn must preserve the same blocker while CSS is pending. + let executor = pending.runtime.page_vm.local_executor.clone(); + pending = super::access::run_named_owner_local_task( + executor, + "stylesheet-suspended parser turn channel closed", + async move { + pending.runtime.owner = ParseTimeOwner::Parser; + pending.runtime.pending_parsing_blocking_wait = PendingParsingBlockingWait::None; + assert_eq!(pending.runtime.drive_owner_step().await?, OwnerStepProgress::BlockedOnPageTask); + assert_eq!(pending.runtime.state.parser_session.run_state(), suspended_state); + Ok(pending) + }, + ).await.expect("parser should remain suspended"); + + release_tx.send(()).expect("release CSS"); + tokio::time::timeout( + std::time::Duration::from_secs(2), + wait_for_standalone_stylesheet_completion(&mut pending), + ).await.expect("CSS completion should arrive"); + pending = run_standalone_selected_page_task( + pending, + crate::runtime::page_vm::PageSelectedTaskTestSelector::StylesheetCompletion, + ).await; + assert_eq!( + pending.runtime.state.parser_session.run_state(), suspended_state, + "CSS completion must leave resumption to the selected parser continuation", + ); + let PendingStandaloneDocumentWritePage { runtime, started, owner_wake_rx } = pending; + let (outcome, _) = resume_standalone_main_parser_continuation_if_ready( + ParseTimePageVmCreationOutcome::PendingPhaseOne( + PendingPhaseOneResidence::ClosedInputPageWork { runtime, started }, + ), + owner_wake_rx, + ).await; + let ParseTimePageVmCreationOutcome::ContinuePhaseTwo { page_vm, .. } = outcome else { + panic!("the owning continuation should finish the parser"); + }; + let result = evaluate_on_owner_local_task( + page_vm, + "JSON.stringify({events: __resumeEvents, tailAtScript: __tailAtScript, tail: !!document.getElementById('parser-tail')})", + ).await; + let expected_after = match resume_owner { + ParserResumeOwner::ParserDriver => r#"{"events":["outer","script"],"tailAtScript":false,"tail":true}"#, + ParserResumeOwner::DocumentWrite => r#"{"events":["outer","outer-done","script"],"tailAtScript":false,"tail":true}"#, + }; + assert_eq!(result.get("value").and_then(serde_json::Value::as_str), Some(expected_after)); + server.await.expect("stylesheet server should finish"); + })); + }); +} + #[test] fn standalone_document_write_external_script_uses_typed_phase_one_route() { super::tests::run_phase_one_large_stack_test("typed-document-write-phase-one", || { diff --git a/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs b/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs index 7c9b8329fd..a09c6582cd 100644 --- a/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs +++ b/moli-renderer-v8/src/runtime/phase_one/parser_turn.rs @@ -19,8 +19,8 @@ use super::*; use crate::document_runtime::parser_script_preparation_failure_page_owned_work; use crate::dom::native::{Attribute, DomMutationEffects, NativeNodeId}; use crate::live_document_parser::{ - DocumentParserFinishRequestState, LiveDocumentParserOwner, LiveDocumentParserStepOutcome, - ParserSuspensionCause, + DocumentParserFinishRequestState, DocumentParserRunState, LiveDocumentParserOwner, + LiveDocumentParserStepOutcome, ParserResumeOwner, ParserSuspensionCause, }; use crate::parser::{ ParserDomMutation, ParserDomMutationConsumer, ParserDomReadConsumer, @@ -538,22 +538,36 @@ impl<'loader, 'state> ParserDriver<'loader, 'state> { ); return Ok(owner_step_progress_after_current_document_stop(page_vm)); } - match self.parser_session.suspension_cause() { - Some(ParserSuspensionCause::ParserClassicStylesheets { .. }) - if page_vm - .vm() - .document_runtime - .has_pending_document_write_stylesheet_blocked_script() => - { - // A document.write() insertion owns this suspension and its - // script. Its stylesheet completion is admitted on the Page - // lane; the main parser runner has no pending script to resume. + match self.parser_session.run_state() { + DocumentParserRunState::Suspended { + resume_owner: ParserResumeOwner::DocumentWrite, + cause: + ParserSuspensionCause::ParserClassicStylesheets { .. } + | ParserSuspensionCause::ParserCreatedStylesheet { .. }, + .. + } => { + // The insertion retains this suspension's resume permit. + // Its continuation must run through the Page scheduler. return Ok(suspend_parser_for_stylesheet_page_task( owner, pending_parsing_blocking_wait, )); } - Some(ParserSuspensionCause::ParserCreatedStylesheet { .. }) => { + DocumentParserRunState::Suspended { + resume_owner: ParserResumeOwner::DocumentWrite, + cause: ParserSuspensionCause::ParserClassicSource { .. }, + .. + } => { + *owner = ParseTimeOwner::Document; + *pending_parsing_blocking_wait = + PendingParsingBlockingWait::PageNetworkingDocumentWriteExternalScript; + return Ok(OwnerStepProgress::BlockedOnPageTask); + } + DocumentParserRunState::Suspended { + resume_owner: ParserResumeOwner::ParserDriver, + cause: ParserSuspensionCause::ParserCreatedStylesheet { .. }, + .. + } => { if !page_vm .vm() .document_runtime @@ -573,31 +587,7 @@ impl<'loader, 'state> ParserDriver<'loader, 'state> { "the admitted stylesheet continuation must resume its exact parser suspension" ); } - Some(ParserSuspensionCause::DocumentWriteExternalScript { .. }) => { - if page_vm - .vm() - .document_runtime - .has_pending_document_write_external_script_load() - { - *owner = ParseTimeOwner::Document; - *pending_parsing_blocking_wait = - PendingParsingBlockingWait::PageNetworkingDocumentWriteExternalScript; - return Ok(OwnerStepProgress::BlockedOnPageTask); - } - let permit = self - .parser_session - .current_resume_permit() - .expect("a document.write-suspended parser must retain its resume permit"); - assert!( - self.parser_session.resume(permit), - "the admitted document.write continuation must resume its exact parser suspension" - ); - } - Some( - ParserSuspensionCause::ParserClassicSource { .. } - | ParserSuspensionCause::ParserClassicStylesheets { .. }, - ) - | None => {} + _ => {} } debug_assert!( !pending_parsing_blocking_wait.is_pending(), @@ -1637,9 +1627,7 @@ impl<'loader, 'state> ParserDriver<'loader, 'state> { break ParserStepAdvanceOutcome::StoppedCurrentDocument; } ScriptHandoffOutcome::NoNavigation => { - if let Some(outcome) = - self.document_write_suspension_step_outcome(page_vm) - { + if let Some(outcome) = self.document_write_suspension_step_outcome() { break outcome; } } @@ -1759,9 +1747,7 @@ impl<'loader, 'state> ParserDriver<'loader, 'state> { break ParserStepAdvanceOutcome::StoppedCurrentDocument; } ScriptHandoffOutcome::NoNavigation => { - if let Some(outcome) = - self.document_write_suspension_step_outcome(page_vm) - { + if let Some(outcome) = self.document_write_suspension_step_outcome() { break outcome; } } @@ -1785,30 +1771,23 @@ impl<'loader, 'state> ParserDriver<'loader, 'state> { Ok(outcome) } - fn document_write_suspension_step_outcome( - &self, - page_vm: &PageVm, - ) -> Option { - match self.parser_session.suspension_cause() { - Some(ParserSuspensionCause::ParserClassicStylesheets { .. }) - if page_vm - .vm() - .document_runtime - .has_pending_document_write_stylesheet_blocked_script() => - { - Some(ParserStepAdvanceOutcome::BlockedOnStylesheetParserPause) + fn document_write_suspension_step_outcome(&self) -> Option { + let DocumentParserRunState::Suspended { + resume_owner: ParserResumeOwner::DocumentWrite, + cause, + .. + } = self.parser_session.run_state() + else { + return None; + }; + Some(match cause { + ParserSuspensionCause::ParserClassicStylesheets { .. } + | ParserSuspensionCause::ParserCreatedStylesheet { .. } => { + ParserStepAdvanceOutcome::BlockedOnStylesheetParserPause } - Some(ParserSuspensionCause::ParserCreatedStylesheet { .. }) => { - Some(ParserStepAdvanceOutcome::BlockedOnStylesheetParserPause) + ParserSuspensionCause::ParserClassicSource { .. } => { + ParserStepAdvanceOutcome::BlockedOnDocumentWriteExternalLoad } - Some(ParserSuspensionCause::DocumentWriteExternalScript { .. }) => { - Some(ParserStepAdvanceOutcome::BlockedOnDocumentWriteExternalLoad) - } - Some( - ParserSuspensionCause::ParserClassicSource { .. } - | ParserSuspensionCause::ParserClassicStylesheets { .. }, - ) - | None => None, - } + }) } }