mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 00:00:55 +00:00
1043 lines
41 KiB
Rust
1043 lines
41 KiB
Rust
use crate::document_runtime::DomHandle;
|
|
use crate::parser_script::action::{
|
|
ParserClassicScriptNetworkRecordUrls, ParserClassicScriptRunnerStep,
|
|
ParserPendingClassicScriptBeginExecutionAction, ParserPendingClassicScriptDisposedReadyAction,
|
|
ParserPendingClassicScriptFinishedExecutionAction, ParserPendingClassicScriptReadyAction,
|
|
ParserPendingClassicScriptSourceFailureAction, ParserPendingClassicScriptSourceLoadAction,
|
|
ParserPendingClassicScriptSourceLoadCandidate,
|
|
ParserPendingClassicScriptSourceLoadClientAction,
|
|
ParserPendingClassicScriptSourceLoadCompletionAction,
|
|
ParserPendingClassicScriptSourceLoadCompletionRecord,
|
|
ParserPendingClassicScriptSourceLoadRequest, ParserPendingClassicScriptSourceLoadWaitAction,
|
|
ParserPendingClassicScriptSourceResultAction,
|
|
};
|
|
use crate::parser_script::context::{
|
|
ParserClassicScriptExecutionGateState, ParserClassicScriptSourceLoadCompletionState,
|
|
ParserClassicScriptSourceLoadOutcomeState, ParserClassicScriptSourceLoadStartState,
|
|
ParserClassicScriptSourceLoadState, ParserClassicScriptSourceLoadWaitState,
|
|
ParserClassicScriptSourceResultState,
|
|
};
|
|
use crate::parser_script::item::ParserClassicScriptRunnerItem;
|
|
use crate::parser_script::owner::{
|
|
ParserScriptBeginExecutionOwner, ParserScriptBeginSourceLoadOwner,
|
|
ParserScriptDisposeReadyOwner, ParserScriptExecutionGate, ParserScriptFinishExecutionOwner,
|
|
ParserScriptOwner, ParserScriptReadyOwner, ParserScriptSourceFailureOwner,
|
|
ParserScriptSourceLoadClientOwner, ParserScriptSourceLoadCompletionOwner,
|
|
ParserScriptSourceLoadWaitOwner, ParserScriptSourceResultOwner,
|
|
};
|
|
#[cfg(test)]
|
|
use crate::parser_script::payload::{
|
|
ParserClassicScriptMetadata, ParserClassicScriptSourceFailure, ParserPreparedClassicScript,
|
|
};
|
|
use crate::parser_script::payload::{
|
|
ParserClassicScriptSourceResult, ParserExecutableClassicScript,
|
|
};
|
|
#[cfg(test)]
|
|
use crate::parser_script::pending::ParserPendingClassicScriptEntry;
|
|
use crate::parser_script::projection::{
|
|
ParserClassicScriptBlockedOnExecution, ParserClassicScriptBlockedOnSourceLoad,
|
|
ParserClassicScriptExecutionGateProjection, ParserClassicScriptNextActionWithBlockedScript,
|
|
ParserClassicScriptSourceResultApplication,
|
|
};
|
|
use crate::parser_script::queue::ParserClassicScriptRunnerQueue;
|
|
#[cfg(test)]
|
|
use crate::parser_script::slot::ParserClassicScriptRunnerSlot;
|
|
use crate::planning::PreparedScript;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct ParserClassicScriptRunner<C> {
|
|
parser_blocking_scripts: ParserClassicScriptRunnerQueue<C>,
|
|
deferred_scripts: ParserClassicScriptRunnerQueue<C>,
|
|
}
|
|
|
|
impl<C> ParserClassicScriptRunner<C>
|
|
where
|
|
C: ParserClassicScriptExecutionGateState,
|
|
{
|
|
pub(crate) fn empty() -> Self {
|
|
Self {
|
|
parser_blocking_scripts: ParserClassicScriptRunnerQueue::empty(),
|
|
deferred_scripts: ParserClassicScriptRunnerQueue::empty(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn from_parser_blocking_script(script: ParserClassicScriptRunnerItem<C>) -> Self {
|
|
let mut runner = Self::empty();
|
|
runner.parser_blocking_scripts.push(script);
|
|
runner
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn new_parser_blocking(
|
|
scripts: impl IntoIterator<Item = ParserClassicScriptRunnerItem<C>>,
|
|
) -> Self {
|
|
Self {
|
|
parser_blocking_scripts: ParserClassicScriptRunnerQueue::new(scripts),
|
|
deferred_scripts: ParserClassicScriptRunnerQueue::empty(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn push_parser_blocking_script(&mut self, script: ParserClassicScriptRunnerItem<C>) {
|
|
self.parser_blocking_scripts.push(script);
|
|
}
|
|
|
|
pub(crate) fn push_deferred_external_script_with_load_action<Owner>(
|
|
&mut self,
|
|
mut script: ParserClassicScriptRunnerItem<C>,
|
|
load_id: u64,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceLoadAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadStartState,
|
|
Owner: ParserScriptBeginSourceLoadOwner<C>,
|
|
{
|
|
let action =
|
|
script.begin_runner_external_load_with_load_id_and_owner(Some(load_id), owner)?;
|
|
self.deferred_scripts.push(script);
|
|
Some(action)
|
|
}
|
|
|
|
pub(crate) fn has_parser_blocking_script(&self) -> bool {
|
|
self.parser_blocking_scripts.has_current()
|
|
}
|
|
|
|
pub(crate) fn current_parser_blocking_context(&self) -> Option<&C> {
|
|
Some(self.parser_blocking_scripts.current()?.context())
|
|
}
|
|
|
|
pub(crate) fn current_deferred_context(&self) -> Option<&C> {
|
|
Some(self.deferred_scripts.current()?.context())
|
|
}
|
|
|
|
pub(crate) fn all_script_contexts_match(&self, predicate: impl FnMut(&C) -> bool) -> bool {
|
|
let mut predicate = predicate;
|
|
self.parser_blocking_scripts
|
|
.all_contexts_match(&mut predicate)
|
|
&& self.deferred_scripts.all_contexts_match(predicate)
|
|
}
|
|
|
|
pub(crate) fn current_parser_blocking_script_handle(&self) -> Option<DomHandle> {
|
|
self.current_parser_blocking_script()?
|
|
.runner_metadata()
|
|
.map(|metadata| metadata.script_handle())
|
|
}
|
|
|
|
pub(crate) fn current_deferred_script_handle(&self) -> Option<DomHandle> {
|
|
self.deferred_scripts
|
|
.current()?
|
|
.runner_metadata()
|
|
.map(|metadata| metadata.script_handle())
|
|
}
|
|
|
|
pub(crate) fn install_parser_blocking_script_blocked_on_execution(
|
|
&mut self,
|
|
blocked: ParserClassicScriptBlockedOnExecution<C>,
|
|
) {
|
|
self.parser_blocking_scripts
|
|
.install_current(blocked.into_script());
|
|
}
|
|
|
|
pub(crate) fn install_parser_blocking_script_blocked_on_source_load(
|
|
&mut self,
|
|
blocked: ParserClassicScriptBlockedOnSourceLoad<C>,
|
|
) {
|
|
self.parser_blocking_scripts
|
|
.install_current(blocked.into_script());
|
|
}
|
|
|
|
pub(crate) fn discard_current_parser_blocking_script_if_handle(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
) -> bool {
|
|
if self.current_parser_blocking_script_handle() != Some(script_handle) {
|
|
return false;
|
|
}
|
|
self.parser_blocking_scripts.finish_current();
|
|
true
|
|
}
|
|
|
|
pub(crate) fn discard_current_deferred_script_if_handle(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
) -> bool {
|
|
if self.current_deferred_script_handle() != Some(script_handle) {
|
|
return false;
|
|
}
|
|
self.deferred_scripts.finish_current();
|
|
true
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn is_complete(&self) -> bool {
|
|
self.parser_blocking_scripts.is_empty() && self.deferred_scripts.is_empty()
|
|
}
|
|
|
|
pub(crate) fn current_parser_blocking_script(
|
|
&self,
|
|
) -> Option<&ParserClassicScriptRunnerItem<C>> {
|
|
self.parser_blocking_scripts.current()
|
|
}
|
|
|
|
pub(crate) fn apply_current_parser_blocking_preloaded_script(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
prepared_script: PreparedScript,
|
|
) -> bool {
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if script
|
|
.runner_metadata()
|
|
.map(|metadata| metadata.script_handle())
|
|
!= Some(script_handle)
|
|
{
|
|
return Some(false);
|
|
}
|
|
let Some(current) = script.runner_script_mut() else {
|
|
return Some(false);
|
|
};
|
|
*current = prepared_script;
|
|
Some(script.promote_runner_external_pending_to_ready())
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
pub(crate) fn current_parser_blocking_execution_gate_with_owner<Owner>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> ParserClassicScriptExecutionGateProjection<C>
|
|
where
|
|
C: Clone,
|
|
Owner: ParserScriptOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return Some(ParserClassicScriptExecutionGateProjection::NoCurrent);
|
|
}
|
|
Some(
|
|
match owner.parser_script_execution_gate(
|
|
script.context().parser_classic_execution_gate_state(),
|
|
) {
|
|
ParserScriptExecutionGate::Ready => {
|
|
ParserClassicScriptExecutionGateProjection::Ready
|
|
}
|
|
ParserScriptExecutionGate::Blocked(blocker) => {
|
|
ParserClassicScriptExecutionGateProjection::Blocked(
|
|
ParserClassicScriptBlockedOnExecution::new(blocker, script.clone()),
|
|
)
|
|
}
|
|
},
|
|
)
|
|
})
|
|
.unwrap_or(ParserClassicScriptExecutionGateProjection::NoCurrent)
|
|
}
|
|
|
|
pub(crate) fn current_parser_blocking_source_load_client_action_with_owner<Owner>(
|
|
&self,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceLoadClientAction>
|
|
where
|
|
Owner: ParserScriptSourceLoadClientOwner<C>,
|
|
{
|
|
let script = self.current_parser_blocking_script()?;
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let script_url = script.runner_external_pending_script_url()?;
|
|
let metadata = script.runner_metadata()?;
|
|
let client = ParserPendingClassicScriptSourceLoadClientAction::new(metadata, script_url);
|
|
owner.parser_script_source_load_client_action(client)
|
|
}
|
|
|
|
pub(crate) fn current_parser_blocking_source_load_completion_action_with_owner<Owner>(
|
|
&self,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceLoadCompletionAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadCompletionState,
|
|
Owner: ParserScriptSourceLoadCompletionOwner<C>,
|
|
{
|
|
let script = self.current_parser_blocking_script()?;
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let (source_identity, _) = script.runner_external_load_identity()?;
|
|
let record = ParserPendingClassicScriptSourceLoadCompletionRecord::new(source_identity);
|
|
let completion = ParserPendingClassicScriptSourceLoadCompletionAction::new(
|
|
script.context().parser_classic_source_load_owner(),
|
|
record,
|
|
);
|
|
owner.parser_script_source_load_completion_action(completion)
|
|
}
|
|
|
|
pub(crate) fn deferred_source_load_completion_action_with_owner<Owner>(
|
|
&self,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceLoadCompletionAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadCompletionState,
|
|
Owner: ParserScriptSourceLoadCompletionOwner<C>,
|
|
{
|
|
self.deferred_scripts.find_map(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let (source_identity, _) = script.runner_external_load_identity()?;
|
|
let record = ParserPendingClassicScriptSourceLoadCompletionRecord::new(source_identity);
|
|
owner.parser_script_source_load_completion_action(
|
|
ParserPendingClassicScriptSourceLoadCompletionAction::new(
|
|
script.context().parser_classic_source_load_owner(),
|
|
record,
|
|
),
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn current_parser_blocking_source_load_wait_action_with_owner<Owner>(
|
|
&self,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceLoadWaitAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadWaitState,
|
|
Owner: ParserScriptSourceLoadWaitOwner<C>,
|
|
{
|
|
let script = self.current_parser_blocking_script()?;
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let action = ParserPendingClassicScriptSourceLoadWaitAction::new(
|
|
script.context().parser_classic_source_load_wait(),
|
|
);
|
|
owner.parser_script_source_load_wait_action(action)
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_next_action_with_owner<Owner, Action>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> Option<Action>
|
|
where
|
|
C: Clone,
|
|
Owner: ParserScriptReadyOwner<C, ReadyAction = Action>
|
|
+ ParserScriptSourceFailureOwner<C, SourceFailureAction = Action>,
|
|
{
|
|
match self.take_current_parser_blocking_next_action_or_blocked_script_with_owner(owner) {
|
|
ParserClassicScriptNextActionWithBlockedScript::Action(action) => Some(action),
|
|
ParserClassicScriptNextActionWithBlockedScript::Blocked(_)
|
|
| ParserClassicScriptNextActionWithBlockedScript::NotReady => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn take_current_deferred_next_action_with_owner<Owner, Action>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> Option<Action>
|
|
where
|
|
C: Clone,
|
|
Owner: ParserScriptReadyOwner<C, ReadyAction = Action>
|
|
+ ParserScriptSourceFailureOwner<C, SourceFailureAction = Action>,
|
|
{
|
|
match self.take_current_deferred_ready_action_with_owner(owner) {
|
|
ParserClassicScriptNextActionWithBlockedScript::Action(action) => Some(action),
|
|
ParserClassicScriptNextActionWithBlockedScript::Blocked(_) => None,
|
|
ParserClassicScriptNextActionWithBlockedScript::NotReady => {
|
|
self.take_current_deferred_source_failure_action_with_owner(owner)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn take_current_deferred_ready_action_with_owner<Owner>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> ParserClassicScriptNextActionWithBlockedScript<Owner::ReadyAction, C>
|
|
where
|
|
C: Clone,
|
|
Owner: ParserScriptReadyOwner<C>,
|
|
{
|
|
self.deferred_scripts
|
|
.update_current_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return Some(ParserClassicScriptNextActionWithBlockedScript::NotReady);
|
|
}
|
|
match owner.parser_script_execution_gate(
|
|
script.context().parser_classic_execution_gate_state(),
|
|
) {
|
|
ParserScriptExecutionGate::Ready => {}
|
|
ParserScriptExecutionGate::Blocked(blocker) => {
|
|
return Some(ParserClassicScriptNextActionWithBlockedScript::Blocked(
|
|
ParserClassicScriptBlockedOnExecution::new(blocker, script.clone()),
|
|
));
|
|
}
|
|
}
|
|
let ParserClassicScriptRunnerStep::Ready(ready) = script.runner_step()? else {
|
|
return Some(ParserClassicScriptNextActionWithBlockedScript::NotReady);
|
|
};
|
|
let ready_action = ParserPendingClassicScriptReadyAction::new(&ready);
|
|
Some(
|
|
owner
|
|
.parser_script_ready_action(script.context(), ready_action)
|
|
.map_or(
|
|
ParserClassicScriptNextActionWithBlockedScript::NotReady,
|
|
ParserClassicScriptNextActionWithBlockedScript::Action,
|
|
),
|
|
)
|
|
})
|
|
.unwrap_or(ParserClassicScriptNextActionWithBlockedScript::NotReady)
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_next_action_or_blocked_script_with_owner<
|
|
Owner,
|
|
Action,
|
|
>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> ParserClassicScriptNextActionWithBlockedScript<Action, C>
|
|
where
|
|
C: Clone,
|
|
Owner: ParserScriptReadyOwner<C, ReadyAction = Action>
|
|
+ ParserScriptSourceFailureOwner<C, SourceFailureAction = Action>,
|
|
{
|
|
match self.take_current_parser_blocking_ready_action_or_blocked_script_with_owner(owner) {
|
|
ParserClassicScriptNextActionWithBlockedScript::Action(action) => {
|
|
ParserClassicScriptNextActionWithBlockedScript::Action(action)
|
|
}
|
|
ParserClassicScriptNextActionWithBlockedScript::Blocked(blocked) => {
|
|
ParserClassicScriptNextActionWithBlockedScript::Blocked(blocked)
|
|
}
|
|
ParserClassicScriptNextActionWithBlockedScript::NotReady => self
|
|
.take_current_parser_blocking_source_failure_action_with_owner(owner)
|
|
.map_or(
|
|
ParserClassicScriptNextActionWithBlockedScript::NotReady,
|
|
ParserClassicScriptNextActionWithBlockedScript::Action,
|
|
),
|
|
}
|
|
}
|
|
|
|
fn take_current_parser_blocking_ready_action_or_blocked_script_with_owner<Owner>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> ParserClassicScriptNextActionWithBlockedScript<Owner::ReadyAction, C>
|
|
where
|
|
C: Clone,
|
|
Owner: ParserScriptReadyOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return Some(ParserClassicScriptNextActionWithBlockedScript::NotReady);
|
|
}
|
|
match owner.parser_script_execution_gate(
|
|
script.context().parser_classic_execution_gate_state(),
|
|
) {
|
|
ParserScriptExecutionGate::Ready => {}
|
|
ParserScriptExecutionGate::Blocked(blocker) => {
|
|
return Some(ParserClassicScriptNextActionWithBlockedScript::Blocked(
|
|
ParserClassicScriptBlockedOnExecution::new(blocker, script.clone()),
|
|
));
|
|
}
|
|
}
|
|
let ParserClassicScriptRunnerStep::Ready(ready) = script.runner_step()? else {
|
|
return Some(ParserClassicScriptNextActionWithBlockedScript::NotReady);
|
|
};
|
|
let ready_action = ParserPendingClassicScriptReadyAction::new(&ready);
|
|
Some(
|
|
owner
|
|
.parser_script_ready_action(script.context(), ready_action)
|
|
.map_or(
|
|
ParserClassicScriptNextActionWithBlockedScript::NotReady,
|
|
ParserClassicScriptNextActionWithBlockedScript::Action,
|
|
),
|
|
)
|
|
})
|
|
.unwrap_or(ParserClassicScriptNextActionWithBlockedScript::NotReady)
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_begin_execution_action_with_owner<Owner>(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::BeginExecutionAction>
|
|
where
|
|
Owner: ParserScriptBeginExecutionOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let ParserClassicScriptRunnerStep::Ready(_) = script.runner_step()? else {
|
|
return None;
|
|
};
|
|
let executable_script = script
|
|
.runner_script()
|
|
.cloned()
|
|
.and_then(ParserExecutableClassicScript::from_prepared_script)?;
|
|
let execution = script.begin_runner_ready_execution(script_handle)?;
|
|
let action =
|
|
ParserPendingClassicScriptBeginExecutionAction::new(execution, executable_script);
|
|
owner.parser_script_begin_execution_action(action)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_deferred_begin_execution_action_with_owner<Owner>(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::BeginExecutionAction>
|
|
where
|
|
Owner: ParserScriptBeginExecutionOwner<C>,
|
|
{
|
|
self.deferred_scripts.update_current_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let ParserClassicScriptRunnerStep::Ready(_) = script.runner_step()? else {
|
|
return None;
|
|
};
|
|
let executable_script = script
|
|
.runner_script()
|
|
.cloned()
|
|
.and_then(ParserExecutableClassicScript::from_prepared_script)?;
|
|
let execution = script.begin_runner_ready_execution(script_handle)?;
|
|
owner.parser_script_begin_execution_action(
|
|
ParserPendingClassicScriptBeginExecutionAction::new(execution, executable_script),
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_disposed_ready_action_with_owner<Owner>(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::DisposedReadyAction>
|
|
where
|
|
Owner: ParserScriptDisposeReadyOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_advance(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let execution = script.dispose_runner_ready_without_execution(script_handle)?;
|
|
let action = ParserPendingClassicScriptDisposedReadyAction::new(execution);
|
|
owner.parser_script_disposed_ready_action(action)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_deferred_disposed_ready_action_with_owner<Owner>(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::DisposedReadyAction>
|
|
where
|
|
Owner: ParserScriptDisposeReadyOwner<C>,
|
|
{
|
|
self.deferred_scripts.update_current_and_advance(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let execution = script.dispose_runner_ready_without_execution(script_handle)?;
|
|
owner.parser_script_disposed_ready_action(
|
|
ParserPendingClassicScriptDisposedReadyAction::new(execution),
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_finished_execution_action_with_owner<Owner>(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::FinishedExecutionAction>
|
|
where
|
|
Owner: ParserScriptFinishExecutionOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_advance(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let execution = script.finish_runner_execution(script_handle)?;
|
|
let action = ParserPendingClassicScriptFinishedExecutionAction::new(execution);
|
|
owner.parser_script_finished_execution_action(action)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_deferred_finished_execution_action_with_owner<Owner>(
|
|
&mut self,
|
|
script_handle: DomHandle,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::FinishedExecutionAction>
|
|
where
|
|
Owner: ParserScriptFinishExecutionOwner<C>,
|
|
{
|
|
self.deferred_scripts.update_current_and_advance(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let execution = script.finish_runner_execution(script_handle)?;
|
|
owner.parser_script_finished_execution_action(
|
|
ParserPendingClassicScriptFinishedExecutionAction::new(execution),
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_source_failure_action_with_owner<Owner>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceFailureAction>
|
|
where
|
|
Owner: ParserScriptSourceFailureOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_advance(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let failure = script.finish_runner_source_failure()?;
|
|
let action = ParserPendingClassicScriptSourceFailureAction::new(failure);
|
|
owner.parser_script_source_failure_action(script.context(), action)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_deferred_source_failure_action_with_owner<Owner>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceFailureAction>
|
|
where
|
|
Owner: ParserScriptSourceFailureOwner<C>,
|
|
{
|
|
self.deferred_scripts.update_current_and_advance(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let failure = script.finish_runner_source_failure()?;
|
|
owner.parser_script_source_failure_action(
|
|
script.context(),
|
|
ParserPendingClassicScriptSourceFailureAction::new(failure),
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_external_load_action_with_owner<Owner>(
|
|
&mut self,
|
|
load_id: u64,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceLoadAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadStartState,
|
|
Owner: ParserScriptBeginSourceLoadOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let script_url = script.runner_external_pending_script_url()?;
|
|
let metadata = script.runner_metadata()?;
|
|
let candidate =
|
|
ParserPendingClassicScriptSourceLoadCandidate::new(metadata, script_url);
|
|
if !owner.parser_script_source_load_candidate_matches(candidate) {
|
|
return None;
|
|
}
|
|
let (source_identity, input) = script.begin_runner_external_load(load_id)?;
|
|
let request = ParserPendingClassicScriptSourceLoadRequest::new(source_identity, input);
|
|
let action = ParserPendingClassicScriptSourceLoadAction::new(request);
|
|
let owner_action = owner.parser_script_source_load_action(action)?;
|
|
if let Some(state) = owner.parser_script_source_load_state() {
|
|
script
|
|
.context_mut()
|
|
.install_parser_classic_source_load_state(state);
|
|
}
|
|
Some(owner_action)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn fail_current_parser_blocking_external_pending_before_load(
|
|
&mut self,
|
|
metadata: crate::parser_script::payload::ParserClassicScriptMetadata,
|
|
script_url: &url::Url,
|
|
error: String,
|
|
) -> bool {
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if script.runner_metadata() != Some(metadata)
|
|
|| script.runner_external_pending_script_url() != Some(script_url)
|
|
{
|
|
return Some(false);
|
|
}
|
|
Some(script.fail_runner_external_pending_before_load(error))
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
fn update_current_parser_blocking_and_keep<R>(
|
|
&mut self,
|
|
update: impl FnOnce(&mut ParserClassicScriptRunnerItem<C>) -> Option<R>,
|
|
) -> Option<R> {
|
|
self.parser_blocking_scripts.update_current_and_keep(update)
|
|
}
|
|
|
|
pub(crate) fn take_current_parser_blocking_source_result_action_with_owner<Owner>(
|
|
&mut self,
|
|
source_result: ParserClassicScriptSourceResult,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceResultAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadState,
|
|
Owner: ParserScriptSourceResultOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let (notification, network_result) =
|
|
script.notify_runner_source_result_with_network_result(source_result)?;
|
|
script
|
|
.context_mut()
|
|
.clear_parser_classic_source_load_state();
|
|
let network_record_urls = script
|
|
.runner_script()
|
|
.map(ParserClassicScriptNetworkRecordUrls::from_prepared_script);
|
|
let action = ParserPendingClassicScriptSourceResultAction::new(
|
|
notification,
|
|
network_result.as_ref(),
|
|
network_record_urls,
|
|
);
|
|
owner.parser_script_source_result_action(action)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn take_deferred_source_result_action_with_owner<Owner>(
|
|
&mut self,
|
|
source_result: ParserClassicScriptSourceResult,
|
|
owner: &mut Owner,
|
|
) -> Option<Owner::SourceResultAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadState,
|
|
Owner: ParserScriptSourceResultOwner<C>,
|
|
{
|
|
let source_result_to_match = source_result.clone();
|
|
self.deferred_scripts.update_first_matching_and_keep(
|
|
|script| script.runner_external_load_matches_source_result(&source_result_to_match),
|
|
|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return None;
|
|
}
|
|
let (notification, network_result) =
|
|
script.notify_runner_source_result_with_network_result(source_result)?;
|
|
script
|
|
.context_mut()
|
|
.clear_parser_classic_source_load_state();
|
|
let network_record_urls = script
|
|
.runner_script()
|
|
.map(ParserClassicScriptNetworkRecordUrls::from_prepared_script);
|
|
owner.parser_script_source_result_action(
|
|
ParserPendingClassicScriptSourceResultAction::new(
|
|
notification,
|
|
network_result.as_ref(),
|
|
network_record_urls,
|
|
),
|
|
)
|
|
},
|
|
)
|
|
}
|
|
|
|
pub(crate) fn apply_current_parser_blocking_source_result_if_ready_with_owner<Owner>(
|
|
&mut self,
|
|
owner: &mut Owner,
|
|
) -> ParserClassicScriptSourceResultApplication<Owner::SourceResultAction>
|
|
where
|
|
C: ParserClassicScriptSourceLoadState + ParserClassicScriptSourceResultState,
|
|
Owner: ParserScriptSourceResultOwner<C>,
|
|
{
|
|
self.update_current_parser_blocking_and_keep(|script| {
|
|
if !owner.is_current_parser_script_owner(script.context()) {
|
|
return Some(ParserClassicScriptSourceResultApplication::NoSourceLoad);
|
|
}
|
|
let source_identity = script
|
|
.runner_external_load_identity()
|
|
.map(|(source_identity, _)| source_identity);
|
|
let network_record_urls = script
|
|
.runner_script()
|
|
.map(ParserClassicScriptNetworkRecordUrls::from_prepared_script);
|
|
let source_load_outcome =
|
|
match script.context().parser_classic_source_load_outcome_state() {
|
|
ParserClassicScriptSourceLoadOutcomeState::NoSourceLoad => {
|
|
return Some(ParserClassicScriptSourceResultApplication::NoSourceLoad);
|
|
}
|
|
ParserClassicScriptSourceLoadOutcomeState::Waiting => {
|
|
return Some(ParserClassicScriptSourceResultApplication::Waiting);
|
|
}
|
|
ParserClassicScriptSourceLoadOutcomeState::Ready(outcome) => outcome,
|
|
};
|
|
let Some(source_identity) = source_identity else {
|
|
return Some(ParserClassicScriptSourceResultApplication::Waiting);
|
|
};
|
|
let source_result = source_identity.into_source_result(source_load_outcome);
|
|
let (notification, network_result) =
|
|
script.notify_runner_source_result_with_network_result(source_result)?;
|
|
script
|
|
.context_mut()
|
|
.clear_parser_classic_source_load_state();
|
|
let action = ParserPendingClassicScriptSourceResultAction::new(
|
|
notification,
|
|
network_result.as_ref(),
|
|
network_record_urls,
|
|
);
|
|
Some(ParserClassicScriptSourceResultApplication::Applied(
|
|
owner.parser_script_source_result_action(action),
|
|
))
|
|
})
|
|
.unwrap_or(ParserClassicScriptSourceResultApplication::NoSourceLoad)
|
|
}
|
|
|
|
fn update_current_parser_blocking_and_advance<R>(
|
|
&mut self,
|
|
update: impl FnOnce(&mut ParserClassicScriptRunnerItem<C>) -> Option<R>,
|
|
) -> Option<R> {
|
|
self.parser_blocking_scripts
|
|
.update_current_and_advance(update)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{
|
|
ParserClassicScriptMetadata, ParserClassicScriptRunner, ParserClassicScriptRunnerItem,
|
|
ParserClassicScriptRunnerSlot, ParserPendingClassicScriptEntry,
|
|
ParserPreparedClassicScript,
|
|
};
|
|
use crate::{
|
|
document_runtime::DomHandle,
|
|
dom::NodeId,
|
|
parser_script::action::{
|
|
ParserPendingClassicScriptReady, ParserPendingClassicScriptReadyAction,
|
|
ParserPendingClassicScriptSourceFailureAction,
|
|
},
|
|
parser_script::owner::{
|
|
ParserScriptExecutionBlocker, ParserScriptExecutionGate, ParserScriptOwner,
|
|
ParserScriptReadyOwner, ParserScriptSourceFailureOwner,
|
|
},
|
|
parser_script::projection::ParserClassicScriptNextActionWithBlockedScript,
|
|
planning::{PreparedScript, ScriptFetchMetadata, ScriptSource},
|
|
types::{ScriptKind, ScriptMode, ScriptSourceKind},
|
|
};
|
|
use url::Url;
|
|
|
|
#[derive(Default)]
|
|
struct CaptureLifecycleEventOwner {
|
|
queued_events: usize,
|
|
}
|
|
|
|
impl ParserScriptOwner<()> for CaptureLifecycleEventOwner {}
|
|
|
|
impl ParserScriptSourceFailureOwner<()> for CaptureLifecycleEventOwner {
|
|
type SourceFailureAction = super::ParserClassicScriptSourceFailure;
|
|
|
|
fn parser_script_source_failure_action(
|
|
&mut self,
|
|
_context: &(),
|
|
action: ParserPendingClassicScriptSourceFailureAction,
|
|
) -> Option<Self::SourceFailureAction> {
|
|
self.queued_events += 1;
|
|
Some(action.into_failure())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn parser_script_owner_captures_script_element_event() {
|
|
let script_handle = DomHandle::new(7);
|
|
let script_url =
|
|
Url::parse("https://parser-script-runner.test/fail.js").expect("script url");
|
|
let mut runner = ParserClassicScriptRunner::new_parser_blocking(vec![
|
|
ParserClassicScriptRunnerItem::from_slot_for_test(
|
|
ParserClassicScriptRunnerSlot::from_pending_entry(
|
|
ParserPendingClassicScriptEntry::external_failed(
|
|
ParserClassicScriptMetadata::new(script_handle, 11),
|
|
script_url.clone(),
|
|
"network failure",
|
|
),
|
|
),
|
|
(),
|
|
),
|
|
]);
|
|
let mut owner = CaptureLifecycleEventOwner::default();
|
|
|
|
let failure = runner
|
|
.take_current_parser_blocking_source_failure_action_with_owner(&mut owner)
|
|
.expect("source failure should finish");
|
|
|
|
assert_eq!(failure.metadata.script_handle(), script_handle);
|
|
assert_eq!(failure.script_url, script_url);
|
|
assert_eq!(owner.queued_events, 1);
|
|
assert!(runner.is_complete());
|
|
}
|
|
|
|
struct BlockReadyProjectionOwner;
|
|
|
|
impl ParserScriptOwner<()> for BlockReadyProjectionOwner {
|
|
fn parser_script_execution_gate(&mut self, _state: ()) -> ParserScriptExecutionGate {
|
|
ParserScriptExecutionGate::Blocked(ParserScriptExecutionBlocker::Stylesheet)
|
|
}
|
|
}
|
|
|
|
impl ParserScriptReadyOwner<()> for BlockReadyProjectionOwner {
|
|
type ReadyAction = ParserPendingClassicScriptReady;
|
|
|
|
fn parser_script_ready_action(
|
|
&mut self,
|
|
_context: &(),
|
|
ready: ParserPendingClassicScriptReadyAction<'_>,
|
|
) -> Option<Self::ReadyAction> {
|
|
Some(ready.ready().clone())
|
|
}
|
|
}
|
|
|
|
impl ParserScriptSourceFailureOwner<()> for BlockReadyProjectionOwner {
|
|
type SourceFailureAction = ParserPendingClassicScriptReady;
|
|
|
|
fn parser_script_source_failure_action(
|
|
&mut self,
|
|
_context: &(),
|
|
_action: ParserPendingClassicScriptSourceFailureAction,
|
|
) -> Option<Self::SourceFailureAction> {
|
|
None
|
|
}
|
|
}
|
|
|
|
struct ReadyProjectionOwner;
|
|
|
|
impl ParserScriptOwner<()> for ReadyProjectionOwner {}
|
|
|
|
impl ParserScriptReadyOwner<()> for ReadyProjectionOwner {
|
|
type ReadyAction = ParserPendingClassicScriptReady;
|
|
|
|
fn parser_script_ready_action(
|
|
&mut self,
|
|
_context: &(),
|
|
ready: ParserPendingClassicScriptReadyAction<'_>,
|
|
) -> Option<Self::ReadyAction> {
|
|
Some(ready.ready().clone())
|
|
}
|
|
}
|
|
|
|
impl ParserScriptSourceFailureOwner<()> for ReadyProjectionOwner {
|
|
type SourceFailureAction = ParserPendingClassicScriptReady;
|
|
|
|
fn parser_script_source_failure_action(
|
|
&mut self,
|
|
_context: &(),
|
|
_action: ParserPendingClassicScriptSourceFailureAction,
|
|
) -> Option<Self::SourceFailureAction> {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn loaded_prepared_script(
|
|
script_handle: DomHandle,
|
|
start_line: u64,
|
|
) -> ParserPreparedClassicScript {
|
|
let script_url =
|
|
Url::parse("https://parser-script-runner.test/ready.js").expect("script url");
|
|
let script = PreparedScript {
|
|
position: 0,
|
|
node_id: NodeId::new(script_handle.index()),
|
|
kind: ScriptKind::Classic,
|
|
mode: ScriptMode::Normal,
|
|
source_kind: ScriptSourceKind::External,
|
|
fetch_metadata: ScriptFetchMetadata::default(),
|
|
source: ScriptSource::Loaded("window.__ready = true".to_owned()),
|
|
url: script_url.clone(),
|
|
base_url: script_url.clone(),
|
|
initiator_url: script_url,
|
|
host_script_handle: None,
|
|
};
|
|
ParserPreparedClassicScript::new(
|
|
ParserClassicScriptMetadata::new(script_handle, start_line),
|
|
script,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn parser_script_owner_gate_blocks_ready_projection() {
|
|
let script_handle = DomHandle::new(9);
|
|
let mut runner = ParserClassicScriptRunner::new_parser_blocking(vec![
|
|
ParserClassicScriptRunnerItem::from_slot_for_test(
|
|
ParserClassicScriptRunnerSlot::from_pending_entry(
|
|
ParserPendingClassicScriptEntry::external_ready(loaded_prepared_script(
|
|
script_handle,
|
|
17,
|
|
)),
|
|
),
|
|
(),
|
|
),
|
|
]);
|
|
let mut blocked_owner = BlockReadyProjectionOwner;
|
|
|
|
let blocked = runner.take_current_parser_blocking_next_action_or_blocked_script_with_owner(
|
|
&mut blocked_owner,
|
|
);
|
|
match blocked {
|
|
ParserClassicScriptNextActionWithBlockedScript::Blocked(blocked) => {
|
|
assert_eq!(blocked.blocker(), ParserScriptExecutionBlocker::Stylesheet);
|
|
assert_eq!(
|
|
blocked
|
|
.script()
|
|
.runner_metadata()
|
|
.map(|metadata| metadata.script_handle()),
|
|
Some(script_handle)
|
|
);
|
|
}
|
|
projection => panic!("expected stylesheet-blocked projection, got {projection:?}"),
|
|
}
|
|
|
|
let mut current_owner = ReadyProjectionOwner;
|
|
let ready = match runner
|
|
.take_current_parser_blocking_next_action_or_blocked_script_with_owner(
|
|
&mut current_owner,
|
|
) {
|
|
ParserClassicScriptNextActionWithBlockedScript::Action(ready) => ready,
|
|
projection => panic!("expected ready projection, got {projection:?}"),
|
|
};
|
|
assert_eq!(ready.script().script_handle(), script_handle);
|
|
assert_eq!(ready.script().start_line(), 17);
|
|
}
|
|
|
|
#[test]
|
|
fn parser_script_runner_discards_current_only_when_handle_still_matches() {
|
|
let first_script_handle = DomHandle::new(11);
|
|
let second_script_handle = DomHandle::new(12);
|
|
let mut runner = ParserClassicScriptRunner::new_parser_blocking(vec![
|
|
ParserClassicScriptRunnerItem::from_slot_for_test(
|
|
ParserClassicScriptRunnerSlot::from_pending_entry(
|
|
ParserPendingClassicScriptEntry::external_ready(loaded_prepared_script(
|
|
first_script_handle,
|
|
17,
|
|
)),
|
|
),
|
|
(),
|
|
),
|
|
ParserClassicScriptRunnerItem::from_slot_for_test(
|
|
ParserClassicScriptRunnerSlot::from_pending_entry(
|
|
ParserPendingClassicScriptEntry::external_ready(loaded_prepared_script(
|
|
second_script_handle,
|
|
23,
|
|
)),
|
|
),
|
|
(),
|
|
),
|
|
]);
|
|
|
|
assert_eq!(
|
|
runner.current_parser_blocking_script_handle(),
|
|
Some(first_script_handle)
|
|
);
|
|
assert!(
|
|
runner.discard_current_parser_blocking_script_if_handle(first_script_handle),
|
|
"first pending script should be discarded when it is still current"
|
|
);
|
|
assert_eq!(
|
|
runner.current_parser_blocking_script_handle(),
|
|
Some(second_script_handle)
|
|
);
|
|
assert!(
|
|
!runner.discard_current_parser_blocking_script_if_handle(first_script_handle),
|
|
"stale handle must not discard the next pending script"
|
|
);
|
|
assert_eq!(
|
|
runner.current_parser_blocking_script_handle(),
|
|
Some(second_script_handle)
|
|
);
|
|
}
|
|
}
|