mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
Resolve JSON and CSS evaluation data by exact module identity in the compiling realm, with weak context entries owned by compiled records. Remove the main-Document source lookup that broke child imports. Cover static and deferred dynamic child imports, realm-local caches, parse errors, identity collisions and record disposal. Mark both JSON/CSS cross-origin WPT cases passing after CLI/CDP verification.
6797 lines
276 KiB
Rust
6797 lines
276 KiB
Rust
use std::pin::pin;
|
|
|
|
use anyhow::Result;
|
|
use moli_webapi_declare::WebApiObject;
|
|
use url::Url;
|
|
|
|
use super::ScriptVm;
|
|
use crate::context_bootstrap::{
|
|
ORIGINAL_WEBASSEMBLY_COMPILE_ERROR_CONSTRUCTOR_SLOT,
|
|
ORIGINAL_WEBASSEMBLY_LINK_ERROR_CONSTRUCTOR_SLOT,
|
|
};
|
|
use crate::document_script_scheduler::{
|
|
DocumentOwnedScriptReadyAction, ParserDeferredClassicSourceLoadApplyResult,
|
|
ParserDeferredClassicSourceLoadCompletion, ParserDeferredScriptStartAction,
|
|
ParserModuleEvaluationReactionUpdate, ParserPendingScriptId, ParserPendingScriptKey,
|
|
};
|
|
use crate::dom::NodeId;
|
|
use crate::frame_owner_model::{
|
|
ChildDynamicModuleCompletedFetchRestoreAction,
|
|
ChildDynamicModuleOwnerFetchCompletionSettlementAction,
|
|
ChildDynamicModuleOwnerFetchWithoutNetworkSettlementAction, DocumentId,
|
|
FrameDocumentDynamicImportEvaluationReadyAction,
|
|
FrameDocumentDynamicImportEvaluationReadyResult,
|
|
FrameDocumentDynamicImportJoinedFetchRestoreResult,
|
|
FrameDocumentDynamicImportMissingJoinedTerminalClient,
|
|
FrameDocumentDynamicImportMissingJoinedTerminalFetch, FrameDocumentDynamicImportOwnerAction,
|
|
FrameDocumentDynamicImportOwnerActionDiagnostic, FrameDocumentDynamicImportOwnerActionHooks,
|
|
FrameDocumentDynamicImportOwnerActionRunner,
|
|
FrameDocumentDynamicImportOwnerFetchSettlementResult,
|
|
FrameDocumentDynamicImportOwnerTerminalRestoreAction, FrameDocumentDynamicImportRejectAction,
|
|
FrameDocumentDynamicImportRejectResult, FrameDocumentDynamicImportSourceReadyAction,
|
|
FrameDocumentDynamicImportSourceReadyResult, FrameDocumentDynamicImportTerminalClientAction,
|
|
FrameDocumentDynamicImportTerminalClientFinishResult,
|
|
FrameDocumentDynamicImportTerminalOutcome, FrameDocumentDynamicImportTerminalPreparedAction,
|
|
FrameDocumentDynamicImportWaitingFetchScheduleAction,
|
|
FrameDocumentDynamicImportWaitingFetchScheduleResult, FrameDocumentModuleFetchClientStart,
|
|
FrameDocumentModuleTerminalQueueFollowup, FrameDocumentTaskOwner, FrameRealmId,
|
|
FrameSchedulerLaneId, LocalWindowId, MainDocumentScriptLoadDelayKind,
|
|
MainDocumentScriptLoadDelayLease,
|
|
};
|
|
use crate::module_runtime::{
|
|
DynamicModuleEvaluationTarget, DynamicModuleFetchContinuation, DynamicModuleFetchFailure,
|
|
DynamicModuleFetchFinish, DynamicModuleJoinedFetch, DynamicModuleScheduledFetch,
|
|
ModuleAttributesKey, ModuleEntryId, ModuleGraphFetchedSource, ModuleGraphHandle,
|
|
ModuleIdentityHash, ModuleImportPhase, ModuleKind, ModuleLoadError, ModuleLoadStage,
|
|
ModuleMapEntryState, ModuleMapKey, ModuleMapTerminalNotification, ModuleRecordEntry,
|
|
ModuleRequestRecord, ModuleScriptGraphFetchContinuation, ModuleSource, NativeDocumentModulator,
|
|
NativeDynamicImportSingleModuleClient, NativeDynamicModuleImportReady, NativeModuleGraphJob,
|
|
NativeModuleGraphJobAdvance, NativeModuleMapSingleModuleClient, NativeModuleOwnerEvent,
|
|
NativeModuleScriptSingleModuleClient, NativeModuleSingleFetchRequest,
|
|
NativeModulepreloadFetchStart, PendingDynamicModuleImport, ResolverScopeGuard,
|
|
WasmDependencyModuleMessages, WasmImportRecord, WasmModuleRecord,
|
|
ensure_wasm_dependency_module_namespace_ready, evaluate_wasm_synthetic_module,
|
|
module_identity_hash_from_v8_module, preserve_current_v8_module_exception,
|
|
resolve_static_module_callback, resolve_static_source_callback, throw_wasm_link_error,
|
|
wasm_dependency_export_value,
|
|
};
|
|
use crate::module_script_continuation::{
|
|
MainParserDeferredClassicSourceLoadCompletion, MainParserDocumentOwner,
|
|
ModuleMapTerminalFanout, ModuleScriptCompletionOwner, ModuleScriptContinuation,
|
|
ModuleScriptContinuationGraphAdvance, ModuleScriptEvaluationContinuation,
|
|
ModuleScriptEvaluationUpdate, ModuleScriptGraphFetchResume, ModuleScriptGraphResumeResult,
|
|
NativeDynamicModuleTerminalFanout, NativeModuleOwnerActions, ParserModuleScriptFailure,
|
|
parser_module_evaluation_continuation_into_ready_action,
|
|
};
|
|
use crate::page_task_queue::{
|
|
PageModuleReactionApplication, PageModuleReactionFollowup, RendererPageModuleReactionEvent,
|
|
};
|
|
use crate::planning::PreparedScript;
|
|
use crate::types::{
|
|
ChildDynamicImportFetchCompletion, ScriptErrorConstructorKind, ScriptErrorValue,
|
|
SubresourceRequestInitiatorType, SubresourceResourceType,
|
|
};
|
|
#[cfg(test)]
|
|
use crate::types::{
|
|
ModuleGraphFetchCompletion, ModuleGraphFetchOrdering, ModuleGraphFetchRequester,
|
|
};
|
|
use crate::util::{context_host_ptr_from_global_bridge, get_private_value, v8_string, v8str};
|
|
use crate::wasm_module_support::{
|
|
prepare_wasm_module_record, v8_exception_message_or, wasm_evaluation_import_modules,
|
|
};
|
|
|
|
mod child_dynamic_import;
|
|
mod child_parser_module;
|
|
mod child_ready_document_script;
|
|
mod dynamic_import_selected_task_body;
|
|
mod load_error;
|
|
mod main_selected_task;
|
|
pub(super) use load_error::retained_module_exception;
|
|
use load_error::{module_load_error_value, retain_module_exception};
|
|
pub(crate) use main_selected_task::{
|
|
MainDynamicImportGraphFetchBodySettlement, MainNativeModuleSelectedTaskApplication,
|
|
MainNativeModuleSelectedTaskBodyActivity,
|
|
};
|
|
|
|
pub(crate) enum NativeDynamicModuleSourceImportResolution {
|
|
Resolved,
|
|
Rejected,
|
|
}
|
|
|
|
enum DocumentModuleReactionUpdate {
|
|
ParserOwned(ParserModuleEvaluationReactionUpdate),
|
|
RuntimeOwned(ModuleScriptEvaluationUpdate),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
struct NativeDynamicModuleTerminalFanoutOutcome {
|
|
scheduled_dynamic_import_fetches: usize,
|
|
ready_imports_handled: usize,
|
|
failed_fetches_rejected: usize,
|
|
graph_advance_failures_handled: usize,
|
|
source_imports_resolved: usize,
|
|
source_imports_rejected: usize,
|
|
evaluation_imports_resolved: usize,
|
|
evaluation_imports_pending: usize,
|
|
evaluation_imports_rejected: usize,
|
|
dynamic_import_jobs_resumed: usize,
|
|
dynamic_import_waits_retained: usize,
|
|
child_followup: FrameDocumentModuleTerminalQueueFollowup,
|
|
}
|
|
|
|
impl NativeDynamicModuleTerminalFanoutOutcome {
|
|
fn child_followup(self) -> FrameDocumentModuleTerminalQueueFollowup {
|
|
self.child_followup
|
|
}
|
|
|
|
fn record_scheduled_dynamic_import_fetches(&mut self, count: usize) {
|
|
self.scheduled_dynamic_import_fetches += count;
|
|
}
|
|
|
|
fn record_ready_import_outcome(&mut self, outcome: FrameDocumentDynamicImportTerminalOutcome) {
|
|
self.ready_imports_handled += 1;
|
|
self.record_dynamic_import_terminal_outcome(outcome);
|
|
}
|
|
|
|
fn record_failed_fetch_rejection_outcome(
|
|
&mut self,
|
|
outcome: FrameDocumentDynamicImportTerminalOutcome,
|
|
) {
|
|
self.failed_fetches_rejected += outcome.dynamic_import_was_rejected() as usize;
|
|
self.record_dynamic_import_terminal_outcome(outcome);
|
|
}
|
|
|
|
fn record_graph_advance_failure_outcome(
|
|
&mut self,
|
|
outcome: FrameDocumentDynamicImportTerminalOutcome,
|
|
) {
|
|
self.graph_advance_failures_handled += outcome.dynamic_import_was_rejected() as usize;
|
|
self.record_dynamic_import_terminal_outcome(outcome);
|
|
}
|
|
|
|
fn record_dynamic_import_terminal_outcome(
|
|
&mut self,
|
|
outcome: FrameDocumentDynamicImportTerminalOutcome,
|
|
) {
|
|
self.child_followup
|
|
.merge(outcome.owner_action_queue_followup());
|
|
self.source_imports_resolved += outcome.source_import_was_resolved() as usize;
|
|
self.source_imports_rejected += outcome.source_import_was_rejected() as usize;
|
|
self.evaluation_imports_resolved += outcome.evaluation_import_was_resolved() as usize;
|
|
self.evaluation_imports_pending += outcome.evaluation_import_was_pending() as usize;
|
|
self.evaluation_imports_rejected += outcome.evaluation_import_was_rejected() as usize;
|
|
}
|
|
|
|
fn record_graph_advance_failure_followup(
|
|
&mut self,
|
|
followup: FrameDocumentModuleTerminalQueueFollowup,
|
|
) {
|
|
self.graph_advance_failures_handled += 1;
|
|
self.child_followup.merge(followup);
|
|
}
|
|
|
|
fn record_dynamic_import_job_resumed(&mut self) {
|
|
self.dynamic_import_jobs_resumed += 1;
|
|
}
|
|
|
|
fn record_dynamic_import_wait_retained(&mut self) {
|
|
self.dynamic_import_waits_retained += 1;
|
|
}
|
|
|
|
fn record_restored_after_unexpected_complete(&mut self) {
|
|
self.child_followup
|
|
.merge(FrameDocumentModuleTerminalQueueFollowup::terminal_warning_recorded());
|
|
}
|
|
|
|
fn merge(&mut self, other: Self) {
|
|
self.scheduled_dynamic_import_fetches += other.scheduled_dynamic_import_fetches;
|
|
self.ready_imports_handled += other.ready_imports_handled;
|
|
self.failed_fetches_rejected += other.failed_fetches_rejected;
|
|
self.graph_advance_failures_handled += other.graph_advance_failures_handled;
|
|
self.source_imports_resolved += other.source_imports_resolved;
|
|
self.source_imports_rejected += other.source_imports_rejected;
|
|
self.evaluation_imports_resolved += other.evaluation_imports_resolved;
|
|
self.evaluation_imports_pending += other.evaluation_imports_pending;
|
|
self.evaluation_imports_rejected += other.evaluation_imports_rejected;
|
|
self.dynamic_import_jobs_resumed += other.dynamic_import_jobs_resumed;
|
|
self.dynamic_import_waits_retained += other.dynamic_import_waits_retained;
|
|
self.child_followup.merge(other.child_followup);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn scheduled_dynamic_import_fetch_count(self) -> usize {
|
|
self.scheduled_dynamic_import_fetches
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn ready_import_count(self) -> usize {
|
|
self.ready_imports_handled
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn failed_fetch_rejected_count(self) -> usize {
|
|
self.failed_fetches_rejected
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn graph_advance_failure_handled_count(self) -> usize {
|
|
self.graph_advance_failures_handled
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn source_import_rejected_count(self) -> usize {
|
|
self.source_imports_rejected
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn evaluation_import_rejected_count(self) -> usize {
|
|
self.evaluation_imports_rejected
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn dynamic_import_job_resumed_count(self) -> usize {
|
|
self.dynamic_import_jobs_resumed
|
|
}
|
|
}
|
|
|
|
/// Realm-settlement strategy used while consuming main native-module work.
|
|
///
|
|
/// Main-document runtime tasks and selected Networking graph terminals use the
|
|
/// body-only strategy in `main_selected_task`, leaving the ordinary task end
|
|
/// to the central dispatcher. Compatibility callers that still own a complete
|
|
/// local task use the checkpointing strategy below. This strategy is local to
|
|
/// an executing carrier; it is never stored in a queued task or consulted by
|
|
/// the scheduler.
|
|
trait ScriptVmMainNativeModuleTaskBody {
|
|
fn note_page_realm_body_attempted(&mut self) {}
|
|
|
|
fn resolve_ready_source_import(
|
|
&mut self,
|
|
vm: &mut ScriptVm,
|
|
request: PendingDynamicModuleImport,
|
|
root_entry: ModuleEntryId,
|
|
) -> std::result::Result<NativeDynamicModuleSourceImportResolution, ModuleLoadError>;
|
|
|
|
fn resolve_completed_evaluation_import(
|
|
&mut self,
|
|
vm: &mut ScriptVm,
|
|
request: PendingDynamicModuleImport,
|
|
target: &DynamicModuleEvaluationTarget,
|
|
) -> std::result::Result<(), ModuleLoadError>;
|
|
|
|
fn reject_dynamic_import(
|
|
&mut self,
|
|
vm: &mut ScriptVm,
|
|
request: PendingDynamicModuleImport,
|
|
error: &ModuleLoadError,
|
|
) -> std::result::Result<(), ModuleLoadError>;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
struct ScriptVmCheckpointingMainNativeModuleTaskBody;
|
|
|
|
#[cfg(test)]
|
|
impl ScriptVmMainNativeModuleTaskBody for ScriptVmCheckpointingMainNativeModuleTaskBody {
|
|
fn resolve_ready_source_import(
|
|
&mut self,
|
|
vm: &mut ScriptVm,
|
|
request: PendingDynamicModuleImport,
|
|
root_entry: ModuleEntryId,
|
|
) -> std::result::Result<NativeDynamicModuleSourceImportResolution, ModuleLoadError> {
|
|
vm.resolve_native_dynamic_module_source_import(request, root_entry)
|
|
}
|
|
|
|
fn resolve_completed_evaluation_import(
|
|
&mut self,
|
|
vm: &mut ScriptVm,
|
|
request: PendingDynamicModuleImport,
|
|
target: &DynamicModuleEvaluationTarget,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
vm.resolve_native_dynamic_module_import(request, target)
|
|
}
|
|
|
|
fn reject_dynamic_import(
|
|
&mut self,
|
|
vm: &mut ScriptVm,
|
|
request: PendingDynamicModuleImport,
|
|
error: &ModuleLoadError,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
vm.reject_native_dynamic_module_import_with_error(request, error)
|
|
}
|
|
}
|
|
|
|
struct ScriptVmMainDynamicImportOwnerActionHooks<'vm, 'body, Body> {
|
|
vm: &'vm mut ScriptVm,
|
|
body: &'body mut Body,
|
|
}
|
|
|
|
impl<'vm, 'body, Body> ScriptVmMainDynamicImportOwnerActionHooks<'vm, 'body, Body> {
|
|
fn new(vm: &'vm mut ScriptVm, body: &'body mut Body) -> Self {
|
|
Self { vm, body }
|
|
}
|
|
|
|
fn unsupported<T>(&self, action: &'static str) -> std::result::Result<T, String> {
|
|
Err(format!(
|
|
"main dynamic import owner-action hook cannot run child-only {action}"
|
|
))
|
|
}
|
|
}
|
|
|
|
impl<Body> FrameDocumentDynamicImportOwnerActionHooks
|
|
for ScriptVmMainDynamicImportOwnerActionHooks<'_, '_, Body>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
fn finish_terminal_client(
|
|
&mut self,
|
|
_action: FrameDocumentDynamicImportTerminalClientAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportTerminalClientFinishResult, String> {
|
|
self.unsupported("terminal client action")
|
|
}
|
|
|
|
fn queue_owner_action_followups(
|
|
&mut self,
|
|
_actions: Vec<FrameDocumentDynamicImportTerminalPreparedAction>,
|
|
) -> std::result::Result<FrameDocumentModuleTerminalQueueFollowup, String> {
|
|
self.unsupported("owner action follow-ups")
|
|
}
|
|
|
|
fn record_missing_joined_terminal_client(
|
|
&mut self,
|
|
_missing: FrameDocumentDynamicImportMissingJoinedTerminalClient,
|
|
) -> std::result::Result<(), String> {
|
|
self.unsupported("missing joined terminal client")
|
|
}
|
|
|
|
fn settle_owner_module_fetch_completion(
|
|
&mut self,
|
|
_action: ChildDynamicModuleOwnerFetchCompletionSettlementAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportOwnerFetchSettlementResult, String> {
|
|
self.unsupported("owner module fetch completion")
|
|
}
|
|
|
|
fn restore_completed_owner_module_fetch_as_joined_terminal_client(
|
|
&mut self,
|
|
_restore: ChildDynamicModuleCompletedFetchRestoreAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportJoinedFetchRestoreResult, String> {
|
|
self.unsupported("completed owner module fetch restore")
|
|
}
|
|
|
|
fn finish_owner_module_fetch_without_network(
|
|
&mut self,
|
|
_action: ChildDynamicModuleOwnerFetchWithoutNetworkSettlementAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportOwnerFetchSettlementResult, String> {
|
|
self.unsupported("owner module fetch without network")
|
|
}
|
|
|
|
fn restore_scheduled_fetch_as_joined_terminal_client(
|
|
&mut self,
|
|
_action: FrameDocumentDynamicImportOwnerTerminalRestoreAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportJoinedFetchRestoreResult, String> {
|
|
self.unsupported("scheduled fetch restore")
|
|
}
|
|
|
|
fn schedule_waiting_fetch(
|
|
&mut self,
|
|
_action: FrameDocumentDynamicImportWaitingFetchScheduleAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportWaitingFetchScheduleResult, String> {
|
|
self.unsupported("waiting fetch schedule")
|
|
}
|
|
|
|
fn record_missing_joined_terminal_fetch(
|
|
&mut self,
|
|
_missing: FrameDocumentDynamicImportMissingJoinedTerminalFetch,
|
|
) -> std::result::Result<(), String> {
|
|
self.unsupported("missing joined terminal fetch")
|
|
}
|
|
|
|
fn resolve_ready_source_import(
|
|
&mut self,
|
|
action: FrameDocumentDynamicImportSourceReadyAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportSourceReadyResult, String> {
|
|
let (request, root_entry) = action.into_parts();
|
|
let document_owner = request.owner();
|
|
if !self
|
|
.vm
|
|
.dynamic_module_import_owner_is_current(document_owner)
|
|
{
|
|
self.vm.record_runtime_warning(format_args!(
|
|
"dropped stale dynamic import source resolution: owner={document_owner:?}"
|
|
));
|
|
return Ok(FrameDocumentDynamicImportSourceReadyResult::DroppedStaleOwner);
|
|
}
|
|
self.body.note_page_realm_body_attempted();
|
|
match self
|
|
.body
|
|
.resolve_ready_source_import(self.vm, request, root_entry)
|
|
.map_err(|error| error.message().to_owned())?
|
|
{
|
|
NativeDynamicModuleSourceImportResolution::Resolved => {
|
|
Ok(FrameDocumentDynamicImportSourceReadyResult::Resolved)
|
|
}
|
|
NativeDynamicModuleSourceImportResolution::Rejected => {
|
|
Ok(FrameDocumentDynamicImportSourceReadyResult::Rejected)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn continue_ready_evaluation_import(
|
|
&mut self,
|
|
action: FrameDocumentDynamicImportEvaluationReadyAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportEvaluationReadyResult, String> {
|
|
let (request, graph) = action.into_parts();
|
|
let document_owner = request.owner();
|
|
if !self
|
|
.vm
|
|
.dynamic_module_import_owner_is_current(document_owner)
|
|
{
|
|
self.vm.record_runtime_warning(format_args!(
|
|
"dropped stale dynamic import before evaluation: owner={document_owner:?}"
|
|
));
|
|
return Ok(FrameDocumentDynamicImportEvaluationReadyResult::DroppedStaleOwner);
|
|
}
|
|
self.body.note_page_realm_body_attempted();
|
|
let evaluation = self.vm.start_native_dynamic_module_import_evaluation(graph);
|
|
if !self
|
|
.vm
|
|
.dynamic_module_import_owner_is_current(document_owner)
|
|
{
|
|
self.vm.record_runtime_warning(format_args!(
|
|
"dropped dynamic import settlement after evaluation replaced its document: owner={document_owner:?}"
|
|
));
|
|
return Ok(FrameDocumentDynamicImportEvaluationReadyResult::DroppedStaleOwner);
|
|
}
|
|
match evaluation {
|
|
Ok(DynamicModuleImportEvaluationStart::Completed(target)) => {
|
|
self.body
|
|
.resolve_completed_evaluation_import(self.vm, request, &target)
|
|
.map_err(|error| error.message().to_owned())?;
|
|
Ok(FrameDocumentDynamicImportEvaluationReadyResult::Resolved)
|
|
}
|
|
Ok(DynamicModuleImportEvaluationStart::Pending { target, promise }) => {
|
|
self.vm
|
|
.attach_native_dynamic_module_import_reactions(request, target, promise)
|
|
.map_err(|error| error.message().to_owned())?;
|
|
Ok(FrameDocumentDynamicImportEvaluationReadyResult::Pending)
|
|
}
|
|
Err(error) => {
|
|
self.body
|
|
.reject_dynamic_import(self.vm, request, &error)
|
|
.map_err(|error| error.message().to_owned())?;
|
|
Ok(FrameDocumentDynamicImportEvaluationReadyResult::Rejected)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn record_restored_after_unexpected_complete(
|
|
&mut self,
|
|
_diagnostic: FrameDocumentDynamicImportOwnerActionDiagnostic,
|
|
) -> std::result::Result<(), String> {
|
|
self.unsupported("unexpected complete warning")
|
|
}
|
|
|
|
fn reject_dynamic_import(
|
|
&mut self,
|
|
action: FrameDocumentDynamicImportRejectAction,
|
|
) -> std::result::Result<FrameDocumentDynamicImportRejectResult, String> {
|
|
let (request, error) = action.into_parts();
|
|
let document_owner = request.owner();
|
|
if !self
|
|
.vm
|
|
.dynamic_module_import_owner_is_current(document_owner)
|
|
{
|
|
self.vm.record_runtime_warning(format_args!(
|
|
"dropped stale dynamic import rejection: owner={document_owner:?}"
|
|
));
|
|
return Ok(FrameDocumentDynamicImportRejectResult::DroppedStaleOwner);
|
|
}
|
|
self.body.note_page_realm_body_attempted();
|
|
self.body
|
|
.reject_dynamic_import(self.vm, request, &error)
|
|
.map_err(|error| error.message().to_owned())?;
|
|
Ok(FrameDocumentDynamicImportRejectResult::Rejected)
|
|
}
|
|
|
|
fn record_action_resumed(
|
|
&mut self,
|
|
_diagnostic: FrameDocumentDynamicImportOwnerActionDiagnostic,
|
|
) {
|
|
}
|
|
|
|
fn record_action_failed(
|
|
&mut self,
|
|
_diagnostic: FrameDocumentDynamicImportOwnerActionDiagnostic,
|
|
_error: &str,
|
|
) {
|
|
}
|
|
}
|
|
|
|
fn chromium_module_key(key: &ModuleMapKey) -> moli_module_script_tree::ModuleMapKey {
|
|
moli_module_script_tree::ModuleMapKey::new(
|
|
key.url().clone(),
|
|
match key.kind() {
|
|
ModuleKind::JavaScript => moli_module_script_tree::ModuleKind::JavaScript,
|
|
ModuleKind::Json => moli_module_script_tree::ModuleKind::Json,
|
|
ModuleKind::Css => moli_module_script_tree::ModuleKind::Css,
|
|
ModuleKind::ModulePreloadText => moli_module_script_tree::ModuleKind::JavaScript,
|
|
ModuleKind::WebAssembly => moli_module_script_tree::ModuleKind::WebAssembly,
|
|
},
|
|
moli_module_script_tree::ModuleAttributesKey::from_pairs(key.attributes().pairs().to_vec()),
|
|
)
|
|
}
|
|
|
|
fn native_dynamic_import_owner_fetch_starts_for_continuation(
|
|
continuation: &DynamicModuleFetchContinuation,
|
|
) -> Vec<Option<FrameDocumentModuleFetchClientStart>> {
|
|
continuation
|
|
.pending_fetch_requests()
|
|
.map(|requests| vec![None; requests.len()])
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn module_script_source_for_runtime_graph_start(
|
|
vm: &mut ScriptVm,
|
|
script: &PreparedScript,
|
|
) -> std::result::Result<(ModuleSource, bool), ModuleLoadError> {
|
|
match &script.source {
|
|
crate::planning::ScriptSource::External => Ok((ModuleSource::text(String::new()), true)),
|
|
crate::planning::ScriptSource::Loaded(source) => {
|
|
Ok((ModuleSource::text(source.clone()), true))
|
|
}
|
|
crate::planning::ScriptSource::LoadedBinary { bytes, .. } => {
|
|
Ok((ModuleSource::binary(bytes.clone()), true))
|
|
}
|
|
crate::planning::ScriptSource::Inline(source) => Ok((
|
|
vm.inline_module_script_source_for_graph_start(script, source),
|
|
false,
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn runtime_owned_module_script_graph_job_for_prepared_script(
|
|
vm: &mut ScriptVm,
|
|
script: &PreparedScript,
|
|
) -> std::result::Result<NativeModuleGraphJob, ModuleLoadError> {
|
|
match &script.source {
|
|
crate::planning::ScriptSource::External => Ok(
|
|
crate::module_runtime::runtime_owned_external_module_script_graph_job(
|
|
vm,
|
|
&script.url,
|
|
&script.initiator_url,
|
|
&script.fetch_metadata,
|
|
),
|
|
),
|
|
crate::planning::ScriptSource::Loaded(_)
|
|
| crate::planning::ScriptSource::LoadedBinary { .. }
|
|
| crate::planning::ScriptSource::Inline(_) => {
|
|
let (source, source_is_external) =
|
|
module_script_source_for_runtime_graph_start(vm, script)?;
|
|
crate::module_runtime::runtime_owned_loaded_module_script_graph_job(
|
|
vm,
|
|
source,
|
|
&script.url,
|
|
&script.initiator_url,
|
|
&script.fetch_metadata,
|
|
source_is_external,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
const DYNAMIC_MODULE_REACTION_ID_SLOT: &str = "reactionId";
|
|
const MODULE_SCRIPT_REACTION_ID_SLOT: &str = "moduleScriptReactionId";
|
|
const MODULE_REACTION_SCHEDULER_LANE_ID_SLOT: &str = "schedulerLaneId";
|
|
const MODULE_REACTION_LOCAL_WINDOW_ID_SLOT: &str = "localWindowId";
|
|
const MODULE_REACTION_DOCUMENT_ID_SLOT: &str = "documentId";
|
|
const MODULE_REACTION_REALM_ID_SLOT: &str = "realmId";
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, data_properties, enumerable)]
|
|
struct NativeDynamicModuleReactionDataDeclaration<'scope> {
|
|
reaction_id: v8::Local<'scope, v8::BigInt>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, data_properties, enumerable)]
|
|
struct NativeModuleScriptReactionDataDeclaration<'scope> {
|
|
module_script_reaction_id: v8::Local<'scope, v8::BigInt>,
|
|
scheduler_lane_id: v8::Local<'scope, v8::BigInt>,
|
|
local_window_id: v8::Local<'scope, v8::BigInt>,
|
|
document_id: v8::Local<'scope, v8::BigInt>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(plain, data_properties, enumerable)]
|
|
struct NativeChildModuleScriptReactionDataDeclaration<'scope> {
|
|
module_script_reaction_id: v8::Local<'scope, v8::BigInt>,
|
|
scheduler_lane_id: v8::Local<'scope, v8::BigInt>,
|
|
local_window_id: v8::Local<'scope, v8::BigInt>,
|
|
document_id: v8::Local<'scope, v8::BigInt>,
|
|
realm_id: v8::Local<'scope, v8::BigInt>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
enum NativeModuleEvaluationOwner {
|
|
Script,
|
|
DynamicImport,
|
|
}
|
|
|
|
pub(crate) struct NativeDynamicModuleEvaluation {
|
|
target: DynamicModuleEvaluationTarget,
|
|
promise: Option<v8::Global<v8::Promise>>,
|
|
}
|
|
|
|
struct NativeModuleEvaluationResult {
|
|
module: v8::Global<v8::Module>,
|
|
promise: Option<v8::Global<v8::Promise>>,
|
|
}
|
|
|
|
enum DynamicModuleImportEvaluationStart {
|
|
Completed(DynamicModuleEvaluationTarget),
|
|
Pending {
|
|
target: DynamicModuleEvaluationTarget,
|
|
promise: v8::Global<v8::Promise>,
|
|
},
|
|
}
|
|
|
|
pub(crate) enum RuntimeModuleScriptGraphStart {
|
|
NotModuleScript,
|
|
Started(NativeModuleOwnerActions),
|
|
}
|
|
|
|
impl NativeDynamicModuleEvaluation {
|
|
pub(crate) fn into_parts(
|
|
self,
|
|
) -> (
|
|
DynamicModuleEvaluationTarget,
|
|
Option<v8::Global<v8::Promise>>,
|
|
) {
|
|
(self.target, self.promise)
|
|
}
|
|
}
|
|
|
|
impl ScriptVm {
|
|
pub(crate) fn current_main_document_task_owner(&self) -> Option<FrameDocumentTaskOwner> {
|
|
self._context_host
|
|
.borrow()
|
|
.current_main_document_task_owner()
|
|
}
|
|
|
|
pub(crate) fn current_main_parser_module_graph_fetch_target(
|
|
&self,
|
|
load_id: u64,
|
|
) -> Option<crate::page_resource_completion::MainParserModuleGraphFetchTarget> {
|
|
let pending_script_id = self
|
|
.document_runtime
|
|
.parser_module_scripts()
|
|
.pending_script_id_for_fetch(load_id)?;
|
|
(self.current_main_document_task_owner() == Some(pending_script_id.owner().task_owner()))
|
|
.then(|| {
|
|
crate::page_resource_completion::MainParserModuleGraphFetchTarget::new(
|
|
pending_script_id,
|
|
load_id,
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn main_parser_module_graph_fetch_target_is_current(
|
|
&self,
|
|
target: crate::page_resource_completion::MainParserModuleGraphFetchTarget,
|
|
) -> bool {
|
|
self.current_main_document_task_owner() == Some(target.document_owner())
|
|
&& (self.current_main_parser_module_graph_fetch_target(target.load_id())
|
|
== Some(target)
|
|
|| self
|
|
.document_runtime
|
|
.has_inflight_native_module_script_fetch(target.load_id()))
|
|
}
|
|
|
|
pub(crate) fn current_main_runtime_module_graph_fetch_target(
|
|
&self,
|
|
load_id: u64,
|
|
) -> Option<crate::page_resource_completion::MainRuntimeModuleGraphFetchTarget> {
|
|
let document_owner = self.current_main_document_task_owner()?;
|
|
let dynamic_script_owner_id = self
|
|
.document_runtime
|
|
.runtime_script_work()
|
|
.dynamic_scripts
|
|
.module_script_owner_id_for_pending_fetch(load_id)?;
|
|
Some(
|
|
crate::page_resource_completion::MainRuntimeModuleGraphFetchTarget::new(
|
|
document_owner,
|
|
dynamic_script_owner_id,
|
|
load_id,
|
|
),
|
|
)
|
|
}
|
|
|
|
pub(crate) fn main_runtime_module_graph_fetch_target_is_current(
|
|
&self,
|
|
target: crate::page_resource_completion::MainRuntimeModuleGraphFetchTarget,
|
|
) -> bool {
|
|
self.current_main_document_task_owner() == Some(target.document_owner())
|
|
&& (self.current_main_runtime_module_graph_fetch_target(target.load_id())
|
|
== Some(target)
|
|
|| self
|
|
.document_runtime
|
|
.has_inflight_native_module_script_fetch(target.load_id()))
|
|
}
|
|
|
|
fn main_dynamic_import_graph_fetch_target(
|
|
&self,
|
|
load_id: u64,
|
|
) -> Option<crate::page_resource_completion::MainDynamicImportGraphFetchTarget> {
|
|
let import_owner = self.document_runtime.with_native_module_owner(|owner| {
|
|
owner.inflight_native_dynamic_module_import_fetch_owner(load_id)
|
|
})?;
|
|
import_owner.child_handle().is_none().then(|| {
|
|
crate::page_resource_completion::MainDynamicImportGraphFetchTarget::new(
|
|
import_owner,
|
|
load_id,
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn current_main_dynamic_import_graph_fetch_target(
|
|
&self,
|
|
load_id: u64,
|
|
) -> Option<crate::page_resource_completion::MainDynamicImportGraphFetchTarget> {
|
|
let target = self.main_dynamic_import_graph_fetch_target(load_id)?;
|
|
self.dynamic_module_import_owner_is_current(target.import_owner())
|
|
.then_some(target)
|
|
}
|
|
|
|
/// Retires an old exact dynamic-import fetch after its network terminal is
|
|
/// dequeued for a no-longer-current Document snapshot.
|
|
///
|
|
/// A replacement PageVm may reuse `load_id`, so both the resolver-owned
|
|
/// import identity and the load id must match before the old wait is
|
|
/// removed. This preserves the pre-migration behavior without allowing a
|
|
/// stale terminal to advance or delete replacement work.
|
|
pub(crate) fn retire_stale_main_dynamic_import_graph_fetch(
|
|
&mut self,
|
|
target: crate::page_resource_completion::MainDynamicImportGraphFetchTarget,
|
|
) -> bool {
|
|
if self.main_dynamic_import_graph_fetch_target(target.load_id()) != Some(target) {
|
|
return false;
|
|
}
|
|
self.document_runtime
|
|
.take_inflight_native_dynamic_module_import_fetch(target.load_id())
|
|
.is_some()
|
|
}
|
|
|
|
pub(crate) fn current_main_modulepreload_fetch_target(
|
|
&self,
|
|
load_id: u64,
|
|
) -> Option<crate::page_resource_completion::MainModulepreloadFetchTarget> {
|
|
let document_owner = self.current_main_document_task_owner()?;
|
|
let is_inflight = self.document_runtime.with_native_module_owner(|owner| {
|
|
owner.has_inflight_native_modulepreload_fetch_for(load_id)
|
|
});
|
|
is_inflight.then(|| {
|
|
crate::page_resource_completion::MainModulepreloadFetchTarget::new(
|
|
document_owner,
|
|
load_id,
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn claim_main_parser_deferred_script(
|
|
&mut self,
|
|
task_owner: FrameDocumentTaskOwner,
|
|
script: PreparedScript,
|
|
shared_load: Option<crate::planning::SharedScriptSourceLoad>,
|
|
document_character_set: Option<&str>,
|
|
blocking_signatures_before: std::collections::HashSet<
|
|
crate::DocumentBlockingStylesheetSignature,
|
|
>,
|
|
) -> Result<bool> {
|
|
let Some(start) = self.accept_main_parser_deferred_script(
|
|
task_owner,
|
|
script,
|
|
shared_load,
|
|
document_character_set,
|
|
blocking_signatures_before,
|
|
) else {
|
|
return Ok(false);
|
|
};
|
|
self.start_main_parser_deferred_script(start)?;
|
|
Ok(true)
|
|
}
|
|
|
|
pub(super) fn accept_main_parser_deferred_script(
|
|
&mut self,
|
|
task_owner: FrameDocumentTaskOwner,
|
|
script: PreparedScript,
|
|
shared_load: Option<crate::planning::SharedScriptSourceLoad>,
|
|
document_character_set: Option<&str>,
|
|
blocking_signatures_before: std::collections::HashSet<
|
|
crate::DocumentBlockingStylesheetSignature,
|
|
>,
|
|
) -> Option<crate::document_runtime::PendingMainParserDeferredScriptStart> {
|
|
if self.current_main_document_task_owner() != Some(task_owner) {
|
|
tracing::debug!(
|
|
?task_owner,
|
|
current_owner = ?self.current_main_document_task_owner(),
|
|
script_node_id = ?script.node_id,
|
|
script_url = %script.url,
|
|
"dropping stale main parser-deferred preparation"
|
|
);
|
|
return None;
|
|
}
|
|
let Some(load_delay_token) = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.acquire_current_main_parser_deferred_script_load_delay(task_owner)
|
|
else {
|
|
tracing::debug!(
|
|
?task_owner,
|
|
script_node_id = ?script.node_id,
|
|
script_url = %script.url,
|
|
"dropping main parser-deferred preparation without a current lifecycle owner"
|
|
);
|
|
return None;
|
|
};
|
|
let script_node_id = script.node_id;
|
|
let script_url = script.url.clone();
|
|
let start_action = self.document_runtime.accept_main_parser_deferred_script(
|
|
task_owner,
|
|
script,
|
|
shared_load,
|
|
document_character_set,
|
|
blocking_signatures_before,
|
|
load_delay_token,
|
|
);
|
|
let Some(start_action) = start_action else {
|
|
let released = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_parser_deferred_script_load_delay(task_owner, load_delay_token);
|
|
debug_assert!(
|
|
released,
|
|
"rejected parser-deferred acceptance must release its lifecycle token"
|
|
);
|
|
return None;
|
|
};
|
|
tracing::debug!(
|
|
?task_owner,
|
|
?load_delay_token,
|
|
?script_node_id,
|
|
script_url = %script_url,
|
|
"accepted main parser-deferred PendingScript with lifecycle ownership"
|
|
);
|
|
Some(start_action)
|
|
}
|
|
|
|
pub(super) fn start_main_parser_deferred_script(
|
|
&mut self,
|
|
start: crate::document_runtime::PendingMainParserDeferredScriptStart,
|
|
) -> Result<()> {
|
|
let (task_owner, load_delay_token, start_action) = start.into_parts();
|
|
if self.current_main_document_task_owner() != Some(task_owner) {
|
|
tracing::debug!(
|
|
?task_owner,
|
|
current_owner = ?self.current_main_document_task_owner(),
|
|
"dropping stale accepted main parser-deferred start action"
|
|
);
|
|
return Ok(());
|
|
}
|
|
let document_loader = self.document_runtime.current_document_resource_loader();
|
|
match start_action {
|
|
ParserDeferredScriptStartAction::NoFetch => {}
|
|
ParserDeferredScriptStartAction::ClassicSource(source_load_request) => {
|
|
if let Some(document_loader) = document_loader.as_ref() {
|
|
let (document_url, request_url) =
|
|
source_load_request.network_attribution_urls();
|
|
let network_attribution = crate::page_resource_completion::
|
|
MainParserDeferredClassicSourceNetworkAttribution::new(
|
|
document_url,
|
|
request_url,
|
|
);
|
|
let source_load =
|
|
source_load_request.start(document_loader, document_loader.task_runner());
|
|
let completion_tx = self._context_host.borrow().resource_completion_sender();
|
|
let (pending_script_id, source_load) = source_load.into_parts();
|
|
let completed_source_load = source_load.clone();
|
|
source_load.register_completion_wake(move || {
|
|
let outcome = completed_source_load.try_outcome().expect(
|
|
"script source completion callback requires a terminal outcome",
|
|
);
|
|
let _ = completion_tx.send_main_parser_deferred_classic_source_load(
|
|
ParserDeferredClassicSourceLoadCompletion::new(
|
|
pending_script_id,
|
|
outcome,
|
|
),
|
|
network_attribution,
|
|
);
|
|
});
|
|
} else {
|
|
self.complete_main_parser_deferred_classic_source_load(
|
|
source_load_request.into_failure_completion(
|
|
"parser-deferred classic script accepted without an installed loader",
|
|
),
|
|
);
|
|
}
|
|
}
|
|
ParserDeferredScriptStartAction::ModuleGraph(start) => {
|
|
let (pending_script_id, script) = start.into_parts();
|
|
let start_result = self
|
|
.accept_registered_parser_pending_module_script_graph_for_document_owner(
|
|
pending_script_id,
|
|
&script,
|
|
None,
|
|
);
|
|
let start_error = match start_result {
|
|
Ok(true) => None,
|
|
Ok(false) => Some(anyhow::anyhow!(
|
|
"registered parser module PendingScript {:?} did not start its graph",
|
|
pending_script_id
|
|
)),
|
|
Err(error) => Some(error),
|
|
};
|
|
if let Some(error) = start_error {
|
|
let canceled_token = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.cancel_parser_deferred_script(pending_script_id);
|
|
debug_assert_eq!(
|
|
canceled_token,
|
|
Some(load_delay_token),
|
|
"failed graph start must cancel the accepted PendingScript"
|
|
);
|
|
let released = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_parser_deferred_script_load_delay(
|
|
task_owner,
|
|
load_delay_token,
|
|
);
|
|
debug_assert!(
|
|
released,
|
|
"failed graph start must release its lifecycle token"
|
|
);
|
|
return Err(error);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn start_pending_main_parser_deferred_scripts(&mut self) -> Result<()> {
|
|
let mut starts = self
|
|
.document_runtime
|
|
.take_main_parser_deferred_script_starts();
|
|
while let Some(start) = starts.pop_front() {
|
|
self.start_main_parser_deferred_script(start)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn inline_module_script_source_for_graph_start(
|
|
&mut self,
|
|
script: &PreparedScript,
|
|
source: &str,
|
|
) -> ModuleSource {
|
|
let request = self.content_security_policy_script_element_request(script);
|
|
// Module graphs compile their root before the later page-task
|
|
// evaluation boundary. Feed that graph the Trusted Types/CSP-compliant
|
|
// source now. A rejected inline source becomes an inert root so the
|
|
// module owner can still retire its scheduling state without compiling
|
|
// the rejected text.
|
|
let source = self
|
|
.inline_script_element_source_for_execution(script.node_id, source, request)
|
|
.unwrap_or_default();
|
|
self.inline_module_script_source_with_origin(script, source)
|
|
}
|
|
|
|
pub(crate) fn inline_module_script_source_with_origin(
|
|
&self,
|
|
script: &PreparedScript,
|
|
source: String,
|
|
) -> ModuleSource {
|
|
let position = self
|
|
.document_runtime
|
|
.parser_script_start_position(script.node_id);
|
|
ModuleSource::text_with_origin(
|
|
source,
|
|
crate::document_module_graph::ModuleSourceOrigin {
|
|
url: script.url.clone(),
|
|
line_offset: position.map_or(0, |position| {
|
|
position.line.saturating_sub(1).min(i32::MAX as u64) as u32
|
|
}),
|
|
column_offset: position.map_or(0, |position| {
|
|
position.column.saturating_sub(1).min(i32::MAX as u64) as u32
|
|
}),
|
|
},
|
|
)
|
|
}
|
|
|
|
pub(crate) fn seal_main_parser_deferred_scripts(
|
|
&mut self,
|
|
task_owner: FrameDocumentTaskOwner,
|
|
) -> Option<crate::page_task_queue::PostParsePageOwnedWork> {
|
|
if self.current_main_document_task_owner() != Some(task_owner) {
|
|
tracing::debug!(
|
|
?task_owner,
|
|
current_owner = ?self.current_main_document_task_owner(),
|
|
"dropping stale main parser-deferred EOF finalization"
|
|
);
|
|
return None;
|
|
}
|
|
let owner = MainParserDocumentOwner::new(task_owner);
|
|
let initial_count = match self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.seal_parser_deferred_scripts(owner)
|
|
{
|
|
Ok(initial_count) => initial_count,
|
|
Err(missing) => {
|
|
self.record_runtime_warning(format_args!(
|
|
"dropping parser-deferred script queue because module PendingScript is missing for parser position {} node {:?}",
|
|
missing.parser_position(),
|
|
missing.script_node_id()
|
|
));
|
|
return None;
|
|
}
|
|
};
|
|
self.arm_main_parser_deferred_scripts(owner, initial_count)
|
|
}
|
|
|
|
pub(crate) fn complete_main_parser_deferred_classic_source_load(
|
|
&mut self,
|
|
completion: MainParserDeferredClassicSourceLoadCompletion,
|
|
) {
|
|
let pending_script_id = completion.pending_script_id();
|
|
let result = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.complete_parser_deferred_classic_source_load(completion);
|
|
match result {
|
|
ParserDeferredClassicSourceLoadApplyResult::Applied => {}
|
|
ParserDeferredClassicSourceLoadApplyResult::MissingDocument => {
|
|
tracing::debug!(
|
|
owner = ?pending_script_id.owner(),
|
|
parser_position = pending_script_id.parser_position(),
|
|
script_node_id = ?pending_script_id.script_node_id(),
|
|
"dropping parser-deferred classic source terminal for retired document"
|
|
);
|
|
}
|
|
ParserDeferredClassicSourceLoadApplyResult::MissingPendingScript => {
|
|
tracing::warn!(
|
|
owner = ?pending_script_id.owner(),
|
|
parser_position = pending_script_id.parser_position(),
|
|
script_node_id = ?pending_script_id.script_node_id(),
|
|
"dropping parser-deferred classic source terminal without PendingScript"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn arm_main_parser_deferred_scripts(
|
|
&mut self,
|
|
owner: MainParserDocumentOwner,
|
|
initial_count: usize,
|
|
) -> Option<crate::page_task_queue::PostParsePageOwnedWork> {
|
|
if initial_count == 0 {
|
|
self.document_runtime
|
|
.disarm_main_parser_deferred_scripts(owner.task_owner());
|
|
return None;
|
|
}
|
|
self.document_runtime
|
|
.arm_main_parser_deferred_scripts(owner.task_owner());
|
|
Some(
|
|
crate::page_task_queue::PostParsePageOwnedWork::main_parser_deferred_scripts(
|
|
owner.task_owner(),
|
|
initial_count,
|
|
),
|
|
)
|
|
}
|
|
|
|
fn queue_main_module_script_graph_ready_work(
|
|
&mut self,
|
|
continuation: ModuleScriptContinuation,
|
|
) -> bool {
|
|
assert_eq!(
|
|
continuation.completion_owner(),
|
|
ModuleScriptCompletionOwner::Parser,
|
|
"runtime-owned ready graph continuations should be owned by DynamicScriptOwner"
|
|
);
|
|
let work = continuation.into_main_document_graph_ready_work();
|
|
self.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.notify_module_script_graph_ready_work(work)
|
|
}
|
|
|
|
fn queue_main_parser_module_graph_failure_work(
|
|
&mut self,
|
|
failure: ParserModuleScriptFailure,
|
|
) -> bool {
|
|
assert_eq!(
|
|
failure.continuation.completion_owner(),
|
|
ModuleScriptCompletionOwner::Parser,
|
|
"runtime-owned graph failures should be owned by DynamicScriptOwner"
|
|
);
|
|
self.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.notify_module_script_graph_failed_action(DocumentOwnedScriptReadyAction::new(
|
|
failure
|
|
.continuation
|
|
.parser_document_owner()
|
|
.expect("parser graph failure requires its original document owner"),
|
|
failure,
|
|
))
|
|
}
|
|
|
|
fn queue_main_parser_module_evaluation_work(
|
|
&mut self,
|
|
evaluation: ModuleScriptEvaluationContinuation,
|
|
) {
|
|
assert_eq!(
|
|
evaluation.script_continuation.completion_owner(),
|
|
ModuleScriptCompletionOwner::Parser,
|
|
"runtime-owned module evaluation continuations should be owned by DynamicScriptOwner"
|
|
);
|
|
let owner = evaluation
|
|
.script_continuation
|
|
.parser_document_owner()
|
|
.expect("parser module evaluation requires its original document owner");
|
|
if evaluation.reaction_state.is_pending() {
|
|
self.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.push_pending_parser_module_evaluation_with_reaction_id(
|
|
DocumentOwnedScriptReadyAction::new(owner, evaluation.script_continuation),
|
|
evaluation.root_entry,
|
|
evaluation.reaction_id,
|
|
);
|
|
} else {
|
|
self.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.notify_module_script_evaluation_completed(DocumentOwnedScriptReadyAction::new(
|
|
owner, evaluation,
|
|
));
|
|
}
|
|
}
|
|
|
|
pub(crate) fn clear_pending_module_script_fetches_for_script(
|
|
&mut self,
|
|
node_id: NodeId,
|
|
owner_error: &ModuleLoadError,
|
|
) {
|
|
let (stale_load_ids, stale_joined_clients) = self
|
|
.document_runtime
|
|
.parser_module_scripts_mut()
|
|
.clear_pending_fetches_for_script(node_id);
|
|
for load_id in stale_load_ids {
|
|
tracing::debug!(
|
|
load_id,
|
|
owner_error = owner_error.message(),
|
|
"detached pending module script fetch from failed owner; network completion will settle the module map entry"
|
|
);
|
|
}
|
|
for client in stale_joined_clients {
|
|
let detached = self
|
|
.document_runtime
|
|
.detach_native_module_fetch_waiter(client);
|
|
tracing::debug!(
|
|
?client,
|
|
detached,
|
|
owner_error = owner_error.message(),
|
|
"detached joined module script fetch client from failed owner"
|
|
);
|
|
}
|
|
}
|
|
|
|
fn clear_runtime_owned_module_script_graph_waits_for_owner(
|
|
&mut self,
|
|
continuation: &ModuleScriptContinuation,
|
|
owner_error: &ModuleLoadError,
|
|
) {
|
|
let Some(owner_id) = continuation.dynamic_script_owner_id() else {
|
|
return;
|
|
};
|
|
let (stale_load_ids, stale_joined_clients) = self
|
|
.document_runtime
|
|
.runtime_script_work_mut()
|
|
.dynamic_scripts
|
|
.clear_module_script_graph_pending_waits(owner_id);
|
|
for load_id in stale_load_ids {
|
|
tracing::debug!(
|
|
load_id,
|
|
dynamic_script_owner_id = ?owner_id,
|
|
owner_error = owner_error.message(),
|
|
"detached runtime-owned pending module script fetch from failed owner; network completion will settle the module map entry"
|
|
);
|
|
}
|
|
for client in stale_joined_clients {
|
|
let detached = self
|
|
.document_runtime
|
|
.detach_native_module_fetch_waiter(client);
|
|
tracing::debug!(
|
|
?client,
|
|
detached,
|
|
dynamic_script_owner_id = ?owner_id,
|
|
owner_error = owner_error.message(),
|
|
"detached runtime-owned joined module script fetch client from failed owner"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn complete_native_module_graph_fetch(
|
|
&mut self,
|
|
completion: ModuleGraphFetchCompletion,
|
|
) -> Result<()> {
|
|
let load_id = completion.load_id;
|
|
let has_modulepreload = self.document_runtime.with_native_module_owner(|owner| {
|
|
owner.has_inflight_native_modulepreload_fetch_for(completion.load_id)
|
|
});
|
|
if has_modulepreload {
|
|
if let Some(network_result) = completion.network_result.as_deref() {
|
|
self.record_module_graph_subresource_network_result(&completion, network_result);
|
|
self.record_modulepreload_resource_performance_entry(
|
|
&completion.request_url,
|
|
network_result,
|
|
);
|
|
}
|
|
self.warn_on_module_graph_fetch_metadata_mismatch(
|
|
&completion,
|
|
ModuleGraphFetchRequester::ModulePreload,
|
|
ModuleGraphFetchOrdering::BackgroundPreload,
|
|
);
|
|
if self
|
|
.apply_main_modulepreload_fetch_result(load_id, completion.result)?
|
|
.is_none()
|
|
{
|
|
return Err(anyhow::anyhow!(
|
|
"known legacy modulepreload fetch lost its in-flight request before application"
|
|
));
|
|
}
|
|
return Ok(());
|
|
}
|
|
if matches!(
|
|
completion.requester,
|
|
ModuleGraphFetchRequester::ParserOwnedModuleScript
|
|
| ModuleGraphFetchRequester::RuntimeOwnedModuleScript
|
|
) {
|
|
self.complete_abandoned_module_script_graph_fetch(completion)?;
|
|
return Ok(());
|
|
}
|
|
let result_summary = match &completion.result {
|
|
Ok(source) => format!("ok({} bytes)", source.len()),
|
|
Err(error) => format!("error({error})"),
|
|
};
|
|
self.record_runtime_warning(format_args!(
|
|
"native module graph fetch completion {load_id} arrived without an in-flight module graph job: requester={:?} ordering={:?} {result_summary}",
|
|
completion.requester,
|
|
completion.ordering
|
|
));
|
|
Ok(())
|
|
}
|
|
|
|
fn apply_main_modulepreload_fetch_result(
|
|
&mut self,
|
|
load_id: u64,
|
|
result: std::result::Result<ModuleGraphFetchedSource, String>,
|
|
) -> Result<Option<ModuleEntryId>> {
|
|
let Some(preload) = self
|
|
.document_runtime
|
|
.take_inflight_native_modulepreload_fetch(load_id)
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
let fetch_key = preload.module_key().clone();
|
|
let source = match result {
|
|
Ok(fetched_source) => self.module_graph_fetched_source_or_csp_error(
|
|
load_id,
|
|
fetched_source,
|
|
preload.fetch_metadata(),
|
|
),
|
|
Err(error) => Err(ModuleLoadError::new(ModuleLoadStage::Fetch, error)),
|
|
};
|
|
let application = match source {
|
|
Ok(fetched_source) => {
|
|
let effective_key = preload.effective_key_for_fetched_source(&fetched_source);
|
|
let effective_fetch_metadata =
|
|
preload.effective_fetch_metadata_for_fetched_source(&fetched_source);
|
|
self.record_css_modulepreload_source_for_owner(&effective_key, &fetched_source);
|
|
self.document_runtime
|
|
.insert_native_module_source_for_request(
|
|
fetch_key,
|
|
effective_key,
|
|
fetched_source.into_source(),
|
|
effective_fetch_metadata,
|
|
)
|
|
}
|
|
Err(error) => {
|
|
self.record_css_modulepreload_failure_for_owner(&fetch_key);
|
|
self.document_runtime
|
|
.mark_native_module_failed(fetch_key, error)
|
|
}
|
|
};
|
|
Ok(Some(application))
|
|
}
|
|
|
|
pub(crate) fn apply_live_main_modulepreload_fetch_completion(
|
|
&mut self,
|
|
authorization: crate::runtime::AuthorizedLiveMainModulepreloadFetchCompletion,
|
|
) -> Result<()> {
|
|
let completion = authorization.into_completion();
|
|
let target = completion.target();
|
|
self.apply_main_modulepreload_fetch_result(target.load_id(), completion.into_result())?
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"authorized main modulepreload terminal lost its exact in-flight request"
|
|
)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn accept_main_parser_async_module_script(
|
|
&mut self,
|
|
task_owner: FrameDocumentTaskOwner,
|
|
script: &PreparedScript,
|
|
) -> Result<bool> {
|
|
if script.kind != crate::types::ScriptKind::Module
|
|
|| script.mode != crate::types::ScriptMode::Async
|
|
{
|
|
return Ok(false);
|
|
}
|
|
let Some(binding) = self.accept_main_document_script_load_delay_binding(
|
|
task_owner,
|
|
MainDocumentScriptLoadDelayKind::Module,
|
|
) else {
|
|
return Ok(false);
|
|
};
|
|
self.accept_main_parser_async_module_script_with_binding(task_owner, script, binding)
|
|
}
|
|
|
|
pub(crate) fn accept_main_parser_async_module_admission(
|
|
&mut self,
|
|
admission: crate::document_script_scheduler::MainParserAsyncModuleAdmission,
|
|
) -> Result<bool> {
|
|
let (script, binding) = admission.into_parts();
|
|
let task_owner = binding.owner();
|
|
self.accept_main_parser_async_module_script_with_binding(task_owner, &script, binding)
|
|
}
|
|
|
|
fn accept_main_parser_async_module_script_with_binding(
|
|
&mut self,
|
|
task_owner: FrameDocumentTaskOwner,
|
|
script: &PreparedScript,
|
|
binding: MainDocumentScriptLoadDelayLease,
|
|
) -> Result<bool> {
|
|
assert_eq!(
|
|
binding.owner(),
|
|
task_owner,
|
|
"parser async-module admission lease must target its exact PendingScript owner"
|
|
);
|
|
assert_eq!(
|
|
binding.kind(),
|
|
MainDocumentScriptLoadDelayKind::Module,
|
|
"parser async-module admission requires a module load-delay lease"
|
|
);
|
|
assert_eq!(
|
|
(script.kind, script.mode),
|
|
(
|
|
crate::types::ScriptKind::Module,
|
|
crate::types::ScriptMode::Async
|
|
),
|
|
"parser async-module admission requires an async module"
|
|
);
|
|
if self.current_main_document_task_owner() != Some(task_owner) {
|
|
tracing::debug!(
|
|
?task_owner,
|
|
current_owner = ?self.current_main_document_task_owner(),
|
|
script_node_id = ?script.node_id,
|
|
script_url = %script.url,
|
|
"dropping stale main parser async module acceptance"
|
|
);
|
|
return Ok(false);
|
|
}
|
|
let owner = MainParserDocumentOwner::new(task_owner);
|
|
let pending_script_id =
|
|
ParserPendingScriptId::from_key(owner, ParserPendingScriptKey::from_script(script));
|
|
if self
|
|
.document_runtime
|
|
.parser_module_document_scripts()
|
|
.has_module_script(pending_script_id)
|
|
{
|
|
self.record_runtime_warning(format_args!(
|
|
"rejecting duplicate main parser async module PendingScript {:?}",
|
|
pending_script_id
|
|
));
|
|
let _ = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_document_script_load_delay(binding);
|
|
return Ok(false);
|
|
}
|
|
let load_delay_token = binding.load_delay_token();
|
|
let watch = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.register_and_watch_module_script(owner, script);
|
|
debug_assert_eq!(watch.pending_script_id(), pending_script_id);
|
|
if !watch.watched() {
|
|
let _ = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.discard_module_script(pending_script_id);
|
|
let settled = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_document_script_load_delay(binding);
|
|
tracing::debug!(
|
|
?task_owner,
|
|
?pending_script_id,
|
|
?load_delay_token,
|
|
settled = ?settled,
|
|
"cancelled main parser async module lifecycle binding after watch rejection"
|
|
);
|
|
return Ok(false);
|
|
}
|
|
let started = match self
|
|
.accept_registered_parser_pending_module_script_graph_for_document_owner(
|
|
pending_script_id,
|
|
script,
|
|
Some(binding),
|
|
) {
|
|
Ok(started) => started,
|
|
Err(error) => {
|
|
let _ = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.discard_module_script(pending_script_id);
|
|
return Err(error);
|
|
}
|
|
};
|
|
if !started {
|
|
let _ = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.discard_module_script(pending_script_id);
|
|
tracing::debug!(
|
|
?task_owner,
|
|
?pending_script_id,
|
|
?load_delay_token,
|
|
"cancelled main parser async module after graph start rejected and released its lease"
|
|
);
|
|
return Ok(false);
|
|
}
|
|
tracing::debug!(
|
|
?task_owner,
|
|
?pending_script_id,
|
|
?load_delay_token,
|
|
ready_before_graph_start = watch.queued_ready_work(),
|
|
script_url = %script.url,
|
|
"accepted and watched main parser async module before graph work"
|
|
);
|
|
if self
|
|
.document_runtime
|
|
.parser_module_document_scripts()
|
|
.has_ready_work()
|
|
{
|
|
let _ = self.enqueue_parser_owned_module_continuation();
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
fn accept_registered_parser_pending_module_script_graph_for_document_owner(
|
|
&mut self,
|
|
pending_script_id: ParserPendingScriptId<MainParserDocumentOwner>,
|
|
script: &PreparedScript,
|
|
mut load_delay_binding: Option<crate::frame_owner_model::MainDocumentScriptLoadDelayLease>,
|
|
) -> Result<bool> {
|
|
let owner = pending_script_id.owner();
|
|
if script.kind != crate::types::ScriptKind::Module
|
|
|| ParserPendingScriptId::from_key(owner, ParserPendingScriptKey::from_script(script))
|
|
!= pending_script_id
|
|
{
|
|
self.record_runtime_warning(format_args!(
|
|
"dropping parser module graph start whose script does not match PendingScript {:?}",
|
|
pending_script_id
|
|
));
|
|
if let Some(binding) = load_delay_binding.take() {
|
|
let _ = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_document_script_load_delay(binding);
|
|
}
|
|
return Ok(false);
|
|
}
|
|
if self.current_main_document_task_owner() != Some(owner.task_owner()) {
|
|
tracing::debug!(
|
|
?owner,
|
|
current_owner = ?self.current_main_document_task_owner(),
|
|
script_node_id = ?script.node_id,
|
|
script_url = %script.url,
|
|
"dropping stale registered parser module graph start"
|
|
);
|
|
if let Some(binding) = load_delay_binding.take() {
|
|
let _ = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_document_script_load_delay(binding);
|
|
}
|
|
return Ok(false);
|
|
}
|
|
if !self
|
|
.document_runtime
|
|
.parser_module_document_scripts()
|
|
.has_module_script(pending_script_id)
|
|
{
|
|
self.record_runtime_warning(format_args!(
|
|
"dropping parser module graph start without registered PendingScript {:?}",
|
|
pending_script_id
|
|
));
|
|
if let Some(binding) = load_delay_binding.take() {
|
|
let _ = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_document_script_load_delay(binding);
|
|
}
|
|
return Ok(false);
|
|
}
|
|
let mut continuation =
|
|
ModuleScriptContinuation::new_parser(script.clone(), pending_script_id);
|
|
if let Some(binding) = load_delay_binding.take() {
|
|
continuation = continuation.with_main_document_load_delay_binding(binding);
|
|
}
|
|
if self
|
|
.document_runtime
|
|
.current_document_resource_loader()
|
|
.is_none()
|
|
{
|
|
let error = ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
"parser module graph accepted without a Document resource authority",
|
|
);
|
|
let pending_failure =
|
|
self.notify_module_script_graph_failure_for_owner(continuation, error);
|
|
debug_assert!(
|
|
pending_failure.is_none(),
|
|
"parser module failure should stay on its watched PendingScript owner queue"
|
|
);
|
|
tracing::debug!(
|
|
url = %script.url,
|
|
position = script.position,
|
|
node_id = ?script.node_id,
|
|
"recorded missing-authority parser module graph failure on preparation-time PendingScript"
|
|
);
|
|
return Ok(true);
|
|
}
|
|
let job =
|
|
match crate::module_runtime::parser_owned_module_script_graph_job_for_prepared_script(
|
|
self, script,
|
|
) {
|
|
Ok(Some(job)) => job,
|
|
Ok(None) => {
|
|
let error = ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
"registered parser module PendingScript produced no module graph job",
|
|
);
|
|
let pending_failure =
|
|
self.notify_module_script_graph_failure_for_owner(continuation, error);
|
|
debug_assert!(
|
|
pending_failure.is_none(),
|
|
"parser module no-job failure should stay on its watched PendingScript owner queue"
|
|
);
|
|
return Ok(true);
|
|
}
|
|
Err(error) => {
|
|
let pending_failure =
|
|
self.notify_module_script_graph_failure_for_owner(continuation, error);
|
|
debug_assert!(
|
|
pending_failure.is_none(),
|
|
"parser module graph failure should stay on its watched PendingScript owner queue"
|
|
);
|
|
return Ok(true);
|
|
}
|
|
};
|
|
let continuation = continuation.with_resumed_graph_job(job);
|
|
let advance = continuation.advance_graph(self);
|
|
let actions = self.handle_module_script_graph_advance_for_owner(advance);
|
|
let (ready_scripts, ready_evaluations, runtime_failures) = actions.into_parts();
|
|
debug_assert!(
|
|
ready_scripts.is_empty() && ready_evaluations.is_empty() && runtime_failures.is_empty(),
|
|
"parser-owned graph start should not produce immediate ready owner actions"
|
|
);
|
|
Ok(true)
|
|
}
|
|
|
|
pub(crate) fn start_runtime_module_script_graph_for_owner(
|
|
&mut self,
|
|
script: &PreparedScript,
|
|
dynamic_script_owner_id: crate::dynamic_script_owner::DynamicScriptOwnerId,
|
|
) -> RuntimeModuleScriptGraphStart {
|
|
if script.kind != crate::types::ScriptKind::Module || script.url.scheme() == "data" {
|
|
return RuntimeModuleScriptGraphStart::NotModuleScript;
|
|
}
|
|
let document_owner = self
|
|
.current_main_document_task_owner()
|
|
.expect("runtime module graph start requires a current main Document owner");
|
|
let continuation = ModuleScriptContinuation::new_runtime(
|
|
script.clone(),
|
|
dynamic_script_owner_id,
|
|
document_owner,
|
|
);
|
|
let job = match runtime_owned_module_script_graph_job_for_prepared_script(self, script) {
|
|
Ok(job) => job,
|
|
Err(error) => {
|
|
let actions = self
|
|
.notify_module_script_graph_failure_for_owner(continuation, error)
|
|
.map(|(continuation, error)| {
|
|
NativeModuleOwnerActions::from_runtime_module_failure(continuation, error)
|
|
})
|
|
.unwrap_or_else(NativeModuleOwnerActions::empty);
|
|
return RuntimeModuleScriptGraphStart::Started(actions);
|
|
}
|
|
};
|
|
let continuation = continuation.with_resumed_graph_job(job);
|
|
let advance = continuation.advance_graph(self);
|
|
let actions = self.handle_runtime_module_script_graph_advance_for_dynamic_owner(advance);
|
|
RuntimeModuleScriptGraphStart::Started(actions)
|
|
}
|
|
|
|
pub(crate) fn dynamic_module_import_owner_is_current(
|
|
&self,
|
|
owner: crate::module_runtime::DynamicModuleImportOwner,
|
|
) -> bool {
|
|
self._context_host
|
|
.borrow()
|
|
.dynamic_module_import_owner_is_current(owner)
|
|
}
|
|
|
|
fn advance_native_dynamic_module_import_job_with_body<Body>(
|
|
&mut self,
|
|
mut job: NativeModuleGraphJob,
|
|
body: &mut Body,
|
|
) -> std::result::Result<(), String>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
if job
|
|
.dynamic_import_request()
|
|
.and_then(PendingDynamicModuleImport::child_browsing_context_handle)
|
|
.is_some()
|
|
{
|
|
return self.advance_child_native_dynamic_module_import_job(job);
|
|
}
|
|
let advance = match job.advance_dynamic_import_owner_lane(self) {
|
|
Ok(advance) => advance,
|
|
Err(error) => {
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_graph_advance_failure(job, error);
|
|
return self
|
|
.handle_dynamic_module_terminal_fanout_for_owner_with_body(fanout, body)
|
|
.map(|_| ())
|
|
.map_err(|error| error.to_string());
|
|
}
|
|
};
|
|
self.continue_native_dynamic_module_import_after_tree_advance_with_body(job, advance, body)
|
|
}
|
|
|
|
fn continue_native_dynamic_module_import_after_tree_advance_with_body<Body>(
|
|
&mut self,
|
|
job: NativeModuleGraphJob,
|
|
advance: NativeModuleGraphJobAdvance,
|
|
body: &mut Body,
|
|
) -> std::result::Result<(), String>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
if job
|
|
.dynamic_import_request()
|
|
.and_then(PendingDynamicModuleImport::child_browsing_context_handle)
|
|
.is_some()
|
|
{
|
|
return self
|
|
.continue_child_native_dynamic_module_import_after_tree_advance(job, advance);
|
|
}
|
|
self.continue_main_native_dynamic_module_import_after_tree_advance_with_body(
|
|
job, advance, body,
|
|
)
|
|
.map(|_| ())
|
|
.map_err(|error| error.to_string())
|
|
}
|
|
|
|
fn continue_main_native_dynamic_module_import_after_tree_advance_with_body<Body>(
|
|
&mut self,
|
|
mut job: NativeModuleGraphJob,
|
|
advance: NativeModuleGraphJobAdvance,
|
|
body: &mut Body,
|
|
) -> Result<NativeDynamicModuleTerminalFanoutOutcome>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
let mut outcome = NativeDynamicModuleTerminalFanoutOutcome::default();
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
match advance {
|
|
NativeModuleGraphJobAdvance::NeedFetches(requests) => {
|
|
let joined_clients = job.take_pending_joined_clients();
|
|
let owner_module_fetch_starts = vec![None; requests.len()];
|
|
let scheduled = self
|
|
.document_runtime
|
|
.suspend_native_dynamic_module_import_fetches(
|
|
requests,
|
|
joined_clients,
|
|
job,
|
|
owner_module_fetch_starts,
|
|
);
|
|
fanout.extend_scheduled_dynamic_import_fetches(scheduled);
|
|
}
|
|
NativeModuleGraphJobAdvance::WaitingForFetches => {
|
|
let joined_clients = job.take_pending_joined_clients();
|
|
if joined_clients.is_empty() {
|
|
self.document_runtime
|
|
.resume_native_dynamic_module_import_front(job);
|
|
outcome.record_dynamic_import_job_resumed();
|
|
} else {
|
|
self.document_runtime
|
|
.suspend_native_dynamic_module_import_fetches(
|
|
Vec::new(),
|
|
joined_clients,
|
|
job,
|
|
Vec::new(),
|
|
);
|
|
outcome.record_dynamic_import_wait_retained();
|
|
}
|
|
}
|
|
NativeModuleGraphJobAdvance::Complete(graph) => {
|
|
fanout.push_ready_import(NativeDynamicModuleImportReady { job, graph });
|
|
}
|
|
}
|
|
outcome
|
|
.merge(self.handle_dynamic_module_terminal_fanout_for_owner_with_body(fanout, body)?);
|
|
Ok(outcome)
|
|
}
|
|
|
|
fn run_completed_native_dynamic_module_import_action_with_body<Body>(
|
|
&mut self,
|
|
job: NativeModuleGraphJob,
|
|
graph: ModuleGraphHandle,
|
|
body: &mut Body,
|
|
) -> std::result::Result<FrameDocumentDynamicImportTerminalOutcome, String>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
let document_owner = job
|
|
.dynamic_import_request()
|
|
.expect("completed dynamic import graph must retain its request")
|
|
.owner();
|
|
if let Some((_child_handle, task_owner, realm_id)) = document_owner.child_parts() {
|
|
let owner = task_owner.document_owner();
|
|
let followup = self
|
|
.child_document_modulator_store
|
|
.dynamic_import_ready_followup(
|
|
owner,
|
|
realm_id,
|
|
NativeDynamicModuleImportReady { job, graph },
|
|
);
|
|
return Ok(
|
|
FrameDocumentDynamicImportTerminalOutcome::from_owner_action_queue_followup(
|
|
self.apply_child_dynamic_import_followup(followup),
|
|
),
|
|
);
|
|
}
|
|
self.run_main_dynamic_import_owner_action_with_body(
|
|
FrameDocumentDynamicImportOwnerAction::ready(NativeDynamicModuleImportReady {
|
|
job,
|
|
graph,
|
|
}),
|
|
body,
|
|
)
|
|
}
|
|
|
|
fn run_main_dynamic_import_owner_action_with_body<Body>(
|
|
&mut self,
|
|
action: FrameDocumentDynamicImportOwnerAction,
|
|
body: &mut Body,
|
|
) -> std::result::Result<FrameDocumentDynamicImportTerminalOutcome, String>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
FrameDocumentDynamicImportOwnerActionRunner::new(
|
|
ScriptVmMainDynamicImportOwnerActionHooks::new(self, body),
|
|
)
|
|
.run_owner_action(action)
|
|
}
|
|
|
|
pub(crate) fn has_ready_native_module_owner_actions(&mut self) -> bool {
|
|
self.document_runtime.has_ready_native_module_owner_event()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn drain_ready_native_module_owner_actions(
|
|
&mut self,
|
|
) -> Result<(
|
|
NativeModuleOwnerActions,
|
|
FrameDocumentModuleTerminalQueueFollowup,
|
|
)> {
|
|
self.drain_ready_native_module_owner_actions_with_body(
|
|
&mut ScriptVmCheckpointingMainNativeModuleTaskBody,
|
|
)
|
|
}
|
|
|
|
fn drain_ready_native_module_owner_actions_with_body<Body>(
|
|
&mut self,
|
|
body: &mut Body,
|
|
) -> Result<(
|
|
NativeModuleOwnerActions,
|
|
FrameDocumentModuleTerminalQueueFollowup,
|
|
)>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
if !self.has_ready_native_module_owner_actions() {
|
|
return Ok((
|
|
NativeModuleOwnerActions::empty(),
|
|
FrameDocumentModuleTerminalQueueFollowup::none(),
|
|
));
|
|
}
|
|
let Some(event) = self.document_runtime.take_next_native_module_owner_event() else {
|
|
return Ok((
|
|
NativeModuleOwnerActions::empty(),
|
|
FrameDocumentModuleTerminalQueueFollowup::none(),
|
|
));
|
|
};
|
|
self.dispatch_native_module_owner_event_with_body(event, body)
|
|
}
|
|
|
|
fn dispatch_native_module_owner_event_with_body<Body>(
|
|
&mut self,
|
|
event: NativeModuleOwnerEvent,
|
|
body: &mut Body,
|
|
) -> Result<(
|
|
NativeModuleOwnerActions,
|
|
FrameDocumentModuleTerminalQueueFollowup,
|
|
)>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
match event {
|
|
NativeModuleOwnerEvent::ModuleMapTerminalNotification(notification) => {
|
|
let fanout =
|
|
self.dispatch_native_document_modulator_terminal_notification(notification)?;
|
|
self.handle_document_modulator_terminal_fanout_for_owner_with_body(fanout, body)
|
|
}
|
|
NativeModuleOwnerEvent::ModulepreloadLinkError(handle) => {
|
|
body.note_page_realm_body_attempted();
|
|
self.dispatch_preload_like_link_error_event(handle);
|
|
Ok((
|
|
NativeModuleOwnerActions::empty(),
|
|
FrameDocumentModuleTerminalQueueFollowup::none(),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn has_ready_runtime_owned_module_owner_actions(&mut self) -> bool {
|
|
self.document_runtime
|
|
.runtime_script_work_mut()
|
|
.dynamic_scripts
|
|
.has_ready_module_script_continuation()
|
|
}
|
|
|
|
pub(crate) fn register_native_modulepreload_for_owner(
|
|
&mut self,
|
|
request: NativeModuleSingleFetchRequest,
|
|
) -> std::result::Result<Option<crate::module_runtime::ModulePreloadJobRun>, String> {
|
|
let document_owner = self.current_main_document_task_owner().ok_or_else(|| {
|
|
"main modulepreload fetch started without a current Document owner".to_owned()
|
|
})?;
|
|
let resource_scheduler = self.resource_scheduler();
|
|
let outcome = self
|
|
.document_runtime
|
|
.start_main_document_modulepreload_fetch(document_owner, &resource_scheduler, request)
|
|
.map_err(|error| error.message().to_owned())?;
|
|
Ok(self.consume_main_document_modulepreload_fetch_outcome(outcome))
|
|
}
|
|
|
|
pub(crate) fn register_native_modulepreload_link_for_owner(
|
|
&mut self,
|
|
request: NativeModuleSingleFetchRequest,
|
|
link_client: std::sync::Arc<crate::module_runtime::NativeModulepreloadLinkClient>,
|
|
) -> std::result::Result<Option<crate::module_runtime::ModulePreloadJobRun>, String> {
|
|
let outcome = self
|
|
.document_runtime
|
|
.fetch_single_native_module_for_modulepreload_link(request, link_client)
|
|
.map_err(|error| error.message().to_owned())?;
|
|
let (start, pending_event) = outcome.into_parts();
|
|
if let Some(pending_event) = pending_event {
|
|
self.enqueue_main_modulepreload_link_event(pending_event);
|
|
}
|
|
self.run_native_modulepreload_fetch_start_for_owner(start)
|
|
}
|
|
|
|
fn enqueue_main_modulepreload_link_event(
|
|
&mut self,
|
|
pending_event: crate::document_runtime::PendingNativeModulepreloadLinkEvent,
|
|
) {
|
|
let Some(event_owner) = pending_event.client().main_document_event_owner() else {
|
|
tracing::debug!(
|
|
element = ?pending_event.client().owner(),
|
|
"discarded main modulepreload terminal without an exact Document owner"
|
|
);
|
|
return;
|
|
};
|
|
debug_assert_eq!(event_owner.element(), pending_event.client().owner());
|
|
if !self
|
|
._context_host
|
|
.borrow()
|
|
.main_document_task_owner_is_current(event_owner.owner())
|
|
{
|
|
tracing::debug!(
|
|
owner = ?event_owner.owner(),
|
|
element = ?event_owner.element(),
|
|
"discarded modulepreload terminal for a retired main Document"
|
|
);
|
|
return;
|
|
}
|
|
let ready = pending_event.into_ready_event();
|
|
self.document_runtime
|
|
.enqueue_ready_native_modulepreload_link_event(ready);
|
|
}
|
|
|
|
fn run_native_modulepreload_fetch_start_for_owner(
|
|
&mut self,
|
|
start: NativeModulepreloadFetchStart,
|
|
) -> std::result::Result<Option<crate::module_runtime::ModulePreloadJobRun>, String> {
|
|
let Some(request) = start.started_request() else {
|
|
return Ok(None);
|
|
};
|
|
let document_owner = self.current_main_document_task_owner().ok_or_else(|| {
|
|
"main modulepreload fetch started without a current Document owner".to_owned()
|
|
})?;
|
|
let resource_scheduler = self.resource_scheduler();
|
|
let outcome = self
|
|
.document_runtime
|
|
.schedule_reserved_main_document_modulepreload_fetch(
|
|
document_owner,
|
|
&resource_scheduler,
|
|
request,
|
|
)
|
|
.map_err(|error| error.message().to_owned())?;
|
|
Ok(self.consume_main_document_modulepreload_fetch_outcome(outcome))
|
|
}
|
|
|
|
pub(crate) fn drain_ready_runtime_owned_module_owner_actions(
|
|
&mut self,
|
|
) -> Result<NativeModuleOwnerActions> {
|
|
let Some(work) = self.take_ready_runtime_owned_module_script_continuation_work() else {
|
|
return Ok(NativeModuleOwnerActions::empty());
|
|
};
|
|
Ok(Self::runtime_owned_module_owner_actions_from_work(work))
|
|
}
|
|
|
|
fn runtime_owned_module_owner_actions_from_work(
|
|
work: crate::dynamic_script_owner::DynamicModuleScriptContinuationWork,
|
|
) -> NativeModuleOwnerActions {
|
|
let mut actions = NativeModuleOwnerActions::empty();
|
|
match work {
|
|
crate::dynamic_script_owner::DynamicModuleScriptContinuationWork::Graph {
|
|
continuation,
|
|
} => actions.push_ready_module_script(*continuation),
|
|
crate::dynamic_script_owner::DynamicModuleScriptContinuationWork::Evaluation {
|
|
evaluation,
|
|
} => actions.push_ready_module_evaluation(*evaluation),
|
|
}
|
|
actions
|
|
}
|
|
|
|
fn consume_main_document_modulepreload_fetch_outcome(
|
|
&mut self,
|
|
outcome: crate::document_runtime::MainDocumentModulepreloadFetchOutcome,
|
|
) -> Option<crate::module_runtime::ModulePreloadJobRun> {
|
|
let (job_run, csp_violations, runtime_warning) = outcome.into_parts();
|
|
for violation in csp_violations {
|
|
self.queue_content_security_policy_violation_event_best_effort(&violation);
|
|
}
|
|
if let Some(runtime_warning) = runtime_warning {
|
|
self.record_runtime_warning(format_args!("{runtime_warning}"));
|
|
}
|
|
job_run
|
|
}
|
|
|
|
fn handle_document_modulator_terminal_fanout_for_owner_with_body<Body>(
|
|
&mut self,
|
|
fanout: ModuleMapTerminalFanout,
|
|
body: &mut Body,
|
|
) -> Result<(
|
|
NativeModuleOwnerActions,
|
|
FrameDocumentModuleTerminalQueueFollowup,
|
|
)>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
let (module_script_results, dynamic_import_fanout) = fanout.into_parts();
|
|
let mut actions = NativeModuleOwnerActions::empty();
|
|
for result in module_script_results {
|
|
actions.merge(self.handle_module_script_graph_advance_for_owner(result));
|
|
}
|
|
let dynamic_import_outcome = self
|
|
.handle_dynamic_module_terminal_fanout_for_owner_with_body(
|
|
dynamic_import_fanout,
|
|
body,
|
|
)?;
|
|
Ok((actions, dynamic_import_outcome.child_followup()))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn handle_dynamic_module_terminal_fanout_for_owner(
|
|
&mut self,
|
|
fanout: NativeDynamicModuleTerminalFanout,
|
|
) -> Result<NativeDynamicModuleTerminalFanoutOutcome> {
|
|
self.handle_dynamic_module_terminal_fanout_for_owner_with_body(
|
|
fanout,
|
|
&mut ScriptVmCheckpointingMainNativeModuleTaskBody,
|
|
)
|
|
}
|
|
|
|
fn handle_dynamic_module_terminal_fanout_for_owner_with_body<Body>(
|
|
&mut self,
|
|
fanout: NativeDynamicModuleTerminalFanout,
|
|
body: &mut Body,
|
|
) -> Result<NativeDynamicModuleTerminalFanoutOutcome>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
let (
|
|
dynamic_imports,
|
|
scheduled_dynamic_import_fetches,
|
|
failed_fetches,
|
|
graph_advance_failures,
|
|
restored_after_unexpected_complete,
|
|
) = fanout.into_parts();
|
|
let mut outcome = NativeDynamicModuleTerminalFanoutOutcome::default();
|
|
let scheduled_fetch_count = scheduled_dynamic_import_fetches.len();
|
|
self.schedule_native_dynamic_module_import_fetches(scheduled_dynamic_import_fetches);
|
|
outcome.record_scheduled_dynamic_import_fetches(scheduled_fetch_count);
|
|
for dynamic_import in dynamic_imports {
|
|
let ready_outcome = self
|
|
.run_completed_native_dynamic_module_import_action_with_body(
|
|
dynamic_import.job,
|
|
dynamic_import.graph,
|
|
body,
|
|
)
|
|
.map_err(|error| anyhow::anyhow!(error))?;
|
|
outcome.record_ready_import_outcome(ready_outcome);
|
|
}
|
|
for failure in failed_fetches {
|
|
let failure_outcome = self
|
|
.run_failed_native_dynamic_module_fetch_action_with_body(failure, body)
|
|
.map_err(|error| anyhow::anyhow!(error))?;
|
|
outcome.record_failed_fetch_rejection_outcome(failure_outcome);
|
|
}
|
|
for (job, error) in graph_advance_failures {
|
|
if job
|
|
.dynamic_import_request()
|
|
.and_then(PendingDynamicModuleImport::child_browsing_context_handle)
|
|
.is_some()
|
|
{
|
|
let followup = self
|
|
.enqueue_child_dynamic_import_graph_advance_failed_owner_action(job, error)
|
|
.map_err(|error| anyhow::anyhow!(error))?;
|
|
outcome.record_graph_advance_failure_followup(followup);
|
|
} else {
|
|
let failure_outcome = self
|
|
.run_main_dynamic_import_owner_action_with_body(
|
|
FrameDocumentDynamicImportOwnerAction::graph_advance_failed(job, error),
|
|
body,
|
|
)
|
|
.map_err(|error| anyhow::anyhow!(error))?;
|
|
outcome.record_graph_advance_failure_outcome(failure_outcome);
|
|
}
|
|
}
|
|
if restored_after_unexpected_complete {
|
|
self.record_runtime_warning(format_args!(
|
|
"native dynamic import tree completed while its pending tree still had clients"
|
|
));
|
|
outcome.record_restored_after_unexpected_complete();
|
|
}
|
|
Ok(outcome)
|
|
}
|
|
|
|
fn handle_module_script_graph_fetch_resume_for_owner(
|
|
&mut self,
|
|
resume: ModuleScriptGraphFetchResume,
|
|
) -> Result<NativeModuleOwnerActions> {
|
|
match resume {
|
|
ModuleScriptGraphFetchResume::Finished { result } => {
|
|
Ok(self.handle_module_script_graph_advance_for_owner(*result))
|
|
}
|
|
ModuleScriptGraphFetchResume::RestoredMissingGraphContinuation => {
|
|
Ok(NativeModuleOwnerActions::empty())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn schedule_native_dynamic_module_import_fetches(
|
|
&mut self,
|
|
scheduled: Vec<DynamicModuleScheduledFetch>,
|
|
) {
|
|
let document_loader = self
|
|
.document_runtime
|
|
.current_document_resource_loader()
|
|
.expect("main dynamic import requires the committed Document resource authority");
|
|
for scheduled_fetch in scheduled {
|
|
let (load_id, request, _) = scheduled_fetch.into_parts();
|
|
let import_owner = self
|
|
.document_runtime
|
|
.with_native_module_owner(|owner| {
|
|
owner.inflight_native_dynamic_module_import_fetch_owner(load_id)
|
|
})
|
|
.expect("scheduled dynamic-import fetch must retain its resolver owner");
|
|
assert!(
|
|
import_owner.child_handle().is_none(),
|
|
"main dynamic-import scheduler cannot publish a child fetch"
|
|
);
|
|
let target = crate::page_resource_completion::MainDynamicImportGraphFetchTarget::new(
|
|
import_owner,
|
|
load_id,
|
|
);
|
|
let document_url = self.document_runtime.document_url().clone();
|
|
self.resource_scheduler()
|
|
.schedule_main_dynamic_import_graph_fetch(
|
|
document_loader.clone(),
|
|
target,
|
|
request,
|
|
document_url,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn complete_shared_module_map_fetch_result(
|
|
&mut self,
|
|
load_id: u64,
|
|
result: std::result::Result<ModuleGraphFetchedSource, String>,
|
|
) -> Result<()> {
|
|
let graph_continuation = self
|
|
.document_runtime
|
|
.take_inflight_native_module_script_fetch(load_id)
|
|
.expect("authorized shared module-map terminal must retain its in-flight fetch");
|
|
let fetch_key = graph_continuation
|
|
.request()
|
|
.pending_fetch_key()
|
|
.cloned()
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"authorized shared module-map terminal {load_id} has no module map key"
|
|
)
|
|
})?;
|
|
let source = match result {
|
|
Ok(fetched_source) => self.module_graph_fetched_source_or_csp_error(
|
|
load_id,
|
|
fetched_source,
|
|
graph_continuation.request().fetch_metadata(),
|
|
),
|
|
Err(error) => Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
format!("native module script fetch completion {load_id} failed: {error}"),
|
|
)),
|
|
};
|
|
match source {
|
|
Ok(fetched_source) => {
|
|
let effective_key = graph_continuation
|
|
.request()
|
|
.effective_key_for_fetched_source(&fetched_source)
|
|
.unwrap_or_else(|| fetch_key.clone());
|
|
let effective_fetch_metadata = graph_continuation
|
|
.request()
|
|
.effective_fetch_metadata_for_fetched_source(&fetched_source);
|
|
self.document_runtime
|
|
.insert_native_module_source_for_request(
|
|
fetch_key,
|
|
effective_key,
|
|
fetched_source.into_source(),
|
|
effective_fetch_metadata,
|
|
);
|
|
}
|
|
Err(error) => {
|
|
self.document_runtime
|
|
.mark_native_module_failed(fetch_key, error);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn complete_abandoned_module_script_graph_fetch(
|
|
&mut self,
|
|
completion: ModuleGraphFetchCompletion,
|
|
) -> Result<()> {
|
|
let load_id = completion.load_id;
|
|
if !self
|
|
.document_runtime
|
|
.has_inflight_native_module_script_fetch(load_id)
|
|
{
|
|
let result_summary = match &completion.result {
|
|
Ok(source) => format!("ok({} bytes)", source.len()),
|
|
Err(error) => format!("error({error})"),
|
|
};
|
|
self.record_runtime_warning(format_args!(
|
|
"native module script fetch completion {load_id} arrived without an owner or in-flight module map continuation: requester={:?} ordering={:?} {result_summary}",
|
|
completion.requester,
|
|
completion.ordering
|
|
));
|
|
return Ok(());
|
|
}
|
|
if let Some(network_result) = completion.network_result.as_deref() {
|
|
self.record_module_graph_subresource_network_result(&completion, network_result);
|
|
}
|
|
self.complete_shared_module_map_fetch_result(load_id, completion.result)
|
|
}
|
|
|
|
fn dynamic_module_fetch_resume_advance_for_owner_with_body<Body>(
|
|
&mut self,
|
|
continuation: DynamicModuleFetchContinuation,
|
|
body: &mut Body,
|
|
) -> Result<FrameDocumentModuleTerminalQueueFollowup>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
let owner_module_fetch_starts =
|
|
native_dynamic_import_owner_fetch_starts_for_continuation(&continuation);
|
|
let advance = self
|
|
.document_runtime
|
|
.continue_native_dynamic_module_import_fetch(continuation, owner_module_fetch_starts);
|
|
Ok(self
|
|
.handle_dynamic_module_terminal_fanout_for_owner_with_body(
|
|
NativeDynamicModuleTerminalFanout::from_owner_advance(advance),
|
|
body,
|
|
)?
|
|
.child_followup())
|
|
}
|
|
|
|
fn dynamic_module_fetch_finish_to_owner_actions_with_body<Body>(
|
|
&mut self,
|
|
finish: DynamicModuleFetchFinish,
|
|
body: &mut Body,
|
|
) -> Result<FrameDocumentModuleTerminalQueueFollowup>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
match finish {
|
|
DynamicModuleFetchFinish::Advanced(continuation) => {
|
|
self.dynamic_module_fetch_resume_advance_for_owner_with_body(continuation, body)
|
|
}
|
|
DynamicModuleFetchFinish::Failed(failure) => {
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_failed_fetch(failure);
|
|
Ok(self
|
|
.handle_dynamic_module_terminal_fanout_for_owner_with_body(fanout, body)?
|
|
.child_followup())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn dispatch_native_document_modulator_terminal_notification(
|
|
&mut self,
|
|
notification: ModuleMapTerminalNotification,
|
|
) -> Result<ModuleMapTerminalFanout> {
|
|
let (key, clients, successful) = notification.into_parts();
|
|
let (fetch_clients, _, modulepreload_link_clients) = clients.into_parts();
|
|
debug_assert!(
|
|
modulepreload_link_clients
|
|
.iter()
|
|
.all(|client| client.frame_document_client().is_none())
|
|
);
|
|
let pending_events = self
|
|
.document_runtime
|
|
.accept_native_modulepreload_link_client_terminals(
|
|
&key,
|
|
modulepreload_link_clients,
|
|
successful,
|
|
);
|
|
for pending_event in pending_events {
|
|
self.enqueue_main_modulepreload_link_event(pending_event);
|
|
}
|
|
self.resume_native_document_modulator_fetch_clients(&key, fetch_clients)
|
|
}
|
|
|
|
fn resume_native_document_modulator_fetch_clients(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
clients: Vec<NativeModuleMapSingleModuleClient>,
|
|
) -> Result<ModuleMapTerminalFanout> {
|
|
let mut fanout = ModuleMapTerminalFanout::empty();
|
|
if clients.is_empty() {
|
|
return Ok(fanout);
|
|
}
|
|
tracing::debug!(
|
|
url = %key.url(),
|
|
client_count = clients.len(),
|
|
"resuming native module map fetch clients"
|
|
);
|
|
for client in clients {
|
|
tracing::trace!(
|
|
url = %key.url(),
|
|
client_name = client.client_name(),
|
|
import_phase = ?client.import_phase(),
|
|
token = ?client.token(),
|
|
"dispatching native module map single-module client"
|
|
);
|
|
match client {
|
|
NativeModuleMapSingleModuleClient::ModuleScript(client) => {
|
|
if let Some(result) = self.resume_module_script_fetch_join_waiter(key, client) {
|
|
fanout.push_module_script_result(result);
|
|
}
|
|
}
|
|
NativeModuleMapSingleModuleClient::DynamicImport(client) => {
|
|
self.resume_native_dynamic_module_fetch_waiter(key, client, &mut fanout)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(fanout)
|
|
}
|
|
|
|
fn resume_native_dynamic_module_fetch_waiter(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
client: NativeDynamicImportSingleModuleClient,
|
|
fanout: &mut ModuleMapTerminalFanout,
|
|
) -> Result<()> {
|
|
let client_token = client.token();
|
|
let Some(joined) = self
|
|
.document_runtime
|
|
.take_joined_native_dynamic_module_import_fetch(client_token)
|
|
else {
|
|
self.record_runtime_warning(format_args!(
|
|
"native module joined fetch client {:?} had no dynamic import continuation",
|
|
client_token
|
|
));
|
|
return Ok(());
|
|
};
|
|
debug_assert_eq!(
|
|
joined.client(),
|
|
client_token,
|
|
"dynamic pending tree should return the requested joined client"
|
|
);
|
|
let document_owner = joined.owner();
|
|
if !self.dynamic_module_import_owner_is_current(document_owner) {
|
|
self.record_runtime_warning(format_args!(
|
|
"dropped stale joined dynamic import terminal: client={client_token:?} owner={document_owner:?}"
|
|
));
|
|
return Ok(());
|
|
}
|
|
match self.finish_native_dynamic_module_joined_fetch(joined, key) {
|
|
DynamicModuleFetchFinish::Advanced(continuation) => {
|
|
self.push_dynamic_module_fetch_resume_advance_into_fanout(continuation, fanout);
|
|
}
|
|
DynamicModuleFetchFinish::Failed(failure) => {
|
|
fanout.push_dynamic_import_fetch_failure(failure);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn push_dynamic_module_fetch_resume_advance_into_fanout(
|
|
&mut self,
|
|
continuation: DynamicModuleFetchContinuation,
|
|
fanout: &mut ModuleMapTerminalFanout,
|
|
) {
|
|
let owner_module_fetch_starts =
|
|
native_dynamic_import_owner_fetch_starts_for_continuation(&continuation);
|
|
let advance = self
|
|
.document_runtime
|
|
.continue_native_dynamic_module_import_fetch(continuation, owner_module_fetch_starts);
|
|
fanout.absorb_dynamic_import_owner_advance(advance);
|
|
}
|
|
|
|
fn run_failed_native_dynamic_module_fetch_action_with_body<Body>(
|
|
&mut self,
|
|
failure: DynamicModuleFetchFailure,
|
|
body: &mut Body,
|
|
) -> std::result::Result<FrameDocumentDynamicImportTerminalOutcome, String>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
let (request, error) = self
|
|
.document_runtime
|
|
.clear_failed_native_dynamic_module_import_fetch(failure);
|
|
self.run_main_dynamic_import_owner_action_with_body(
|
|
FrameDocumentDynamicImportOwnerAction::fetch_failed(request, error),
|
|
body,
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn record_module_graph_subresource_network_result(
|
|
&mut self,
|
|
completion: &ModuleGraphFetchCompletion,
|
|
network_result: &std::result::Result<crate::types::NavigationResponse, String>,
|
|
) {
|
|
let document_url = self.document_runtime.document_url().clone();
|
|
self._context_host
|
|
.borrow_mut()
|
|
.record_staged_get_subresource_network_result_with_initiator(
|
|
None,
|
|
document_url,
|
|
completion.request_url.clone(),
|
|
SubresourceResourceType::Script,
|
|
match completion.requester {
|
|
ModuleGraphFetchRequester::DynamicImport => {
|
|
SubresourceRequestInitiatorType::Script
|
|
}
|
|
ModuleGraphFetchRequester::ParserOwnedModuleScript
|
|
| ModuleGraphFetchRequester::RuntimeOwnedModuleScript
|
|
| ModuleGraphFetchRequester::ModulePreload => {
|
|
SubresourceRequestInitiatorType::Parser
|
|
}
|
|
},
|
|
network_result,
|
|
);
|
|
}
|
|
|
|
pub(crate) fn record_main_modulepreload_network_result(
|
|
&mut self,
|
|
document_url: Url,
|
|
request_url: Url,
|
|
network_result: &std::result::Result<crate::types::NavigationResponse, String>,
|
|
) {
|
|
self._context_host
|
|
.borrow_mut()
|
|
.record_get_subresource_network_result_with_initiator(
|
|
None,
|
|
document_url,
|
|
request_url.clone(),
|
|
SubresourceResourceType::Script,
|
|
SubresourceRequestInitiatorType::Parser,
|
|
network_result,
|
|
);
|
|
self.record_modulepreload_resource_performance_entry(&request_url, network_result);
|
|
}
|
|
|
|
pub(crate) fn record_historical_main_modulepreload_network_result(
|
|
&mut self,
|
|
document_url: Url,
|
|
request_url: Url,
|
|
network_result: &std::result::Result<crate::types::NavigationResponse, String>,
|
|
) {
|
|
self._context_host
|
|
.borrow_mut()
|
|
.record_historical_get_subresource_network_result_with_initiator(
|
|
None,
|
|
document_url,
|
|
request_url,
|
|
SubresourceResourceType::Script,
|
|
SubresourceRequestInitiatorType::Parser,
|
|
network_result,
|
|
);
|
|
}
|
|
|
|
fn record_modulepreload_resource_performance_entry(
|
|
&mut self,
|
|
request_url: &Url,
|
|
network_result: &std::result::Result<crate::types::NavigationResponse, String>,
|
|
) {
|
|
let performance_entry =
|
|
crate::context_bootstrap::ResourcePerformanceEntry::from_network_result(
|
|
request_url.as_str(),
|
|
"link",
|
|
None,
|
|
network_result,
|
|
);
|
|
if let Err(error) = self.with_default_context_scope(|scope, _host_ptr| {
|
|
crate::context_bootstrap::record_resource_performance_entry(scope, performance_entry);
|
|
Ok(())
|
|
}) {
|
|
self.record_runtime_warning(format_args!(
|
|
"failed to record native modulepreload performance entry for `{}`: {}",
|
|
request_url, error
|
|
));
|
|
}
|
|
}
|
|
|
|
fn record_css_modulepreload_source_for_owner(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
fetched_source: &ModuleGraphFetchedSource,
|
|
) {
|
|
if key.kind() != ModuleKind::Css {
|
|
return;
|
|
}
|
|
let Some(source) = fetched_source.source().text_source() else {
|
|
self.record_css_modulepreload_failure_for_owner(key);
|
|
return;
|
|
};
|
|
self._context_host
|
|
.borrow_mut()
|
|
.record_css_module_text_for_url(key.url(), source.to_owned());
|
|
}
|
|
|
|
fn record_css_modulepreload_failure_for_owner(&mut self, key: &ModuleMapKey) {
|
|
if key.kind() != ModuleKind::Css {
|
|
return;
|
|
}
|
|
self._context_host
|
|
.borrow_mut()
|
|
.record_css_module_failure_for_url(key.url());
|
|
}
|
|
|
|
pub(crate) fn module_graph_fetched_source_or_csp_error(
|
|
&mut self,
|
|
load_id: u64,
|
|
fetched_source: ModuleGraphFetchedSource,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
) -> std::result::Result<ModuleGraphFetchedSource, ModuleLoadError> {
|
|
if fetched_source.redirected() {
|
|
let redirect_status =
|
|
crate::content_security_policy::ContentSecurityPolicyRedirectStatus::FollowedRedirect;
|
|
let request = module_fetch_csp_request(fetch_metadata);
|
|
if let Some(violation) = self
|
|
.document_runtime
|
|
.script_element_request_csp_report_only_violation_with_redirect_status(
|
|
fetched_source.final_url(),
|
|
redirect_status,
|
|
request,
|
|
)
|
|
{
|
|
self.queue_content_security_policy_violation_event_best_effort(&violation);
|
|
}
|
|
if let Some(violation) = self
|
|
.document_runtime
|
|
.script_element_request_csp_violation_with_redirect_status(
|
|
fetched_source.final_url(),
|
|
redirect_status,
|
|
request,
|
|
)
|
|
{
|
|
self.queue_content_security_policy_violation_event_best_effort(&violation);
|
|
return Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
format!(
|
|
"native module graph fetch completion {load_id} blocked by document Content Security Policy for `{}`",
|
|
violation.blocked_uri
|
|
),
|
|
));
|
|
}
|
|
}
|
|
Ok(fetched_source)
|
|
}
|
|
|
|
pub(crate) fn csp_blocked_module_fetch_error_for_owner(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
) -> Option<ModuleLoadError> {
|
|
if !matches!(key.kind(), ModuleKind::JavaScript | ModuleKind::WebAssembly) {
|
|
return None;
|
|
}
|
|
let violation = self
|
|
.document_runtime
|
|
.script_element_request_csp_violation_with_request(
|
|
key.url(),
|
|
module_fetch_csp_request(fetch_metadata),
|
|
)?;
|
|
self.queue_content_security_policy_violation_event_best_effort(&violation);
|
|
Some(ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
format!(
|
|
"Refused to load module `{}` because it violates the document Content Security Policy directive `{}`",
|
|
key.url(),
|
|
violation.effective_directive
|
|
),
|
|
))
|
|
}
|
|
|
|
pub(crate) fn dispatch_module_fetch_csp_report_only_violation_for_owner(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
) {
|
|
if !matches!(key.kind(), ModuleKind::JavaScript | ModuleKind::WebAssembly) {
|
|
return;
|
|
}
|
|
if let Some(violation) = self
|
|
.document_runtime
|
|
.script_element_request_csp_report_only_violation_with_request(
|
|
key.url(),
|
|
module_fetch_csp_request(fetch_metadata),
|
|
)
|
|
{
|
|
self.queue_content_security_policy_violation_event_best_effort(&violation);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(super) fn warn_on_module_graph_fetch_metadata_mismatch(
|
|
&mut self,
|
|
completion: &ModuleGraphFetchCompletion,
|
|
expected_requester: ModuleGraphFetchRequester,
|
|
expected_ordering: ModuleGraphFetchOrdering,
|
|
) {
|
|
if completion.requester == expected_requester && completion.ordering == expected_ordering {
|
|
return;
|
|
}
|
|
self.record_runtime_warning(format_args!(
|
|
"module graph fetch completion {} metadata mismatch: got requester={:?} ordering={:?}, expected requester={:?} ordering={:?}",
|
|
completion.load_id,
|
|
completion.requester,
|
|
completion.ordering,
|
|
expected_requester,
|
|
expected_ordering
|
|
));
|
|
}
|
|
|
|
pub(crate) fn note_module_script_evaluation_suspended_for_owner(
|
|
&mut self,
|
|
evaluation: ModuleScriptEvaluationContinuation,
|
|
) {
|
|
match evaluation.script_continuation.completion_owner() {
|
|
ModuleScriptCompletionOwner::Parser => {
|
|
self.queue_main_parser_module_evaluation_work(evaluation)
|
|
}
|
|
ModuleScriptCompletionOwner::Runtime => {
|
|
let owner_id = evaluation
|
|
.script_continuation
|
|
.dynamic_script_owner_id()
|
|
.expect("runtime-owned module evaluation should carry dynamic owner id");
|
|
self.note_runtime_owned_module_script_evaluation_suspended(
|
|
owner_id,
|
|
Box::new(evaluation),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mark_module_evaluation_reaction_fulfilled_for_owner(
|
|
&mut self,
|
|
reaction_id: u64,
|
|
) -> Option<DocumentModuleReactionUpdate> {
|
|
let parser_update = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.mark_parser_module_evaluation_fulfilled(
|
|
reaction_id,
|
|
parser_module_evaluation_continuation_into_ready_action,
|
|
)
|
|
.map(DocumentModuleReactionUpdate::ParserOwned);
|
|
parser_update.or_else(|| {
|
|
self.mark_runtime_owned_module_script_evaluation_fulfilled(reaction_id)
|
|
.map(DocumentModuleReactionUpdate::RuntimeOwned)
|
|
})
|
|
}
|
|
|
|
fn mark_module_evaluation_reaction_rejected_for_owner(
|
|
&mut self,
|
|
reaction_id: u64,
|
|
reason: String,
|
|
error_value: Option<ScriptErrorValue>,
|
|
) -> Option<DocumentModuleReactionUpdate> {
|
|
let parser_update = self
|
|
.document_runtime
|
|
.parser_module_document_scripts_mut()
|
|
.mark_parser_module_evaluation_rejected(
|
|
reaction_id,
|
|
reason.clone(),
|
|
error_value,
|
|
parser_module_evaluation_continuation_into_ready_action,
|
|
)
|
|
.map(DocumentModuleReactionUpdate::ParserOwned);
|
|
parser_update.or_else(|| {
|
|
self.mark_runtime_owned_module_script_evaluation_rejected(
|
|
reaction_id,
|
|
reason,
|
|
error_value,
|
|
)
|
|
.map(DocumentModuleReactionUpdate::RuntimeOwned)
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn has_pending_parser_owned_module_script(&self) -> bool {
|
|
let Some(owner) = self.current_main_document_task_owner() else {
|
|
return false;
|
|
};
|
|
self._context_host
|
|
.borrow()
|
|
.current_main_document_has_parser_deferred_script_load_delay(owner)
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
pub(crate) fn release_main_parser_deferred_script_load_delay(
|
|
&mut self,
|
|
owner: FrameDocumentTaskOwner,
|
|
token: crate::frame_owner_model::DocumentLoadDelayTokenId,
|
|
) -> bool {
|
|
let released = self
|
|
._context_host
|
|
.borrow_mut()
|
|
.release_main_parser_deferred_script_load_delay(owner, token);
|
|
tracing::debug!(
|
|
?owner,
|
|
?token,
|
|
released,
|
|
"settled main parser-deferred lifecycle ownership"
|
|
);
|
|
released
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn has_pending_parser_owned_module_fetch(&self) -> bool {
|
|
self.document_runtime
|
|
.parser_module_scripts()
|
|
.has_pending_fetch(self.document_runtime.parser_module_document_scripts())
|
|
|| self
|
|
.document_runtime
|
|
.has_native_module_script_fetch_waiters()
|
|
}
|
|
|
|
pub(crate) fn note_module_script_graph_waits_suspended_for_owner(
|
|
&mut self,
|
|
load_ids: Vec<u64>,
|
|
joined_clients: Vec<moli_module_script_tree::SingleModuleClientToken>,
|
|
continuation: ModuleScriptContinuation,
|
|
) {
|
|
match continuation.completion_owner() {
|
|
ModuleScriptCompletionOwner::Parser => self
|
|
.document_runtime
|
|
.parser_module_scripts_mut()
|
|
.insert_parser_pending_waits(load_ids, joined_clients, continuation),
|
|
ModuleScriptCompletionOwner::Runtime => {
|
|
let owner_id = continuation
|
|
.dynamic_script_owner_id()
|
|
.expect("runtime-owned module graph fetch should carry dynamic owner id");
|
|
self.note_runtime_owned_module_script_graph_fetch_suspended(
|
|
owner_id,
|
|
load_ids,
|
|
joined_clients,
|
|
Box::new(continuation),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn note_or_restore_module_script_graph_waits_for_owner(
|
|
&mut self,
|
|
load_ids: Vec<u64>,
|
|
joined_clients: Vec<moli_module_script_tree::SingleModuleClientToken>,
|
|
continuation: ModuleScriptContinuation,
|
|
) {
|
|
if load_ids.is_empty() && joined_clients.is_empty() {
|
|
self.restore_module_script_graph_pending_continuation_for_owner(continuation);
|
|
return;
|
|
}
|
|
self.note_module_script_graph_waits_suspended_for_owner(
|
|
load_ids,
|
|
joined_clients,
|
|
continuation,
|
|
);
|
|
}
|
|
|
|
pub(crate) fn suspend_and_schedule_module_script_graph_fetches_for_owner(
|
|
&mut self,
|
|
continuation: ModuleScriptContinuation,
|
|
job: NativeModuleGraphJob,
|
|
fetches: Vec<ModuleScriptGraphFetchContinuation>,
|
|
joined_clients: Vec<moli_module_script_tree::SingleModuleClientToken>,
|
|
trace_message: &'static str,
|
|
) {
|
|
#[derive(Clone, Copy)]
|
|
enum FetchScheduleOwner {
|
|
Parser(
|
|
crate::document_script_scheduler::ParserPendingScriptId<
|
|
crate::module_script_continuation::MainParserDocumentOwner,
|
|
>,
|
|
),
|
|
Runtime {
|
|
document_owner: FrameDocumentTaskOwner,
|
|
dynamic_script_owner_id: crate::dynamic_script_owner::DynamicScriptOwnerId,
|
|
},
|
|
}
|
|
|
|
let completion_owner = continuation.completion_owner();
|
|
let fetch_schedule_owner = match completion_owner {
|
|
ModuleScriptCompletionOwner::Parser => FetchScheduleOwner::Parser(
|
|
continuation
|
|
.parser_pending_script_id()
|
|
.expect("parser module continuation must retain its PendingScript owner"),
|
|
),
|
|
ModuleScriptCompletionOwner::Runtime => FetchScheduleOwner::Runtime {
|
|
document_owner: self.current_main_document_task_owner().expect(
|
|
"runtime-created module graph fetch must start with a current main Document owner",
|
|
),
|
|
dynamic_script_owner_id: continuation.dynamic_script_owner_id().expect(
|
|
"runtime-created module continuation must retain its dynamic script owner",
|
|
),
|
|
},
|
|
};
|
|
let mut load_ids = Vec::with_capacity(fetches.len());
|
|
let mut scheduled = Vec::with_capacity(fetches.len());
|
|
for fetch in fetches {
|
|
let request = fetch.request().clone();
|
|
let load_id = self
|
|
.document_runtime
|
|
.suspend_native_module_script_fetch(fetch);
|
|
load_ids.push(load_id);
|
|
scheduled.push((load_id, request));
|
|
}
|
|
let continuation = continuation.with_pending_graph_fetches(job, load_ids.first().copied());
|
|
tracing::debug!(
|
|
url = %continuation.script.url,
|
|
completion_owner = ?continuation.completion_owner(),
|
|
dynamic_script_owner_id = ?continuation.dynamic_script_owner_id(),
|
|
fetch_count = load_ids.len(),
|
|
joined_fetch_count = joined_clients.len(),
|
|
trace_message,
|
|
"module script graph fetch waits installed"
|
|
);
|
|
self.note_or_restore_module_script_graph_waits_for_owner(
|
|
load_ids,
|
|
joined_clients,
|
|
continuation,
|
|
);
|
|
let document_loader = self
|
|
.document_runtime
|
|
.current_document_resource_loader()
|
|
.expect("main module graph requires the committed Document resource authority");
|
|
for (load_id, request) in scheduled {
|
|
match fetch_schedule_owner {
|
|
FetchScheduleOwner::Parser(pending_script_id) => {
|
|
self.resource_scheduler()
|
|
.schedule_main_parser_module_graph_fetch(
|
|
document_loader.clone(),
|
|
crate::page_resource_completion::MainParserModuleGraphFetchTarget::new(
|
|
pending_script_id,
|
|
load_id,
|
|
),
|
|
request,
|
|
self.document_runtime.document_url().clone(),
|
|
);
|
|
}
|
|
FetchScheduleOwner::Runtime {
|
|
document_owner,
|
|
dynamic_script_owner_id,
|
|
} => {
|
|
self.resource_scheduler()
|
|
.schedule_main_runtime_module_graph_fetch(
|
|
document_loader.clone(),
|
|
crate::page_resource_completion::MainRuntimeModuleGraphFetchTarget::new(
|
|
document_owner,
|
|
dynamic_script_owner_id,
|
|
load_id,
|
|
),
|
|
request,
|
|
self.document_runtime.document_url().clone(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn handle_module_script_graph_advance_for_owner(
|
|
&mut self,
|
|
advance: ModuleScriptContinuationGraphAdvance,
|
|
) -> NativeModuleOwnerActions {
|
|
match advance {
|
|
ModuleScriptContinuationGraphAdvance::Ready(script_continuation) => {
|
|
if script_continuation.completed_graph.is_some() {
|
|
tracing::debug!(
|
|
url = %script_continuation.script.url,
|
|
completion_owner = ?script_continuation.completion_owner(),
|
|
active_fetch_load_id = ?script_continuation.active_fetch_load_id(),
|
|
"module script graph completed after fetch"
|
|
);
|
|
}
|
|
self.note_module_script_graph_ready_for_owner(*script_continuation);
|
|
NativeModuleOwnerActions::empty()
|
|
}
|
|
ModuleScriptContinuationGraphAdvance::NeedFetches {
|
|
continuation,
|
|
mut job,
|
|
fetches,
|
|
} => {
|
|
let joined_clients = job.take_pending_joined_clients();
|
|
self.suspend_and_schedule_module_script_graph_fetches_for_owner(
|
|
*continuation,
|
|
*job,
|
|
fetches,
|
|
joined_clients,
|
|
"module script graph requested parallel fetches",
|
|
);
|
|
NativeModuleOwnerActions::empty()
|
|
}
|
|
ModuleScriptContinuationGraphAdvance::Failed {
|
|
continuation,
|
|
error,
|
|
} => {
|
|
self.clear_pending_module_script_fetches_for_script(
|
|
continuation.script.node_id,
|
|
&error,
|
|
);
|
|
self.notify_module_script_graph_failure_for_owner(*continuation, error)
|
|
.map(|(continuation, error)| {
|
|
NativeModuleOwnerActions::from_runtime_module_failure(continuation, error)
|
|
})
|
|
.unwrap_or_else(NativeModuleOwnerActions::empty)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_runtime_module_script_graph_advance_for_dynamic_owner(
|
|
&mut self,
|
|
advance: ModuleScriptContinuationGraphAdvance,
|
|
) -> NativeModuleOwnerActions {
|
|
match advance {
|
|
ModuleScriptContinuationGraphAdvance::Ready(script_continuation) => {
|
|
NativeModuleOwnerActions::from_ready_module_script(*script_continuation)
|
|
}
|
|
ModuleScriptContinuationGraphAdvance::NeedFetches {
|
|
continuation,
|
|
mut job,
|
|
fetches,
|
|
} => {
|
|
let joined_clients = job.take_pending_joined_clients();
|
|
self.suspend_and_schedule_module_script_graph_fetches_for_owner(
|
|
*continuation,
|
|
*job,
|
|
fetches,
|
|
joined_clients,
|
|
"runtime module script graph requested parallel fetches",
|
|
);
|
|
NativeModuleOwnerActions::empty()
|
|
}
|
|
ModuleScriptContinuationGraphAdvance::Failed {
|
|
continuation,
|
|
error,
|
|
} => {
|
|
self.clear_runtime_owned_module_script_graph_waits_for_owner(&continuation, &error);
|
|
NativeModuleOwnerActions::from_runtime_module_failure(*continuation, error)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn restore_module_script_graph_pending_continuation_for_owner(
|
|
&mut self,
|
|
continuation: ModuleScriptContinuation,
|
|
) {
|
|
match continuation.completion_owner() {
|
|
ModuleScriptCompletionOwner::Parser => self
|
|
.document_runtime
|
|
.parser_module_scripts_mut()
|
|
.restore_pending_continuation(continuation),
|
|
ModuleScriptCompletionOwner::Runtime => {
|
|
let owner_id = continuation.dynamic_script_owner_id().expect(
|
|
"runtime-owned module graph continuation should carry dynamic owner id",
|
|
);
|
|
let restored = self.restore_runtime_owned_module_script_graph_pending_continuation(
|
|
owner_id,
|
|
Box::new(continuation),
|
|
);
|
|
debug_assert!(
|
|
restored,
|
|
"runtime-owned module graph pending continuation should restore into owner state"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn note_module_script_graph_ready_for_owner(
|
|
&mut self,
|
|
continuation: ModuleScriptContinuation,
|
|
) {
|
|
match continuation.completion_owner() {
|
|
ModuleScriptCompletionOwner::Parser => {
|
|
self.queue_main_module_script_graph_ready_work(continuation);
|
|
}
|
|
ModuleScriptCompletionOwner::Runtime => {
|
|
let owner_id = continuation
|
|
.dynamic_script_owner_id()
|
|
.expect("runtime-owned module continuation should carry dynamic owner id");
|
|
let ready = self
|
|
.note_runtime_owned_module_script_graph_ready(owner_id, Box::new(continuation));
|
|
debug_assert!(
|
|
ready,
|
|
"dynamic owner should accept runtime-owned ready graph continuation"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn notify_module_script_graph_failure_for_owner(
|
|
&mut self,
|
|
continuation: ModuleScriptContinuation,
|
|
error: ModuleLoadError,
|
|
) -> Option<(ModuleScriptContinuation, ModuleLoadError)> {
|
|
match continuation.completion_owner() {
|
|
ModuleScriptCompletionOwner::Parser => {
|
|
self.queue_main_parser_module_graph_failure_work(ParserModuleScriptFailure {
|
|
continuation,
|
|
error,
|
|
});
|
|
None
|
|
}
|
|
ModuleScriptCompletionOwner::Runtime => Some((continuation, error)),
|
|
}
|
|
}
|
|
|
|
fn complete_parser_owned_module_script_graph_fetch_result(
|
|
&mut self,
|
|
load_id: u64,
|
|
result: std::result::Result<ModuleGraphFetchedSource, String>,
|
|
) -> Result<Option<ModuleScriptGraphFetchResume>> {
|
|
let Some(active_tree) = self
|
|
.document_runtime
|
|
.parser_module_scripts_mut()
|
|
.take_active_tree_for_fetch_completion(load_id)
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
let Some(graph_continuation) = self
|
|
.document_runtime
|
|
.take_inflight_native_module_script_fetch(load_id)
|
|
else {
|
|
self.record_runtime_warning(format_args!(
|
|
"module script continuation {load_id} had no graph fetch continuation"
|
|
));
|
|
self.document_runtime
|
|
.parser_module_scripts_mut()
|
|
.restore_active_tree_for_fetch(load_id, active_tree);
|
|
return Ok(Some(
|
|
ModuleScriptGraphFetchResume::RestoredMissingGraphContinuation,
|
|
));
|
|
};
|
|
let source = match result {
|
|
Ok(fetched_source) => self.module_graph_fetched_source_or_csp_error(
|
|
load_id,
|
|
fetched_source,
|
|
graph_continuation.request().fetch_metadata(),
|
|
),
|
|
Err(error) => Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
format!("native module script fetch completion {load_id} failed: {error}"),
|
|
)),
|
|
};
|
|
let result = active_tree.finish_fetch_into_graph(self, graph_continuation, source);
|
|
Ok(Some(ModuleScriptGraphFetchResume::finished(result)))
|
|
}
|
|
|
|
pub(crate) fn apply_current_main_parser_module_graph_fetch_completion(
|
|
&mut self,
|
|
authorization: crate::runtime::AuthorizedCurrentMainParserModuleGraphFetchCompletion,
|
|
) -> Result<()> {
|
|
let completion = authorization.into_completion();
|
|
let target = completion.target();
|
|
assert!(
|
|
self.main_parser_module_graph_fetch_target_is_current(target),
|
|
"authorized main parser module terminal must retain its exact Document fetch"
|
|
);
|
|
let result = completion.into_result();
|
|
if self.current_main_parser_module_graph_fetch_target(target.load_id()) != Some(target) {
|
|
return self.complete_shared_module_map_fetch_result(target.load_id(), result);
|
|
}
|
|
let resume = self
|
|
.complete_parser_owned_module_script_graph_fetch_result(target.load_id(), result)?
|
|
.expect("active main parser module terminal must retain its PendingScript fetch");
|
|
let actions = self.handle_module_script_graph_fetch_resume_for_owner(resume)?;
|
|
let (ready_scripts, ready_evaluations, runtime_failures) = actions.into_parts();
|
|
assert!(
|
|
ready_scripts.is_empty() && ready_evaluations.is_empty() && runtime_failures.is_empty(),
|
|
"parser-owned module fetch application must publish follow-up work through the parser owner, not inline runtime actions"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn complete_runtime_owned_module_script_graph_fetch_result(
|
|
&mut self,
|
|
load_id: u64,
|
|
result: std::result::Result<ModuleGraphFetchedSource, String>,
|
|
) -> Result<Option<ModuleScriptGraphFetchResume>> {
|
|
let Some(script_continuation) =
|
|
self.take_runtime_owned_module_script_graph_pending_fetch(load_id)
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
let Some(graph_continuation) = self
|
|
.document_runtime
|
|
.take_inflight_native_module_script_fetch(load_id)
|
|
else {
|
|
self.record_runtime_warning(format_args!(
|
|
"module script continuation {load_id} had no graph fetch continuation"
|
|
));
|
|
self.note_module_script_graph_waits_suspended_for_owner(
|
|
vec![load_id],
|
|
Vec::new(),
|
|
script_continuation,
|
|
);
|
|
return Ok(Some(
|
|
ModuleScriptGraphFetchResume::RestoredMissingGraphContinuation,
|
|
));
|
|
};
|
|
|
|
let source = match result {
|
|
Ok(fetched_source) => self.module_graph_fetched_source_or_csp_error(
|
|
load_id,
|
|
fetched_source,
|
|
graph_continuation.request().fetch_metadata(),
|
|
),
|
|
Err(error) => Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
format!("native module script fetch completion {load_id} failed: {error}"),
|
|
)),
|
|
};
|
|
let result =
|
|
script_continuation.finish_fetch_into_resumed_graph(self, graph_continuation, source);
|
|
Ok(Some(ModuleScriptGraphFetchResume::finished(result)))
|
|
}
|
|
|
|
pub(crate) fn apply_current_main_runtime_module_graph_fetch_completion(
|
|
&mut self,
|
|
authorization: crate::runtime::AuthorizedCurrentMainRuntimeModuleGraphFetchCompletion,
|
|
) -> Result<NativeModuleOwnerActions> {
|
|
let completion = authorization.into_completion();
|
|
let target = completion.target();
|
|
assert!(
|
|
self.main_runtime_module_graph_fetch_target_is_current(target),
|
|
"authorized main runtime module terminal must retain its exact Document fetch"
|
|
);
|
|
let result = completion.into_result();
|
|
if self.current_main_runtime_module_graph_fetch_target(target.load_id()) != Some(target) {
|
|
self.complete_shared_module_map_fetch_result(target.load_id(), result)?;
|
|
return Ok(NativeModuleOwnerActions::empty());
|
|
}
|
|
let resume = self
|
|
.complete_runtime_owned_module_script_graph_fetch_result(target.load_id(), result)?
|
|
.expect("active main runtime module terminal must retain its dynamic-script fetch");
|
|
self.handle_module_script_graph_fetch_resume_for_owner(resume)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn complete_current_main_dynamic_import_graph_fetch_result(
|
|
&mut self,
|
|
target: crate::page_resource_completion::MainDynamicImportGraphFetchTarget,
|
|
result: std::result::Result<ModuleGraphFetchedSource, String>,
|
|
) -> Result<NativeModuleOwnerActions> {
|
|
self.complete_current_main_dynamic_import_graph_fetch_result_with_body(
|
|
target,
|
|
result,
|
|
&mut ScriptVmCheckpointingMainNativeModuleTaskBody,
|
|
)
|
|
}
|
|
|
|
fn complete_current_main_dynamic_import_graph_fetch_result_with_body<Body>(
|
|
&mut self,
|
|
target: crate::page_resource_completion::MainDynamicImportGraphFetchTarget,
|
|
result: std::result::Result<ModuleGraphFetchedSource, String>,
|
|
body: &mut Body,
|
|
) -> Result<NativeModuleOwnerActions>
|
|
where
|
|
Body: ScriptVmMainNativeModuleTaskBody,
|
|
{
|
|
let inflight = self
|
|
.document_runtime
|
|
.take_inflight_native_dynamic_module_import_fetch(target.load_id())
|
|
.expect("authorized main dynamic-import terminal must retain its resolver fetch claim");
|
|
assert_eq!(
|
|
inflight.owner(),
|
|
target.import_owner(),
|
|
"dynamic-import resolver claim must match the authorized exact import owner"
|
|
);
|
|
let source = match result {
|
|
Ok(fetched_source) => self.module_graph_fetched_source_or_csp_error(
|
|
target.load_id(),
|
|
fetched_source,
|
|
inflight.fetch_metadata(),
|
|
),
|
|
Err(error) => Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Fetch,
|
|
format!(
|
|
"native dynamic import fetch completion {} failed: {error}",
|
|
target.load_id()
|
|
),
|
|
)),
|
|
};
|
|
let finish = self.finish_native_dynamic_module_inflight_fetch(inflight, source);
|
|
let mut terminal_followup =
|
|
self.dynamic_module_fetch_finish_to_owner_actions_with_body(finish, body)?;
|
|
let mut owner_actions = NativeModuleOwnerActions::empty();
|
|
while self.has_ready_native_module_owner_actions() {
|
|
let (actions, followup) =
|
|
self.drain_ready_native_module_owner_actions_with_body(body)?;
|
|
owner_actions.merge(actions);
|
|
terminal_followup.merge(followup);
|
|
}
|
|
// `terminal_followup` describes already-published child/module-owner
|
|
// work. The stable sources own that work; this exact Page terminal only
|
|
// returns main runtime actions to its caller.
|
|
let _ = terminal_followup;
|
|
Ok(owner_actions)
|
|
}
|
|
|
|
pub(crate) fn resume_parser_owned_module_script_joined_fetch(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
client: NativeModuleScriptSingleModuleClient,
|
|
) -> Option<ModuleScriptGraphResumeResult> {
|
|
let client_token = client.token();
|
|
let active_tree = self
|
|
.document_runtime
|
|
.parser_module_scripts_mut()
|
|
.take_active_tree_for_joined_client(client_token)?;
|
|
Some(active_tree.finish_joined_fetch_into_graph(
|
|
self,
|
|
chromium_module_key(key),
|
|
client_token,
|
|
))
|
|
}
|
|
|
|
pub(crate) fn resume_runtime_owned_module_script_joined_fetch(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
client: NativeModuleScriptSingleModuleClient,
|
|
) -> Option<ModuleScriptGraphResumeResult> {
|
|
let client_token = client.token();
|
|
let script_continuation =
|
|
self.take_runtime_owned_module_script_graph_pending_joined_client(client_token)?;
|
|
let result = script_continuation.finish_joined_fetch_into_resumed_graph(
|
|
self,
|
|
chromium_module_key(key),
|
|
client_token,
|
|
);
|
|
Some(result)
|
|
}
|
|
|
|
fn resume_module_script_fetch_join_waiter(
|
|
&mut self,
|
|
key: &ModuleMapKey,
|
|
client: NativeModuleScriptSingleModuleClient,
|
|
) -> Option<ModuleScriptGraphResumeResult> {
|
|
let client_token = client.token();
|
|
if let Some(result) = self.resume_parser_owned_module_script_joined_fetch(key, client) {
|
|
return Some(result);
|
|
}
|
|
if let Some(result) = self.resume_runtime_owned_module_script_joined_fetch(key, client) {
|
|
return Some(result);
|
|
}
|
|
self.record_runtime_warning(format_args!(
|
|
"module script joined fetch client {:?} had no graph continuation",
|
|
client_token
|
|
));
|
|
None
|
|
}
|
|
|
|
pub(crate) fn compile_native_module_record(
|
|
&mut self,
|
|
key: ModuleMapKey,
|
|
source: &ModuleSource,
|
|
source_url: &Url,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> {
|
|
let context_ptr = self.native_module_default_context_ptr();
|
|
self.compile_native_module_record_in_context(
|
|
context_ptr,
|
|
key,
|
|
source,
|
|
source_url,
|
|
fetch_metadata,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn compile_native_module_record_for_frame_realm(
|
|
&mut self,
|
|
realm_id: FrameRealmId,
|
|
key: ModuleMapKey,
|
|
source: &ModuleSource,
|
|
source_url: &Url,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> {
|
|
let context_ptr = self.frame_realm_context_ptr(realm_id).map_err(|error| {
|
|
ModuleLoadError::new(
|
|
ModuleLoadStage::Compile,
|
|
format!("failed to find FrameRealm {realm_id:?} for module compile: {error}"),
|
|
)
|
|
})?;
|
|
self.compile_native_module_record_in_context(
|
|
context_ptr,
|
|
key,
|
|
source,
|
|
source_url,
|
|
fetch_metadata,
|
|
)
|
|
}
|
|
|
|
fn native_module_default_context_ptr(&self) -> *const v8::Global<v8::Context> {
|
|
&self.page_default_context as *const _
|
|
}
|
|
|
|
fn compile_native_module_record_in_context(
|
|
&mut self,
|
|
context_ptr: *const v8::Global<v8::Context>,
|
|
key: ModuleMapKey,
|
|
source: &ModuleSource,
|
|
source_url: &Url,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> {
|
|
match key.kind() {
|
|
ModuleKind::JavaScript => {
|
|
let origin = source.origin();
|
|
let Some(source) = source.text_source() else {
|
|
return Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Compile,
|
|
format!("javascript module `{source_url}` did not retain text source"),
|
|
));
|
|
};
|
|
self.compile_javascript_module_record_in_context(
|
|
context_ptr,
|
|
key,
|
|
source,
|
|
source_url,
|
|
fetch_metadata,
|
|
origin,
|
|
)
|
|
}
|
|
ModuleKind::Json | ModuleKind::Css => {
|
|
let Some(source) = source.text_source() else {
|
|
return Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Compile,
|
|
format!("synthetic text module `{source_url}` did not retain text source"),
|
|
));
|
|
};
|
|
self.compile_synthetic_module_record_in_context(
|
|
context_ptr,
|
|
key,
|
|
source,
|
|
source_url,
|
|
)
|
|
}
|
|
ModuleKind::WebAssembly => {
|
|
let Some(bytes) = source.binary_source() else {
|
|
return Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Compile,
|
|
format!("WebAssembly module `{source_url}` did not retain binary source"),
|
|
));
|
|
};
|
|
self.compile_wasm_module_record_in_context(context_ptr, key, bytes, source_url)
|
|
}
|
|
ModuleKind::ModulePreloadText => Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Compile,
|
|
format!("modulepreload text `{source_url}` is not a module graph record"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn compile_javascript_module_record_in_context(
|
|
&mut self,
|
|
context_ptr: *const v8::Global<v8::Context>,
|
|
key: ModuleMapKey,
|
|
source: &str,
|
|
source_url: &Url,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
source_origin: Option<&crate::document_module_graph::ModuleSourceOrigin>,
|
|
) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> {
|
|
let mut exception_id = None;
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
|
|
let source_string = v8_string(&scope, source)
|
|
.ok_or_else(|| anyhow::anyhow!("failed to allocate v8 module source string"))?;
|
|
let origin = create_module_script_origin(
|
|
&mut scope,
|
|
source_url.as_str(),
|
|
fetch_metadata,
|
|
source_origin,
|
|
);
|
|
let mut compiler_source =
|
|
v8::script_compiler::Source::new(source_string, Some(&origin));
|
|
let module = v8::script_compiler::compile_module(&scope, &mut compiler_source)
|
|
.ok_or_else(|| {
|
|
if let Some(exception) = scope.exception() {
|
|
match retain_module_exception(&mut scope, exception) {
|
|
Ok(id) => exception_id = Some(id),
|
|
Err(error) => return error,
|
|
}
|
|
}
|
|
let exception = scope
|
|
.exception()
|
|
.and_then(|exception| exception.to_detail_string(&scope))
|
|
.map(|message| message.to_rust_string_lossy(&scope))
|
|
.or_else(|| {
|
|
scope.message().map(|message| {
|
|
message.get(&scope).to_rust_string_lossy(&scope)
|
|
})
|
|
})
|
|
.unwrap_or_else(|| {
|
|
format!(
|
|
"unknown compile exception (caught={}, can_continue={}, terminated={})",
|
|
scope.has_caught(),
|
|
scope.can_continue(),
|
|
scope.has_terminated()
|
|
)
|
|
});
|
|
anyhow::anyhow!(
|
|
"v8 failed to compile native module `{source_url}`: {}",
|
|
exception
|
|
)
|
|
})?;
|
|
let requests = collect_module_requests(&mut scope, module)?;
|
|
let identity = module_identity_hash_from_v8_module(module);
|
|
let compiled_module = v8::Global::new(scope.as_ref(), module);
|
|
Ok((
|
|
ModuleRecordEntry::new(key, compiled_module, requests),
|
|
identity,
|
|
))
|
|
})
|
|
.map_err(|error| {
|
|
let message = error.to_string();
|
|
let mut load_error = ModuleLoadError::new(ModuleLoadStage::Compile, message.clone());
|
|
if let Some(exception_id) = exception_id {
|
|
load_error = load_error.with_exception_id(exception_id);
|
|
}
|
|
if message.starts_with("v8 failed to compile WebAssembly module `") {
|
|
load_error
|
|
.with_error_constructor(ScriptErrorConstructorKind::WebAssemblyCompileError)
|
|
} else {
|
|
load_error.with_error_constructor(ScriptErrorConstructorKind::SyntaxError)
|
|
}
|
|
})
|
|
}
|
|
|
|
fn compile_wasm_module_record_in_context(
|
|
&mut self,
|
|
context_ptr: *const v8::Global<v8::Context>,
|
|
key: ModuleMapKey,
|
|
bytes: &[u8],
|
|
source_url: &Url,
|
|
) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> {
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let scope = try_catch.init();
|
|
let prepared = prepare_wasm_module_record(&scope, bytes)?.ok_or_else(|| {
|
|
let exception = v8_exception_message_or(
|
|
&scope,
|
|
scope.exception(),
|
|
"unknown wasm compile exception",
|
|
);
|
|
anyhow::anyhow!(
|
|
"v8 failed to compile WebAssembly module `{source_url}`: {exception}"
|
|
)
|
|
})?;
|
|
let requests = if prepared.has_reserved_name_link_error {
|
|
Vec::new()
|
|
} else {
|
|
wasm_module_requests_for_imports(prepared.record.imports())
|
|
};
|
|
let export_name_refs = prepared
|
|
.record
|
|
.exports()
|
|
.iter()
|
|
.map(|export| export.name())
|
|
.collect::<Vec<_>>();
|
|
let module_name = v8_string(&scope, source_url.as_str()).ok_or_else(|| {
|
|
anyhow::anyhow!("failed to allocate WebAssembly synthetic module name")
|
|
})?;
|
|
let export_names = export_name_refs
|
|
.iter()
|
|
.map(|name| {
|
|
v8_string(&scope, name)
|
|
.ok_or_else(|| anyhow::anyhow!("failed to allocate wasm export name"))
|
|
})
|
|
.collect::<Result<Vec<_>>>()?;
|
|
let module = v8::Module::create_synthetic_module(
|
|
&scope,
|
|
module_name,
|
|
&export_names,
|
|
wasm_synthetic_module_evaluation_steps,
|
|
);
|
|
let identity = module_identity_hash_from_v8_module(module);
|
|
let compiled_module = v8::Global::new(scope.as_ref(), module);
|
|
Ok((
|
|
ModuleRecordEntry::new_with_wasm_module(
|
|
key,
|
|
compiled_module,
|
|
requests,
|
|
prepared.record,
|
|
),
|
|
identity,
|
|
))
|
|
})
|
|
.map_err(|error| {
|
|
ModuleLoadError::new(ModuleLoadStage::Compile, error.to_string())
|
|
.with_error_constructor(ScriptErrorConstructorKind::WebAssemblyCompileError)
|
|
})
|
|
}
|
|
|
|
fn compile_synthetic_module_record_in_context(
|
|
&mut self,
|
|
context_ptr: *const v8::Global<v8::Context>,
|
|
key: ModuleMapKey,
|
|
source: &str,
|
|
source_url: &Url,
|
|
) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> {
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
|
|
let module_name = v8_string(&scope, source_url.as_str()).ok_or_else(|| {
|
|
anyhow::anyhow!("failed to allocate v8 synthetic module name")
|
|
})?;
|
|
let default_export = v8_string(&scope, "default")
|
|
.ok_or_else(|| anyhow::anyhow!("failed to allocate synthetic export name"))?;
|
|
let module = v8::Module::create_synthetic_module(
|
|
&scope,
|
|
module_name,
|
|
&[default_export],
|
|
synthetic_text_module_evaluation_steps,
|
|
);
|
|
let identity = module_identity_hash_from_v8_module(module);
|
|
let evaluation_source = crate::module_runtime::SyntheticTextModuleSource::register(
|
|
&mut scope,
|
|
module,
|
|
key.clone(),
|
|
source,
|
|
);
|
|
let compiled_module = v8::Global::new(scope.as_ref(), module);
|
|
let entry = ModuleRecordEntry::new(key, compiled_module, Vec::new())
|
|
.with_synthetic_text_module_source(evaluation_source);
|
|
Ok((entry, identity))
|
|
})
|
|
.map_err(|error| ModuleLoadError::new(ModuleLoadStage::Compile, error.to_string()))
|
|
}
|
|
|
|
pub(crate) fn instantiate_native_module_graph(
|
|
&mut self,
|
|
graph: &crate::module_runtime::ModuleGraphHandle,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
let context_ptr = self.native_module_default_context_ptr();
|
|
self.instantiate_native_module_graph_in_context(context_ptr, graph)
|
|
}
|
|
|
|
fn instantiate_native_module_graph_in_context(
|
|
&mut self,
|
|
context_ptr: *const v8::Global<v8::Context>,
|
|
graph: &crate::module_runtime::ModuleGraphHandle,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
let root_entry = graph.root_entry;
|
|
let graph_urls = graph
|
|
.entries
|
|
.iter()
|
|
.map(|entry_id| self.document_runtime.native_module_entry_url(*entry_id))
|
|
.collect::<Vec<_>>();
|
|
let has_wasm_entry = graph.entries.iter().any(|entry_id| {
|
|
self.document_runtime
|
|
.native_module_wasm_record(*entry_id)
|
|
.is_some()
|
|
});
|
|
let document_modulator = self.document_runtime.native_document_modulator_ptr();
|
|
let root_module = self
|
|
.document_runtime
|
|
.native_compiled_module(root_entry)
|
|
.ok_or_else(|| {
|
|
ModuleLoadError::new(
|
|
ModuleLoadStage::Instantiate,
|
|
format!("native root module entry {root_entry:?} is not compiled"),
|
|
)
|
|
})?;
|
|
let mut caught_error_constructor = None;
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
|
|
let root_module = v8::Local::new(&scope, &root_module);
|
|
let _resolver_scope = ResolverScopeGuard::new(document_modulator);
|
|
match root_module.instantiate_module2(
|
|
&scope,
|
|
resolve_static_module_callback,
|
|
resolve_static_source_callback,
|
|
) {
|
|
Some(true) => Ok(()),
|
|
Some(false) => Err(anyhow::anyhow!("v8 reported module instantiate failure")),
|
|
None => {
|
|
let exception = match scope.exception() {
|
|
Some(exception) => {
|
|
caught_error_constructor =
|
|
script_error_constructor_kind_from_value(&mut scope, exception);
|
|
exception
|
|
.to_string(&scope)
|
|
.map(|message| message.to_rust_string_lossy(&scope))
|
|
.unwrap_or_else(|| "unknown instantiate exception".to_owned())
|
|
}
|
|
None => "unknown instantiate exception".to_owned(),
|
|
};
|
|
Err(anyhow::anyhow!(
|
|
"{}",
|
|
canonical_native_module_instantiate_error(&exception, &graph_urls)
|
|
))
|
|
}
|
|
}
|
|
})
|
|
.map_err(|error| {
|
|
native_module_instantiate_load_error(
|
|
error.to_string(),
|
|
caught_error_constructor,
|
|
has_wasm_entry,
|
|
)
|
|
})?;
|
|
self.document_runtime
|
|
.mark_native_module_instantiated(root_entry);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn evaluate_native_module_graph(
|
|
&mut self,
|
|
root_entry: crate::module_runtime::ModuleEntryId,
|
|
) -> std::result::Result<Option<v8::Global<v8::Promise>>, ModuleLoadError> {
|
|
let context_ptr = self.native_module_default_context_ptr();
|
|
self.evaluate_native_module_graph_with_owner_in_context(
|
|
context_ptr,
|
|
root_entry,
|
|
NativeModuleEvaluationOwner::Script,
|
|
)
|
|
.map(|result| result.promise)
|
|
}
|
|
|
|
pub(crate) fn evaluate_native_dynamic_module_graph(
|
|
&mut self,
|
|
root_entry: crate::module_runtime::ModuleEntryId,
|
|
) -> std::result::Result<NativeDynamicModuleEvaluation, ModuleLoadError> {
|
|
let context_ptr = self.native_module_default_context_ptr();
|
|
self.evaluate_native_module_graph_with_owner_in_context(
|
|
context_ptr,
|
|
root_entry,
|
|
NativeModuleEvaluationOwner::DynamicImport,
|
|
)
|
|
.map(|result| NativeDynamicModuleEvaluation {
|
|
target: DynamicModuleEvaluationTarget::new(root_entry, result.module),
|
|
promise: result.promise,
|
|
})
|
|
}
|
|
|
|
fn start_native_dynamic_module_import_evaluation(
|
|
&mut self,
|
|
graph: ModuleGraphHandle,
|
|
) -> std::result::Result<DynamicModuleImportEvaluationStart, ModuleLoadError> {
|
|
match self
|
|
.document_runtime
|
|
.native_module_entry_state(graph.root_entry)
|
|
{
|
|
ModuleMapEntryState::Compiled => {
|
|
self.instantiate_native_module_graph(&graph)?;
|
|
self.start_native_module_graph_evaluation(graph.root_entry)
|
|
}
|
|
ModuleMapEntryState::Instantiated | ModuleMapEntryState::Evaluating => {
|
|
self.start_native_module_graph_evaluation(graph.root_entry)
|
|
}
|
|
ModuleMapEntryState::Evaluated => {
|
|
let module = self
|
|
.document_runtime
|
|
.native_compiled_module(graph.root_entry)
|
|
.ok_or_else(|| {
|
|
ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
"native dynamic import root was evaluated without a compiled module",
|
|
)
|
|
})?;
|
|
Ok(DynamicModuleImportEvaluationStart::Completed(
|
|
DynamicModuleEvaluationTarget::new(graph.root_entry, module),
|
|
))
|
|
}
|
|
ModuleMapEntryState::Fetching
|
|
| ModuleMapEntryState::Fetched
|
|
| ModuleMapEntryState::Failed => Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
"native dynamic import root was not ready to evaluate",
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn start_native_module_graph_evaluation(
|
|
&mut self,
|
|
root_entry: crate::module_runtime::ModuleEntryId,
|
|
) -> std::result::Result<DynamicModuleImportEvaluationStart, ModuleLoadError> {
|
|
let evaluation = self.evaluate_native_dynamic_module_graph(root_entry)?;
|
|
let (target, promise) = evaluation.into_parts();
|
|
let Some(promise) = promise else {
|
|
return Ok(DynamicModuleImportEvaluationStart::Completed(target));
|
|
};
|
|
Ok(DynamicModuleImportEvaluationStart::Pending { target, promise })
|
|
}
|
|
|
|
fn evaluate_native_module_graph_with_owner_in_context(
|
|
&mut self,
|
|
context_ptr: *const v8::Global<v8::Context>,
|
|
root_entry: crate::module_runtime::ModuleEntryId,
|
|
owner: NativeModuleEvaluationOwner,
|
|
) -> std::result::Result<NativeModuleEvaluationResult, ModuleLoadError> {
|
|
let root_module = self
|
|
.document_runtime
|
|
.native_compiled_module(root_entry)
|
|
.ok_or_else(|| {
|
|
ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
format!("native root module entry {root_entry:?} is not compiled"),
|
|
)
|
|
})?;
|
|
let document_owner_before_evaluation =
|
|
self.current_main_document_task_owner().ok_or_else(|| {
|
|
ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
"native module evaluation has no current main Document owner",
|
|
)
|
|
})?;
|
|
self.document_runtime
|
|
.mark_native_module_evaluating(root_entry);
|
|
let promise = self
|
|
.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
|
|
let root_module = v8::Local::new(&scope, &root_module);
|
|
let Some(value) = crate::script_execution::evaluate_module(&mut scope, root_module)
|
|
else {
|
|
let error = scope
|
|
.exception()
|
|
.map(|exception| {
|
|
native_module_evaluation_exception_error(
|
|
&mut scope,
|
|
exception,
|
|
"v8 failed to evaluate native module graph",
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
"v8 failed to evaluate native module graph: unknown exception",
|
|
)
|
|
});
|
|
return Ok(Err(error));
|
|
};
|
|
let promise = v8::Local::<v8::Promise>::try_from(value).ok();
|
|
if owner == NativeModuleEvaluationOwner::DynamicImport
|
|
&& let Some(promise) = promise
|
|
{
|
|
// Dynamic import owns the module-evaluation promise itself.
|
|
// Return it before a checkpoint so the import rejection
|
|
// handler is attached before V8 can report an unhandled
|
|
// rejection for synchronously rejected module evaluation.
|
|
match promise.state() {
|
|
v8::PromiseState::Fulfilled => return Ok(Ok(None)),
|
|
v8::PromiseState::Rejected | v8::PromiseState::Pending => {
|
|
let promise = v8::Global::new(scope.as_ref(), promise);
|
|
return Ok(Ok(Some(promise)));
|
|
}
|
|
}
|
|
}
|
|
// Script-element evaluation consumes rejection itself, either
|
|
// below or through its retained TLA continuation. Claim that
|
|
// responsibility before cleanup can notify rejected promises.
|
|
// Dynamic import returned above and owns its own reaction.
|
|
if let Some(promise) = promise {
|
|
promise.mark_as_handled();
|
|
}
|
|
if let Err(error) = Self::perform_microtask_checkpoints(&mut scope, None) {
|
|
return Ok(Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
error.to_string(),
|
|
)));
|
|
}
|
|
if root_module.get_status() == v8::ModuleStatus::Errored {
|
|
let exception = root_module.get_exception();
|
|
return Ok(Err(native_module_evaluation_exception_error(
|
|
&mut scope,
|
|
exception,
|
|
"native module graph evaluation rejected",
|
|
)));
|
|
}
|
|
if let Some(promise) = promise {
|
|
match promise.state() {
|
|
v8::PromiseState::Fulfilled => return Ok(Ok(None)),
|
|
v8::PromiseState::Rejected => {
|
|
let result = promise.result(&scope);
|
|
return Ok(Err(native_module_evaluation_exception_error(
|
|
&mut scope,
|
|
result,
|
|
"native module graph evaluation rejected",
|
|
)));
|
|
}
|
|
v8::PromiseState::Pending => {
|
|
let promise = v8::Global::new(scope.as_ref(), promise);
|
|
return Ok(Ok(Some(promise)));
|
|
}
|
|
}
|
|
}
|
|
Ok(Ok(None))
|
|
})
|
|
.map_err(|error| {
|
|
ModuleLoadError::new(ModuleLoadStage::Evaluate, error.to_string())
|
|
})??;
|
|
if promise.is_none()
|
|
&& self.current_main_document_task_owner() == Some(document_owner_before_evaluation)
|
|
{
|
|
self.document_runtime
|
|
.mark_native_module_evaluated(root_entry);
|
|
}
|
|
Ok(NativeModuleEvaluationResult {
|
|
module: root_module,
|
|
promise,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn attach_native_dynamic_module_import_reactions(
|
|
&mut self,
|
|
request: PendingDynamicModuleImport,
|
|
target: DynamicModuleEvaluationTarget,
|
|
promise: v8::Global<v8::Promise>,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
let context = request.context().clone();
|
|
let document_owner = request.owner();
|
|
let reaction_id = self
|
|
.document_runtime
|
|
.reserve_native_dynamic_module_evaluation_reaction(request, target);
|
|
let attach_result = self
|
|
.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Local::new(scope, &context);
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let reaction_id_value = v8::BigInt::new_from_u64(scope, reaction_id);
|
|
let data = NativeDynamicModuleReactionDataDeclaration {
|
|
reaction_id: reaction_id_value,
|
|
}
|
|
.bind(scope)
|
|
.map_err(|error| {
|
|
anyhow::anyhow!("failed to create native dynamic import reaction data: {error}")
|
|
})?;
|
|
let on_fulfilled =
|
|
v8::Function::builder(native_dynamic_module_reaction_fulfilled_callback)
|
|
.data(data.into())
|
|
.build(scope)
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"failed to create native dynamic import success reaction"
|
|
)
|
|
})?;
|
|
let on_rejected =
|
|
v8::Function::builder(native_dynamic_module_reaction_rejected_callback)
|
|
.data(data.into())
|
|
.build(scope)
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"failed to create native dynamic import failure reaction"
|
|
)
|
|
})?;
|
|
let promise = v8::Local::new(scope, &promise);
|
|
promise
|
|
.then2(scope, on_fulfilled, on_rejected)
|
|
.map(|_| ())
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("failed to attach native dynamic import reactions")
|
|
})?;
|
|
// `then` does not invoke the dynamic-import reaction inline. It
|
|
// queues a V8 promise-reaction microtask, even when the module
|
|
// evaluation promise is already settled. Run the browser-style
|
|
// checkpoint for this owner-lane task so the fulfilled/rejected
|
|
// callback can transfer the dynamic import result into
|
|
// DocumentRuntime before the driver decides whether more module
|
|
// work is ready.
|
|
Self::perform_microtask_checkpoints(scope, None)
|
|
});
|
|
if let Err(error) = attach_result {
|
|
if let Some(reaction) = self
|
|
.document_runtime
|
|
.take_native_dynamic_module_evaluation_reaction(reaction_id, document_owner)
|
|
{
|
|
let (request, _) = reaction.into_parts();
|
|
let _ = self.reject_native_dynamic_module_import(request, &error.to_string());
|
|
}
|
|
return Err(ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
error.to_string(),
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn attach_native_module_script_evaluation_reactions(
|
|
&mut self,
|
|
reaction_id: u64,
|
|
promise: v8::Global<v8::Promise>,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
let document_owner = self.current_main_document_task_owner().ok_or_else(|| {
|
|
ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
"main module evaluation reaction has no current Document owner",
|
|
)
|
|
})?;
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Local::new(scope, &self.page_default_context);
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let reaction_id_value = v8::BigInt::new_from_u64(scope, reaction_id);
|
|
let data = NativeModuleScriptReactionDataDeclaration {
|
|
module_script_reaction_id: reaction_id_value,
|
|
scheduler_lane_id: v8::BigInt::new_from_u64(
|
|
scope,
|
|
document_owner.scheduler_lane_id.0,
|
|
),
|
|
local_window_id: v8::BigInt::new_from_u64(
|
|
scope,
|
|
document_owner.local_window_id.0,
|
|
),
|
|
document_id: v8::BigInt::new_from_u64(scope, document_owner.document_id.0),
|
|
}
|
|
.bind(scope)
|
|
.map_err(|error| {
|
|
anyhow::anyhow!("failed to create native module script reaction data: {error}")
|
|
})?;
|
|
let on_fulfilled =
|
|
v8::Function::builder(native_module_script_reaction_fulfilled_callback)
|
|
.data(data.into())
|
|
.build(scope)
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"failed to create native module script success reaction"
|
|
)
|
|
})?;
|
|
let on_rejected =
|
|
v8::Function::builder(native_module_script_reaction_rejected_callback)
|
|
.data(data.into())
|
|
.build(scope)
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"failed to create native module script failure reaction"
|
|
)
|
|
})?;
|
|
let promise = v8::Local::new(scope, &promise);
|
|
promise
|
|
.then2(scope, on_fulfilled, on_rejected)
|
|
.map(|_| ())
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("failed to attach native module script reactions")
|
|
})?;
|
|
// `then` schedules the module-script evaluation reaction as a
|
|
// V8 microtask, even if the evaluation promise is already
|
|
// settled. Chromium runs module evaluation's error handling
|
|
// before the script runner advances to later pending scripts;
|
|
// drain this owner-lane checkpoint now so TLA rejection cannot
|
|
// be observed after a later dynamic module.
|
|
Self::perform_microtask_checkpoints(scope, None)
|
|
})
|
|
.map_err(|error| ModuleLoadError::new(ModuleLoadStage::Evaluate, error.to_string()))
|
|
}
|
|
|
|
pub(crate) fn attach_child_parser_module_script_evaluation_reactions(
|
|
&mut self,
|
|
context_ptr: *const v8::Global<v8::Context>,
|
|
document_owner: FrameDocumentTaskOwner,
|
|
realm_id: FrameRealmId,
|
|
reaction_id: u64,
|
|
promise: v8::Global<v8::Promise>,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let reaction_id_value = v8::BigInt::new_from_u64(scope, reaction_id);
|
|
let data = NativeChildModuleScriptReactionDataDeclaration {
|
|
module_script_reaction_id: reaction_id_value,
|
|
scheduler_lane_id: v8::BigInt::new_from_u64(
|
|
scope,
|
|
document_owner.scheduler_lane_id.0,
|
|
),
|
|
local_window_id: v8::BigInt::new_from_u64(
|
|
scope,
|
|
document_owner.local_window_id.0,
|
|
),
|
|
document_id: v8::BigInt::new_from_u64(scope, document_owner.document_id.0),
|
|
realm_id: v8::BigInt::new_from_i64(scope, realm_id.0),
|
|
}
|
|
.bind(scope)
|
|
.map_err(|error| {
|
|
anyhow::anyhow!("failed to create child parser module reaction data: {error}")
|
|
})?;
|
|
let on_fulfilled =
|
|
v8::Function::builder(child_parser_module_reaction_fulfilled_callback)
|
|
.data(data.into())
|
|
.build(scope)
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("failed to create child parser module success reaction")
|
|
})?;
|
|
let on_rejected =
|
|
v8::Function::builder(child_parser_module_reaction_rejected_callback)
|
|
.data(data.into())
|
|
.build(scope)
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("failed to create child parser module failure reaction")
|
|
})?;
|
|
let promise = v8::Local::new(scope, &promise);
|
|
promise
|
|
.then2(scope, on_fulfilled, on_rejected)
|
|
.map(|_| ())
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("failed to attach child parser module script reactions")
|
|
})?;
|
|
// Evaluation has just returned a genuinely pending promise
|
|
// after its algorithm-required checkpoint. Attaching the TLA
|
|
// observers is setup, not a second checkpoint boundary. The
|
|
// selected DocumentScriptReady task performs its ordinary
|
|
// task-end checkpoint after the script element load event.
|
|
Ok(())
|
|
})
|
|
.map_err(|error| ModuleLoadError::new(ModuleLoadStage::Evaluate, error.to_string()))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn resolve_native_dynamic_module_import(
|
|
&mut self,
|
|
request: PendingDynamicModuleImport,
|
|
target: &DynamicModuleEvaluationTarget,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Local::new(scope, request.context());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::Local::new(scope, request.resolver());
|
|
let root_module = v8::Local::new(scope, target.module());
|
|
let namespace = root_module.get_module_namespace();
|
|
let _ = resolver.resolve(scope, namespace);
|
|
Self::perform_microtask_checkpoints(scope, None)?;
|
|
Ok(())
|
|
})
|
|
.map_err(|error: anyhow::Error| {
|
|
ModuleLoadError::new(ModuleLoadStage::Evaluate, error.to_string())
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn resolve_native_dynamic_module_source_import(
|
|
&mut self,
|
|
request: PendingDynamicModuleImport,
|
|
root_entry: crate::module_runtime::ModuleEntryId,
|
|
) -> std::result::Result<NativeDynamicModuleSourceImportResolution, ModuleLoadError> {
|
|
let Some(wasm_record) = self.document_runtime.native_module_wasm_record(root_entry) else {
|
|
let error = ModuleLoadError::new(
|
|
ModuleLoadStage::Resolve,
|
|
format!(
|
|
"source-phase dynamic import `{}` is not a WebAssembly module",
|
|
request.specifier()
|
|
),
|
|
)
|
|
.with_error_constructor(ScriptErrorConstructorKind::SyntaxError);
|
|
self.reject_native_dynamic_module_import_with_error(request, &error)?;
|
|
return Ok(NativeDynamicModuleSourceImportResolution::Rejected);
|
|
};
|
|
self.resolve_native_dynamic_module_source_import_with_wasm_record(request, wasm_record)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn resolve_native_dynamic_module_source_import_with_wasm_record(
|
|
&mut self,
|
|
request: PendingDynamicModuleImport,
|
|
wasm_record: WasmModuleRecord,
|
|
) -> std::result::Result<NativeDynamicModuleSourceImportResolution, ModuleLoadError> {
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Local::new(scope, request.context());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::Local::new(scope, request.resolver());
|
|
let Some(source) = wasm_record.source_module(scope) else {
|
|
let exception = v8_string(scope, "failed to materialize WebAssembly source")
|
|
.map(|message| v8::Exception::type_error(scope, message))
|
|
.unwrap_or_else(|| v8::undefined(scope).into());
|
|
let _ = resolver.reject(scope, exception);
|
|
Self::perform_microtask_checkpoints(scope, None)?;
|
|
return Ok(NativeDynamicModuleSourceImportResolution::Rejected);
|
|
};
|
|
let _ = resolver.resolve(scope, source.into());
|
|
Self::perform_microtask_checkpoints(scope, None)?;
|
|
Ok(NativeDynamicModuleSourceImportResolution::Resolved)
|
|
})
|
|
.map_err(|error: anyhow::Error| {
|
|
ModuleLoadError::new(ModuleLoadStage::Evaluate, error.to_string())
|
|
})
|
|
}
|
|
|
|
pub(crate) fn reject_native_dynamic_module_import(
|
|
&mut self,
|
|
request: PendingDynamicModuleImport,
|
|
message: &str,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
self.reject_native_dynamic_module_import_and_checkpoint(
|
|
request,
|
|
&ModuleLoadError::new(ModuleLoadStage::Fetch, message),
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn reject_native_dynamic_module_import_with_error(
|
|
&mut self,
|
|
request: PendingDynamicModuleImport,
|
|
error: &ModuleLoadError,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
self.reject_native_dynamic_module_import_and_checkpoint(request, error)
|
|
}
|
|
|
|
fn reject_native_dynamic_module_import_and_checkpoint(
|
|
&mut self,
|
|
request: PendingDynamicModuleImport,
|
|
error: &ModuleLoadError,
|
|
) -> std::result::Result<(), ModuleLoadError> {
|
|
self.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Local::new(scope, request.context());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::Local::new(scope, request.resolver());
|
|
let exception = module_load_error_value(scope, error)?;
|
|
let _ = resolver.reject(scope, exception);
|
|
Self::perform_microtask_checkpoints(scope, None)?;
|
|
Ok(())
|
|
})
|
|
.map_err(|error: anyhow::Error| {
|
|
ModuleLoadError::new(ModuleLoadStage::Evaluate, error.to_string())
|
|
})
|
|
}
|
|
|
|
pub(crate) fn module_reaction_target_is_current(
|
|
&self,
|
|
target: crate::page_task_queue::RendererPageModuleReactionTarget,
|
|
) -> bool {
|
|
match target {
|
|
crate::page_task_queue::RendererPageModuleReactionTarget::DocumentModuleScript {
|
|
document_owner,
|
|
} => self.current_main_document_task_owner() == Some(document_owner),
|
|
crate::page_task_queue::RendererPageModuleReactionTarget::ChildParserModule {
|
|
document_owner,
|
|
realm_id,
|
|
} => self.child_parser_module_route_task_is_current(document_owner, realm_id),
|
|
crate::page_task_queue::RendererPageModuleReactionTarget::DynamicModuleImport {
|
|
import_owner,
|
|
} => self.dynamic_module_import_owner_is_current(import_owner),
|
|
}
|
|
}
|
|
|
|
/// Apply one module reaction whose exact target has already been accepted
|
|
/// by the Page owner arbiter.
|
|
pub(crate) fn apply_current_page_module_reaction(
|
|
&mut self,
|
|
authorization: crate::runtime::AuthorizedCurrentPageModuleReaction,
|
|
) -> Result<PageModuleReactionApplication> {
|
|
let reaction = authorization.into_task().into_event();
|
|
let application = match reaction {
|
|
RendererPageModuleReactionEvent::DocumentModuleScriptEvaluationFulfilled {
|
|
reaction_id,
|
|
..
|
|
} => self
|
|
.apply_native_module_script_evaluation_fulfilled(reaction_id)
|
|
.map(PageModuleReactionApplication::module_state_updated),
|
|
RendererPageModuleReactionEvent::DocumentModuleScriptEvaluationRejected {
|
|
reaction_id,
|
|
reason,
|
|
error_value,
|
|
..
|
|
} => self
|
|
.apply_native_module_script_evaluation_rejected(reaction_id, reason, error_value)
|
|
.map(PageModuleReactionApplication::module_state_updated),
|
|
RendererPageModuleReactionEvent::ChildParserModuleEvaluationFulfilled {
|
|
reaction_id,
|
|
..
|
|
} => (self.apply_child_parser_module_evaluation_fulfilled(reaction_id) > 0).then_some(
|
|
PageModuleReactionApplication::module_state_updated(
|
|
PageModuleReactionFollowup::None,
|
|
),
|
|
),
|
|
RendererPageModuleReactionEvent::ChildParserModuleEvaluationRejected {
|
|
reaction_id,
|
|
reason,
|
|
error_value,
|
|
..
|
|
} => (self.apply_child_parser_module_evaluation_rejected(
|
|
reaction_id,
|
|
reason,
|
|
error_value,
|
|
) > 0)
|
|
.then_some(PageModuleReactionApplication::module_state_updated(
|
|
PageModuleReactionFollowup::None,
|
|
)),
|
|
RendererPageModuleReactionEvent::DynamicModuleEvaluationFulfilled {
|
|
import_owner,
|
|
reaction_id,
|
|
} => self
|
|
.apply_native_dynamic_module_evaluation_fulfilled(import_owner, reaction_id)
|
|
.map_err(|error| anyhow::anyhow!(error.message().to_owned()))?
|
|
.then_some(PageModuleReactionApplication::dynamic_import_promise_settled()),
|
|
RendererPageModuleReactionEvent::DynamicModuleEvaluationRejected {
|
|
import_owner,
|
|
reaction_id,
|
|
reason,
|
|
} => self
|
|
.apply_native_dynamic_module_evaluation_rejected(import_owner, reaction_id, reason)
|
|
.map_err(|error| anyhow::anyhow!(error.message().to_owned()))?
|
|
.then_some(PageModuleReactionApplication::dynamic_import_promise_settled()),
|
|
};
|
|
Ok(application.unwrap_or(PageModuleReactionApplication::NoPendingReaction))
|
|
}
|
|
|
|
pub(crate) fn discard_stale_page_module_reaction(
|
|
&mut self,
|
|
reaction: &RendererPageModuleReactionEvent,
|
|
) {
|
|
let (import_owner, reaction_id) = match reaction {
|
|
RendererPageModuleReactionEvent::DynamicModuleEvaluationFulfilled {
|
|
import_owner,
|
|
reaction_id,
|
|
}
|
|
| RendererPageModuleReactionEvent::DynamicModuleEvaluationRejected {
|
|
import_owner,
|
|
reaction_id,
|
|
..
|
|
} => (*import_owner, *reaction_id),
|
|
RendererPageModuleReactionEvent::DocumentModuleScriptEvaluationFulfilled { .. }
|
|
| RendererPageModuleReactionEvent::DocumentModuleScriptEvaluationRejected { .. }
|
|
| RendererPageModuleReactionEvent::ChildParserModuleEvaluationFulfilled { .. }
|
|
| RendererPageModuleReactionEvent::ChildParserModuleEvaluationRejected { .. } => {
|
|
return;
|
|
}
|
|
};
|
|
let reaction_claimed = self
|
|
.document_runtime
|
|
.take_native_dynamic_module_evaluation_reaction(reaction_id, import_owner)
|
|
.is_some();
|
|
self.record_runtime_warning(format_args!(
|
|
"ignored stale module reaction: owner={import_owner:?} reaction_id={reaction_id} reaction_claimed={reaction_claimed}"
|
|
));
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn has_page_module_reaction_for_executor_test(&self) -> bool {
|
|
self._page_task_residence_for_executor_test
|
|
.as_ref()
|
|
.expect("module-reaction executor fixture must retain its production Page source")
|
|
.task_sources()
|
|
.has_module_reaction_for_executor_test()
|
|
}
|
|
|
|
/// Publish a document-module reaction without reserving a local reaction
|
|
/// record. This is only for Page authorization tests that need a concrete
|
|
/// spent or stale source ticket.
|
|
#[cfg(test)]
|
|
pub(crate) fn queue_missing_document_module_reaction_for_test(&mut self, reaction_id: u64) {
|
|
let document_owner = self
|
|
.current_main_document_task_owner()
|
|
.expect("module reaction fixture requires a current main Document owner");
|
|
self._context_host
|
|
.borrow_mut()
|
|
.queue_document_module_script_evaluation_fulfilled(document_owner, reaction_id);
|
|
}
|
|
|
|
/// Apply only the body of one production module-reaction task in a
|
|
/// low-level ScriptVm semantic fixture.
|
|
///
|
|
/// This helper deliberately does not model selected-task completion.
|
|
/// Page-root admission, task-end checkpoint ownership, and scheduler
|
|
/// liveness are covered through the PageVm selected-dispatcher test driver.
|
|
#[cfg(test)]
|
|
pub(crate) fn run_page_module_reaction_body_for_test(
|
|
&mut self,
|
|
) -> Result<Option<crate::page_task_queue::PageModuleReactionTargetEffect>> {
|
|
let source = self
|
|
._page_task_residence_for_executor_test
|
|
.as_ref()
|
|
.expect("module-reaction executor fixture must retain its production Page source")
|
|
.task_sources();
|
|
let Some(task) = source.take_module_reaction_for_executor_test() else {
|
|
return Ok(None);
|
|
};
|
|
if self.module_reaction_target_is_current(task.owner().target()) {
|
|
let application = self.apply_current_page_module_reaction(
|
|
crate::runtime::AuthorizedCurrentPageModuleReaction::new_for_executor_test(task),
|
|
)?;
|
|
return Ok(Some(match application {
|
|
PageModuleReactionApplication::Applied { current_effect, .. } => {
|
|
crate::page_task_queue::PageModuleReactionTargetEffect::AppliedToCurrentOwner(
|
|
current_effect,
|
|
)
|
|
}
|
|
PageModuleReactionApplication::NoPendingReaction => {
|
|
crate::page_task_queue::PageModuleReactionTargetEffect::DiscardedMissingReaction
|
|
}
|
|
}));
|
|
}
|
|
self.discard_stale_page_module_reaction(&task.into_event());
|
|
Ok(Some(
|
|
crate::page_task_queue::PageModuleReactionTargetEffect::IgnoredStaleOwner,
|
|
))
|
|
}
|
|
|
|
fn apply_native_module_script_evaluation_fulfilled(
|
|
&mut self,
|
|
reaction_id: u64,
|
|
) -> Option<PageModuleReactionFollowup> {
|
|
let update = self.mark_module_evaluation_reaction_fulfilled_for_owner(reaction_id)?;
|
|
let (root_entry, followup) = match update {
|
|
DocumentModuleReactionUpdate::ParserOwned(update) => (
|
|
update.root_entry(),
|
|
PageModuleReactionFollowup::main_parser_owned_evaluations(
|
|
update.queued_ready_action_count(),
|
|
),
|
|
),
|
|
DocumentModuleReactionUpdate::RuntimeOwned(update) => (
|
|
update.root_entry,
|
|
PageModuleReactionFollowup::RuntimeOwnedModuleContinuation,
|
|
),
|
|
};
|
|
self.document_runtime
|
|
.mark_native_module_evaluated(root_entry);
|
|
Some(followup)
|
|
}
|
|
|
|
fn apply_native_module_script_evaluation_rejected(
|
|
&mut self,
|
|
reaction_id: u64,
|
|
reason: String,
|
|
error_value: Option<ScriptErrorValue>,
|
|
) -> Option<PageModuleReactionFollowup> {
|
|
let update = self.mark_module_evaluation_reaction_rejected_for_owner(
|
|
reaction_id,
|
|
reason,
|
|
error_value,
|
|
)?;
|
|
Some(match update {
|
|
DocumentModuleReactionUpdate::ParserOwned(update) => {
|
|
PageModuleReactionFollowup::main_parser_owned_evaluations(
|
|
update.queued_ready_action_count(),
|
|
)
|
|
}
|
|
DocumentModuleReactionUpdate::RuntimeOwned(_) => {
|
|
PageModuleReactionFollowup::RuntimeOwnedModuleContinuation
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn apply_native_dynamic_module_evaluation_fulfilled(
|
|
&mut self,
|
|
import_owner: crate::module_runtime::DynamicModuleImportOwner,
|
|
reaction_id: u64,
|
|
) -> std::result::Result<bool, ModuleLoadError> {
|
|
let Some(reaction) = self
|
|
.document_runtime
|
|
.take_native_dynamic_module_evaluation_reaction(reaction_id, import_owner)
|
|
else {
|
|
return Ok(false);
|
|
};
|
|
let (request, target) = reaction.into_parts();
|
|
let child_owner_parts = request
|
|
.owner()
|
|
.child_parts()
|
|
.map(|(_child_handle, task_owner, realm_id)| (task_owner.document_owner(), realm_id));
|
|
if let Some((owner, realm_id)) = child_owner_parts {
|
|
self.mark_child_native_dynamic_module_evaluated(owner, realm_id, target.root_entry());
|
|
} else {
|
|
self.document_runtime
|
|
.mark_native_module_evaluated(target.root_entry());
|
|
}
|
|
// Commit the exact owner's module-map state before resolving the user
|
|
// Promise. Its reactions may replace the Document when the selected
|
|
// task dispatcher performs the task-end checkpoint.
|
|
self.resolve_native_dynamic_module_import_selected_task_body(request, &target)?;
|
|
Ok(true)
|
|
}
|
|
|
|
pub(crate) fn apply_native_dynamic_module_evaluation_rejected(
|
|
&mut self,
|
|
import_owner: crate::module_runtime::DynamicModuleImportOwner,
|
|
reaction_id: u64,
|
|
reason: v8::Global<v8::Value>,
|
|
) -> std::result::Result<bool, ModuleLoadError> {
|
|
let Some(reaction) = self
|
|
.document_runtime
|
|
.take_native_dynamic_module_evaluation_reaction(reaction_id, import_owner)
|
|
else {
|
|
return Ok(false);
|
|
};
|
|
let (request, _) = reaction.into_parts();
|
|
self.reject_native_dynamic_module_import_reaction_body(request, reason)?;
|
|
Ok(true)
|
|
}
|
|
}
|
|
|
|
fn wasm_module_requests_for_imports(imports: &[WasmImportRecord]) -> Vec<ModuleRequestRecord> {
|
|
wasm_evaluation_import_modules(imports)
|
|
.into_iter()
|
|
.map(|module| {
|
|
ModuleRequestRecord::new(
|
|
module,
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn native_dynamic_module_reaction_fulfilled_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some(reaction_id) = native_dynamic_module_reaction_data(scope, args.data()) else {
|
|
return;
|
|
};
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return;
|
|
};
|
|
unsafe { &mut *host_ptr }.queue_native_dynamic_module_evaluation_fulfilled(reaction_id);
|
|
}
|
|
|
|
fn native_dynamic_module_reaction_rejected_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some(reaction_id) = native_dynamic_module_reaction_data(scope, args.data()) else {
|
|
return;
|
|
};
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return;
|
|
};
|
|
let reason = args.get(0);
|
|
let reason = v8::Global::new(scope, reason);
|
|
unsafe { &mut *host_ptr }.queue_native_dynamic_module_evaluation_rejected(reaction_id, reason);
|
|
}
|
|
|
|
fn native_dynamic_module_reaction_data<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
data: v8::Local<'s, v8::Value>,
|
|
) -> Option<u64> {
|
|
let data = v8::Local::<v8::Object>::try_from(data).ok()?;
|
|
get_u64_reaction_data_slot(scope, data, DYNAMIC_MODULE_REACTION_ID_SLOT)
|
|
}
|
|
|
|
fn native_module_script_reaction_fulfilled_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some((document_owner, reaction_id)) =
|
|
native_module_script_reaction_data(scope, args.data())
|
|
else {
|
|
return;
|
|
};
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return;
|
|
};
|
|
unsafe { &mut *host_ptr }
|
|
.queue_document_module_script_evaluation_fulfilled(document_owner, reaction_id);
|
|
}
|
|
|
|
fn native_module_script_reaction_rejected_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some((document_owner, reaction_id)) =
|
|
native_module_script_reaction_data(scope, args.data())
|
|
else {
|
|
return;
|
|
};
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return;
|
|
};
|
|
let error = native_module_evaluation_exception_error(scope, args.get(0), "");
|
|
unsafe { &mut *host_ptr }.queue_document_module_script_evaluation_rejected(
|
|
document_owner,
|
|
reaction_id,
|
|
error.message().to_owned(),
|
|
error.error_value(),
|
|
);
|
|
}
|
|
|
|
fn child_parser_module_reaction_fulfilled_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some((document_owner, realm_id, reaction_id)) =
|
|
native_child_module_script_reaction_data(scope, args.data())
|
|
else {
|
|
return;
|
|
};
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return;
|
|
};
|
|
unsafe { &mut *host_ptr }.queue_child_parser_module_script_evaluation_fulfilled(
|
|
document_owner,
|
|
realm_id,
|
|
reaction_id,
|
|
);
|
|
}
|
|
|
|
fn child_parser_module_reaction_rejected_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some((document_owner, realm_id, reaction_id)) =
|
|
native_child_module_script_reaction_data(scope, args.data())
|
|
else {
|
|
return;
|
|
};
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return;
|
|
};
|
|
let error = native_module_evaluation_exception_error(scope, args.get(0), "");
|
|
unsafe { &mut *host_ptr }.queue_child_parser_module_script_evaluation_rejected(
|
|
document_owner,
|
|
realm_id,
|
|
reaction_id,
|
|
error.message().to_owned(),
|
|
error.error_value(),
|
|
);
|
|
}
|
|
|
|
fn native_module_script_reaction_data<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
data: v8::Local<'s, v8::Value>,
|
|
) -> Option<(FrameDocumentTaskOwner, u64)> {
|
|
let data = v8::Local::<v8::Object>::try_from(data).ok()?;
|
|
let reaction_id = get_u64_reaction_data_slot(scope, data, MODULE_SCRIPT_REACTION_ID_SLOT)?;
|
|
let document_owner = frame_document_owner_from_module_reaction_data(scope, data)?;
|
|
Some((document_owner, reaction_id))
|
|
}
|
|
|
|
fn native_child_module_script_reaction_data<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
data: v8::Local<'s, v8::Value>,
|
|
) -> Option<(FrameDocumentTaskOwner, FrameRealmId, u64)> {
|
|
let data = v8::Local::<v8::Object>::try_from(data).ok()?;
|
|
let reaction_id = get_u64_reaction_data_slot(scope, data, MODULE_SCRIPT_REACTION_ID_SLOT)?;
|
|
let document_owner = frame_document_owner_from_module_reaction_data(scope, data)?;
|
|
let realm_id = get_i64_reaction_data_slot(scope, data, MODULE_REACTION_REALM_ID_SLOT)?;
|
|
Some((document_owner, FrameRealmId(realm_id), reaction_id))
|
|
}
|
|
|
|
fn frame_document_owner_from_module_reaction_data<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
data: v8::Local<'s, v8::Object>,
|
|
) -> Option<FrameDocumentTaskOwner> {
|
|
let scheduler_lane_id =
|
|
get_u64_reaction_data_slot(scope, data, MODULE_REACTION_SCHEDULER_LANE_ID_SLOT)?;
|
|
let local_window_id =
|
|
get_u64_reaction_data_slot(scope, data, MODULE_REACTION_LOCAL_WINDOW_ID_SLOT)?;
|
|
let document_id = get_u64_reaction_data_slot(scope, data, MODULE_REACTION_DOCUMENT_ID_SLOT)?;
|
|
Some(FrameDocumentTaskOwner::new(
|
|
FrameSchedulerLaneId(scheduler_lane_id),
|
|
LocalWindowId(local_window_id),
|
|
DocumentId(document_id),
|
|
))
|
|
}
|
|
|
|
fn get_u64_reaction_data_slot<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
data: v8::Local<'s, v8::Object>,
|
|
slot: &'static str,
|
|
) -> Option<u64> {
|
|
let value = data.get(scope, v8str(scope, slot).into())?;
|
|
let value = v8::Local::<v8::BigInt>::try_from(value).ok()?;
|
|
let (value, lossless) = value.u64_value();
|
|
lossless.then_some(value)
|
|
}
|
|
|
|
fn get_i64_reaction_data_slot<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
data: v8::Local<'s, v8::Object>,
|
|
slot: &'static str,
|
|
) -> Option<i64> {
|
|
let value = data.get(scope, v8str(scope, slot).into())?;
|
|
let value = v8::Local::<v8::BigInt>::try_from(value).ok()?;
|
|
let (value, lossless) = value.i64_value();
|
|
lossless.then_some(value)
|
|
}
|
|
|
|
fn synthetic_text_module_evaluation_steps<'s>(
|
|
context: v8::Local<'s, v8::Context>,
|
|
module: v8::Local<'s, v8::Module>,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
v8::callback_scope!(unsafe scope, context);
|
|
let Some(record) =
|
|
crate::module_runtime::SyntheticTextModuleSource::for_module(context, module)
|
|
else {
|
|
return throw_synthetic_module_error(scope, "synthetic module source is not available");
|
|
};
|
|
let source = record.source();
|
|
match record.key().kind() {
|
|
ModuleKind::Json => evaluate_json_synthetic_module(scope, module, source),
|
|
ModuleKind::Css => {
|
|
evaluate_css_synthetic_module(scope, module, record.key().url().as_str(), source)
|
|
}
|
|
_ => throw_synthetic_module_error(scope, "unexpected synthetic text module kind"),
|
|
}
|
|
}
|
|
|
|
fn wasm_synthetic_module_evaluation_steps<'s>(
|
|
context: v8::Local<'s, v8::Context>,
|
|
module: v8::Local<'s, v8::Module>,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
v8::callback_scope!(unsafe scope, context);
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return throw_synthetic_module_error(scope, "synthetic module host is not available");
|
|
};
|
|
let Some(wasm_record) = (unsafe { &*host_ptr }).native_module_wasm_record_for(module) else {
|
|
return throw_synthetic_module_error(
|
|
scope,
|
|
"WebAssembly synthetic module record is not available",
|
|
);
|
|
};
|
|
evaluate_wasm_synthetic_module(scope, module, &wasm_record, |scope, import| {
|
|
wasm_import_value(scope, module, import)
|
|
})
|
|
}
|
|
|
|
fn evaluate_json_synthetic_module<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
module: v8::Local<'s, v8::Module>,
|
|
source: &str,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let Some(json_source) = v8_string(scope, source) else {
|
|
return throw_synthetic_module_error(scope, "failed to allocate JSON module source");
|
|
};
|
|
let value = v8::json::parse(scope, json_source)?;
|
|
set_synthetic_default_export(scope, module, value)
|
|
}
|
|
|
|
fn evaluate_css_synthetic_module<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
module: v8::Local<'s, v8::Module>,
|
|
url: &str,
|
|
source: &str,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let Some(sheet) =
|
|
crate::native_bridge::element::css_module_sheet_for_url(scope, url, Some(source))
|
|
else {
|
|
return throw_synthetic_module_error(scope, "failed to create CSS module sheet");
|
|
};
|
|
set_synthetic_default_export(scope, module, sheet.into())
|
|
}
|
|
|
|
fn set_synthetic_default_export<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
module: v8::Local<'s, v8::Module>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let export_name = v8str(scope, "default");
|
|
if module
|
|
.set_synthetic_module_export(scope, export_name, value)
|
|
.is_none_or(|ok| !ok)
|
|
{
|
|
return throw_synthetic_module_error(
|
|
scope,
|
|
"failed to set synthetic module default export",
|
|
);
|
|
}
|
|
Some(v8::undefined(scope).into())
|
|
}
|
|
|
|
fn wasm_import_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
referrer: v8::Local<'s, v8::Module>,
|
|
import: &WasmImportRecord,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return throw_wasm_link_error(scope, "wasm import host is not available");
|
|
};
|
|
let attributes = ModuleAttributesKey::empty();
|
|
let Some(dependency) = (unsafe { &*host_ptr }).native_resolved_dependency_module_for(
|
|
referrer,
|
|
import.module(),
|
|
&attributes,
|
|
) else {
|
|
return throw_wasm_link_error(scope, "wasm import dependency is not available");
|
|
};
|
|
let dependency = v8::Local::new(scope, &dependency);
|
|
ensure_dependency_module_namespace_ready(scope, dependency)?;
|
|
let dependency_wasm_record = (unsafe { &*host_ptr }).native_module_wasm_record_for(dependency);
|
|
wasm_dependency_export_value(
|
|
scope,
|
|
dependency,
|
|
dependency_wasm_record.as_ref(),
|
|
import.name(),
|
|
"failed to allocate wasm import export name",
|
|
"wasm import export is not available",
|
|
)
|
|
}
|
|
|
|
fn ensure_dependency_module_namespace_ready<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
module: v8::Local<'s, v8::Module>,
|
|
) -> Option<()> {
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
throw_synthetic_module_error(scope, "module dependency host is not available");
|
|
return None;
|
|
};
|
|
let document_modulator_ptr = (unsafe { &*host_ptr }).native_document_modulator_ptr();
|
|
let document_modulator = unsafe { &*document_modulator_ptr };
|
|
let mut dependency_modules_for =
|
|
|_: &mut v8::PinScope<'s, '_>, dependency: v8::Local<'s, v8::Module>| {
|
|
document_modulator.evaluation_dependency_modules_for(dependency)
|
|
};
|
|
ensure_wasm_dependency_module_namespace_ready(
|
|
scope,
|
|
module,
|
|
|scope: &mut v8::PinScope<'s, '_>, module: v8::Local<'s, v8::Module>| {
|
|
let _resolver_scope = ResolverScopeGuard::new(document_modulator_ptr);
|
|
match module.instantiate_module2(
|
|
scope,
|
|
resolve_static_module_callback,
|
|
resolve_static_source_callback,
|
|
) {
|
|
Some(true) => Some(()),
|
|
Some(false) => {
|
|
throw_synthetic_module_error(
|
|
scope,
|
|
"module dependency instantiate returned false",
|
|
);
|
|
None
|
|
}
|
|
None => {
|
|
preserve_current_v8_module_exception(scope);
|
|
None
|
|
}
|
|
}
|
|
},
|
|
&mut dependency_modules_for,
|
|
|scope| {
|
|
if ScriptVm::perform_microtask_checkpoints(scope, None).is_err() {
|
|
throw_synthetic_module_error(
|
|
scope,
|
|
"module dependency microtask checkpoint failed",
|
|
);
|
|
return None;
|
|
}
|
|
Some(())
|
|
},
|
|
WasmDependencyModuleMessages {
|
|
instantiating: "module dependency is still instantiating",
|
|
already_failed: "module dependency already failed",
|
|
evaluation_failed: "module dependency evaluation failed",
|
|
not_instantiated: "module dependency is not instantiated",
|
|
cyclic: "cyclic WebAssembly module evaluation through JavaScript dependencies is not supported yet",
|
|
graph_unavailable: "module dependency graph is not available",
|
|
pending: "module dependency evaluation is pending",
|
|
},
|
|
)
|
|
}
|
|
|
|
fn throw_synthetic_module_error<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
message: &str,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let message = v8_string(scope, message)?;
|
|
let exception = v8::Exception::type_error(scope, message);
|
|
scope.throw_exception(exception);
|
|
None
|
|
}
|
|
|
|
fn canonical_native_module_instantiate_error(exception: &str, graph_urls: &[Url]) -> String {
|
|
if let Some(error) = canonical_missing_export_link_error(exception, graph_urls) {
|
|
return error;
|
|
}
|
|
format!("v8 failed to instantiate native module graph: {exception}")
|
|
}
|
|
|
|
fn native_module_instantiate_load_error(
|
|
message: String,
|
|
caught_error_constructor: Option<ScriptErrorConstructorKind>,
|
|
has_wasm_entry: bool,
|
|
) -> ModuleLoadError {
|
|
let fallback_constructor = if has_wasm_entry {
|
|
ScriptErrorConstructorKind::WebAssemblyLinkError
|
|
} else {
|
|
ScriptErrorConstructorKind::SyntaxError
|
|
};
|
|
ModuleLoadError::new(ModuleLoadStage::Instantiate, message)
|
|
.with_error_constructor(caught_error_constructor.unwrap_or(fallback_constructor))
|
|
}
|
|
|
|
fn canonical_missing_export_link_error(exception: &str, graph_urls: &[Url]) -> Option<String> {
|
|
let module = quoted_value_after(exception, "The requested module ")?;
|
|
let export = quoted_value_after(exception, "does not provide an export named ")?;
|
|
let module = canonical_link_error_module_url(module, graph_urls);
|
|
Some(format!(
|
|
"ModuleLinkFailed: module `{module}` does not export `{export}`"
|
|
))
|
|
}
|
|
|
|
fn canonical_link_error_module_url(module: &str, graph_urls: &[Url]) -> String {
|
|
if Url::parse(module).is_ok() {
|
|
return module.to_owned();
|
|
}
|
|
let suffix = module.trim_start_matches("./");
|
|
graph_urls
|
|
.iter()
|
|
.find(|url| url.path().ends_with(suffix))
|
|
.map(ToString::to_string)
|
|
.unwrap_or_else(|| module.to_owned())
|
|
}
|
|
|
|
fn quoted_value_after<'a>(message: &'a str, marker: &str) -> Option<&'a str> {
|
|
let start = message.find(marker)? + marker.len();
|
|
let rest = message.get(start..)?.trim_start();
|
|
let quote = rest.chars().next()?;
|
|
if quote != '\'' && quote != '"' && quote != '`' {
|
|
return None;
|
|
}
|
|
let value_start = quote.len_utf8();
|
|
let rest = rest.get(value_start..)?;
|
|
let value_end = rest.find(quote)?;
|
|
rest.get(..value_end)
|
|
}
|
|
|
|
fn native_module_evaluation_exception_error(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
exception: v8::Local<'_, v8::Value>,
|
|
prefix: &str,
|
|
) -> ModuleLoadError {
|
|
let id = match retain_module_exception(scope, exception) {
|
|
Ok(id) => id,
|
|
Err(error) => {
|
|
return ModuleLoadError::new(
|
|
ModuleLoadStage::Evaluate,
|
|
format!("failed to retain module evaluation exception: {error}"),
|
|
);
|
|
}
|
|
};
|
|
// V8's internal diagnostic does not invoke author-defined toString or
|
|
// location getters. The diagnostic text never substitutes for the value.
|
|
let message = v8::Exception::create_message(scope, exception)
|
|
.get(scope)
|
|
.to_rust_string_lossy(scope);
|
|
let message = if prefix.is_empty() {
|
|
message
|
|
} else {
|
|
format!("{prefix}: {message}")
|
|
};
|
|
let error = ModuleLoadError::new(ModuleLoadStage::Evaluate, message).with_exception_id(id);
|
|
match script_error_constructor_kind_from_value(scope, exception) {
|
|
Some(error_constructor) => error.with_error_constructor(error_constructor),
|
|
None => error,
|
|
}
|
|
}
|
|
|
|
fn script_error_constructor_kind_from_value(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
value: v8::Local<'_, v8::Value>,
|
|
) -> Option<ScriptErrorConstructorKind> {
|
|
if !value.is_native_error() {
|
|
return None;
|
|
}
|
|
let object = v8::Local::<v8::Object>::try_from(value).ok()?;
|
|
let prototype = object.get_prototype(scope)?;
|
|
for candidate in [
|
|
ScriptErrorConstructorKind::SyntaxError,
|
|
ScriptErrorConstructorKind::WebAssemblyCompileError,
|
|
ScriptErrorConstructorKind::WebAssemblyLinkError,
|
|
ScriptErrorConstructorKind::Error,
|
|
] {
|
|
if let Some(candidate_prototype) = script_error_prototype(scope, candidate)
|
|
&& prototype.strict_equals(candidate_prototype)
|
|
{
|
|
return Some(candidate);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn script_error_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
constructor_kind: ScriptErrorConstructorKind,
|
|
message: v8::Local<'s, v8::String>,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
match constructor_kind {
|
|
ScriptErrorConstructorKind::Error => Some(v8::Exception::error(scope, message)),
|
|
ScriptErrorConstructorKind::SyntaxError => {
|
|
Some(v8::Exception::syntax_error(scope, message))
|
|
}
|
|
ScriptErrorConstructorKind::TypeError => Some(v8::Exception::type_error(scope, message)),
|
|
ScriptErrorConstructorKind::WebAssemblyCompileError => {
|
|
captured_webassembly_error_constructor(
|
|
scope,
|
|
ORIGINAL_WEBASSEMBLY_COMPILE_ERROR_CONSTRUCTOR_SLOT,
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.and_then(|constructor| constructor.new_instance(scope, &[message.into()]))
|
|
.map(v8::Local::<v8::Value>::from)
|
|
}
|
|
ScriptErrorConstructorKind::WebAssemblyLinkError => captured_webassembly_error_constructor(
|
|
scope,
|
|
ORIGINAL_WEBASSEMBLY_LINK_ERROR_CONSTRUCTOR_SLOT,
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
.and_then(|constructor| constructor.new_instance(scope, &[message.into()]))
|
|
.map(v8::Local::<v8::Value>::from),
|
|
}
|
|
}
|
|
|
|
fn script_error_prototype<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
constructor_kind: ScriptErrorConstructorKind,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let empty = v8::String::empty(scope);
|
|
let error = match constructor_kind {
|
|
ScriptErrorConstructorKind::Error => v8::Exception::error(scope, empty),
|
|
ScriptErrorConstructorKind::SyntaxError => v8::Exception::syntax_error(scope, empty),
|
|
ScriptErrorConstructorKind::TypeError => v8::Exception::type_error(scope, empty),
|
|
ScriptErrorConstructorKind::WebAssemblyCompileError => {
|
|
let constructor = captured_webassembly_error_constructor(
|
|
scope,
|
|
ORIGINAL_WEBASSEMBLY_COMPILE_ERROR_CONSTRUCTOR_SLOT,
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
|
|
constructor.new_instance(scope, &[])?.into()
|
|
}
|
|
ScriptErrorConstructorKind::WebAssemblyLinkError => {
|
|
let constructor = captured_webassembly_error_constructor(
|
|
scope,
|
|
ORIGINAL_WEBASSEMBLY_LINK_ERROR_CONSTRUCTOR_SLOT,
|
|
)
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
|
|
constructor.new_instance(scope, &[])?.into()
|
|
}
|
|
};
|
|
v8::Local::<v8::Object>::try_from(error)
|
|
.ok()?
|
|
.get_prototype(scope)
|
|
}
|
|
|
|
fn captured_webassembly_error_constructor<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
slot: &str,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let global = scope.get_current_context().global(scope);
|
|
get_private_value(scope, global, slot)
|
|
}
|
|
|
|
fn module_fetch_csp_request(
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
) -> crate::content_security_policy::ContentSecurityPolicyScriptElementRequest<'_> {
|
|
crate::content_security_policy::ContentSecurityPolicyScriptElementRequest {
|
|
nonce: fetch_metadata.nonce(),
|
|
integrity: fetch_metadata.integrity(),
|
|
parser_inserted: fetch_metadata.parser_inserted,
|
|
}
|
|
}
|
|
|
|
fn create_module_script_origin<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
resource_name: &str,
|
|
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
|
source_origin: Option<&crate::document_module_graph::ModuleSourceOrigin>,
|
|
) -> v8::ScriptOrigin<'s> {
|
|
let name = v8::String::new(
|
|
scope,
|
|
source_origin.map_or(resource_name, |origin| origin.url.as_str()),
|
|
)
|
|
.expect("v8 string allocation");
|
|
// Inline diagnostics identify the source document, while imports continue
|
|
// to resolve against the module's preparation-time base URL.
|
|
let base_url = Url::parse(resource_name).ok();
|
|
let host_defined_options = base_url.as_ref().and_then(|base_url| {
|
|
crate::util::script_host_defined_options_with_fetch_metadata(
|
|
scope,
|
|
base_url,
|
|
fetch_metadata.nonce(),
|
|
fetch_metadata.parser_inserted,
|
|
)
|
|
});
|
|
v8::ScriptOrigin::new(
|
|
scope,
|
|
name.into(),
|
|
source_origin.map_or(0, |origin| origin.line_offset.min(i32::MAX as u32) as i32),
|
|
source_origin.map_or(0, |origin| origin.column_offset.min(i32::MAX as u32) as i32),
|
|
false,
|
|
-1,
|
|
None,
|
|
false,
|
|
false,
|
|
true,
|
|
host_defined_options,
|
|
)
|
|
}
|
|
|
|
fn collect_module_requests(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
module: v8::Local<'_, v8::Module>,
|
|
) -> Result<Vec<ModuleRequestRecord>> {
|
|
let requests = module.get_module_requests();
|
|
let mut records = Vec::with_capacity(requests.length());
|
|
for index in 0..requests.length() {
|
|
let Some(request_data) = requests.get(scope, index) else {
|
|
continue;
|
|
};
|
|
let request = v8::Local::<v8::ModuleRequest>::try_from(request_data)
|
|
.map_err(|_| anyhow::anyhow!("module request entry was not a ModuleRequest"))?;
|
|
let specifier = request.get_specifier().to_rust_string_lossy(scope);
|
|
let attributes = module_request_attributes(scope, request);
|
|
if let Some(invalid_key) = attributes.invalid_import_attribute_key() {
|
|
return Err(anyhow::anyhow!("Invalid attribute key \"{invalid_key}\"."));
|
|
}
|
|
records.push(ModuleRequestRecord::new(
|
|
specifier,
|
|
attributes,
|
|
module_import_phase(request.get_phase()),
|
|
));
|
|
}
|
|
Ok(records)
|
|
}
|
|
|
|
fn module_request_attributes(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
request: v8::Local<'_, v8::ModuleRequest>,
|
|
) -> ModuleAttributesKey {
|
|
let attributes = request.get_import_attributes();
|
|
let mut pairs = Vec::with_capacity(attributes.length() / 3);
|
|
let mut index = 0;
|
|
while index + 1 < attributes.length() {
|
|
let key = attributes
|
|
.get(scope, index)
|
|
.and_then(|value| v8::Local::<v8::String>::try_from(value).ok())
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
let value = attributes
|
|
.get(scope, index + 1)
|
|
.and_then(|value| v8::Local::<v8::String>::try_from(value).ok())
|
|
.map(|value| value.to_rust_string_lossy(scope));
|
|
if let (Some(key), Some(value)) = (key, value) {
|
|
pairs.push((key, value));
|
|
}
|
|
index += 3;
|
|
}
|
|
ModuleAttributesKey::from_pairs(pairs)
|
|
}
|
|
|
|
fn module_import_phase(phase: v8::ModuleImportPhase) -> ModuleImportPhase {
|
|
match phase {
|
|
v8::ModuleImportPhase::kSource => ModuleImportPhase::Source,
|
|
_ => ModuleImportPhase::Evaluation,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
mod constructor_preservation;
|
|
mod parse_errors;
|
|
use std::pin::pin;
|
|
|
|
use super::{
|
|
DYNAMIC_MODULE_REACTION_ID_SLOT, MainNativeModuleSelectedTaskApplication,
|
|
NativeChildModuleScriptReactionDataDeclaration, NativeDynamicModuleReactionDataDeclaration,
|
|
NativeModuleScriptReactionDataDeclaration, ScriptVmCheckpointingMainNativeModuleTaskBody,
|
|
get_u64_reaction_data_slot, native_child_module_script_reaction_data,
|
|
native_module_script_reaction_data, preserve_current_v8_module_exception,
|
|
};
|
|
use crate::dom::native::{DomHost, NativeDom};
|
|
use crate::ensure_v8_for_test as ensure_v8;
|
|
use crate::frame_owner_model::{
|
|
ChildFrameSemanticTurnKind, DocumentId, FrameDocumentDynamicImportGraphAdvanceFollowup,
|
|
FrameDocumentDynamicImportMissingJoinedTerminalFetch,
|
|
FrameDocumentDynamicImportOwnerAction, FrameDocumentDynamicImportPendingJobResume,
|
|
FrameDocumentOwner, FrameDocumentTaskOwner, FrameRealmId, FrameSchedulerLaneId,
|
|
LocalWindowId,
|
|
};
|
|
use crate::module_runtime::{
|
|
DynamicModuleFetchFailure, DynamicModuleFetchOwnerAdvance, DynamicModuleImportOwner,
|
|
ModuleAttributesKey, ModuleEntryId, ModuleFetchMetadata, ModuleGraphFetchedSource,
|
|
ModuleGraphHandle, ModuleImportPhase, ModuleKind, ModuleLoadError, ModuleLoadStage,
|
|
ModuleMapEntryState, ModuleMapKey, ModuleSource, NativeModuleGraphFetchRequest,
|
|
NativeModuleGraphJob, NativeModuleGraphJobAdvance, PendingDynamicModuleImport,
|
|
};
|
|
use crate::module_script_continuation::NativeDynamicModuleTerminalFanout;
|
|
use crate::network::ResourceRequestClient;
|
|
use crate::script_vm::{ScriptVm, ScriptVmDefaultWorldBootstrap, StandaloneScriptVmHarness};
|
|
use crate::types::{
|
|
ModuleGraphFetchCompletion, ModuleGraphFetchOrdering, ModuleGraphFetchRequester,
|
|
ScriptErrorConstructorKind,
|
|
};
|
|
use crate::util::v8str;
|
|
use moli_fetch::FetchConfig;
|
|
use url::Url;
|
|
|
|
fn new_test_vm(url: &str) -> StandaloneScriptVmHarness {
|
|
let _js_runtime = crate::JsRuntime::initialize();
|
|
let page_task_queue = crate::page_task_queue::PageTaskQueueTestHarness::new();
|
|
let post_domcontentloaded_page_task_sender =
|
|
page_task_queue.owner_attached_runtime_page_task_sender_for_test();
|
|
let page_task_front_injection_tx = page_task_queue.parser_boundary_sender();
|
|
let page_runtime_task_source = page_task_queue.residence();
|
|
ScriptVmDefaultWorldBootstrap::standalone_from_dom_host_for_test(
|
|
DomHost::from_dom(NativeDom::new(Url::parse(url).expect("test url"))),
|
|
post_domcontentloaded_page_task_sender,
|
|
page_task_front_injection_tx,
|
|
)
|
|
.expect("script vm bootstrap should succeed")
|
|
.finish()
|
|
.map(|mut vm| {
|
|
vm.install_page_task_residence_for_executor_test(page_runtime_task_source);
|
|
vm
|
|
})
|
|
.expect("script vm finish should succeed")
|
|
}
|
|
|
|
fn dynamic_import_completion(load_id: u64, request_url: &str) -> ModuleGraphFetchCompletion {
|
|
dynamic_import_completion_with_source(load_id, request_url, "export const value = 1;")
|
|
}
|
|
|
|
fn dynamic_import_completion_with_source(
|
|
load_id: u64,
|
|
request_url: &str,
|
|
source: &str,
|
|
) -> ModuleGraphFetchCompletion {
|
|
let url = Url::parse(request_url).expect("request URL should parse");
|
|
ModuleGraphFetchCompletion {
|
|
load_id,
|
|
requester: ModuleGraphFetchRequester::DynamicImport,
|
|
ordering: ModuleGraphFetchOrdering::Runtime,
|
|
request_url: url.clone(),
|
|
result: Ok(ModuleGraphFetchedSource::new(
|
|
url,
|
|
false,
|
|
ModuleSource::text(source.to_owned()),
|
|
)),
|
|
network_result: None,
|
|
}
|
|
}
|
|
|
|
fn dynamic_import_reaction_parts_in_vm(
|
|
vm: &mut ScriptVm,
|
|
owner: DynamicModuleImportOwner,
|
|
) -> (
|
|
PendingDynamicModuleImport,
|
|
crate::module_runtime::DynamicModuleEvaluationTarget,
|
|
) {
|
|
let context_ptr: *const v8::Global<v8::Context> = match owner.child_parts() {
|
|
None => &vm.page_default_context,
|
|
Some((_child_handle, _task_owner, realm_id)) => vm
|
|
.frame_realm_context_ptr(realm_id)
|
|
.expect("child dynamic-import reaction test realm must be materialized"),
|
|
};
|
|
vm.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = std::pin::pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
let source =
|
|
v8::String::new(scope, "export default 1;").expect("test module source");
|
|
let source_name =
|
|
v8::String::new(scope, "https://dynamic-reaction-owner.test/pending.mjs")
|
|
.expect("test module source name");
|
|
let origin = v8::ScriptOrigin::new(
|
|
scope,
|
|
source_name.into(),
|
|
0,
|
|
0,
|
|
false,
|
|
-1,
|
|
None,
|
|
false,
|
|
false,
|
|
true,
|
|
None,
|
|
);
|
|
let mut compiler_source = v8::script_compiler::Source::new(source, Some(&origin));
|
|
let module = v8::script_compiler::compile_module(scope, &mut compiler_source)
|
|
.expect("test module should compile");
|
|
Ok((
|
|
PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
owner,
|
|
"./pending.mjs",
|
|
Url::parse("https://dynamic-reaction-owner.test/page.html")
|
|
.expect("test base URL"),
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
),
|
|
crate::module_runtime::DynamicModuleEvaluationTarget::new(
|
|
ModuleEntryId::for_test(0),
|
|
v8::Global::new(scope, module),
|
|
),
|
|
))
|
|
})
|
|
.expect("test reaction payload should be created in the ScriptVm isolate")
|
|
}
|
|
|
|
fn dynamic_import_request_in_vm(
|
|
vm: &mut ScriptVm,
|
|
specifier: &str,
|
|
base_url: Url,
|
|
phase: ModuleImportPhase,
|
|
) -> PendingDynamicModuleImport {
|
|
let context_host = vm._context_host.clone();
|
|
vm.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = std::pin::pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Local::new(scope, &vm.page_default_context);
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let owner = context_host
|
|
.borrow()
|
|
.current_dynamic_module_import_owner(scope, None)
|
|
.expect("test ScriptVm must have a main dynamic-import owner");
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
Ok(PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
owner,
|
|
specifier,
|
|
base_url,
|
|
ModuleAttributesKey::empty(),
|
|
phase,
|
|
))
|
|
})
|
|
.expect("test dynamic import request should be created in the ScriptVm isolate")
|
|
}
|
|
|
|
fn current_child_dynamic_import_owner(
|
|
vm: &mut ScriptVm,
|
|
child_handle: crate::document_runtime::DomHandle,
|
|
) -> DynamicModuleImportOwner {
|
|
let realm_id = vm
|
|
.child_frame_realm_store
|
|
.values()
|
|
.find(|realm| realm.child_handle == child_handle)
|
|
.map(|realm| realm.owner_realm_id)
|
|
.expect("test child document must have a current realm");
|
|
vm.with_frame_realm_scope_and_checkpoint_for_test(realm_id, move |scope, host_ptr| {
|
|
unsafe { &*host_ptr }
|
|
.current_dynamic_module_import_owner(scope, Some(child_handle))
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("test child document must have a current dynamic-import owner")
|
|
})
|
|
})
|
|
.expect("test child owner scope should enter")
|
|
}
|
|
|
|
async fn commit_child_document_and_run_parser_script_for_dynamic_import_test(
|
|
vm: &mut ScriptVm,
|
|
label: &str,
|
|
) {
|
|
assert_eq!(
|
|
vm.run_next_child_frame_semantic_turn_for_test().await,
|
|
Some(ChildFrameSemanticTurnKind::NavigationCommit),
|
|
"{label} should commit its srcdoc navigation before script execution"
|
|
);
|
|
while vm
|
|
.run_child_realm_materialization_body_for_test()
|
|
.expect("child realm materialization prerequisite should succeed")
|
|
{
|
|
// Consume only consecutive realm tasks at the child-family head;
|
|
// a script task must never be bypassed to reach a later realm.
|
|
}
|
|
assert!(
|
|
vm.run_child_frame_task_source_once_for_test(
|
|
ChildFrameSemanticTurnKind::DocumentScriptReady
|
|
)
|
|
.await,
|
|
"{label} parser script should run from DocumentScriptReady"
|
|
);
|
|
}
|
|
|
|
async fn finish_child_document_after_parser_script_for_dynamic_import_test(
|
|
vm: &mut ScriptVm,
|
|
label: &str,
|
|
) {
|
|
for transition in ["interactive", "DOMContentLoaded", "complete"] {
|
|
assert!(
|
|
vm.run_child_frame_task_source_once_for_test(
|
|
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
|
)
|
|
.await,
|
|
"{label} should run its {transition} lifecycle turn"
|
|
);
|
|
}
|
|
assert!(
|
|
vm.run_child_frame_task_source_once_for_test(ChildFrameSemanticTurnKind::HostLoad)
|
|
.await,
|
|
"{label} should finish through a later HostLoad turn"
|
|
);
|
|
}
|
|
|
|
async fn commit_child_document_and_run_parser_script_for_page_executor_test(
|
|
vm: &mut crate::runtime::PageVmTaskExecutorTestHarness,
|
|
label: &str,
|
|
) {
|
|
for expected in [
|
|
ChildFrameSemanticTurnKind::NavigationCommit,
|
|
ChildFrameSemanticTurnKind::RealmMaterialization,
|
|
ChildFrameSemanticTurnKind::DocumentScriptReady,
|
|
] {
|
|
assert_eq!(
|
|
vm.run_next_child_frame_semantic_turn().await,
|
|
Some(expected),
|
|
"{label} should advance {expected:?} through the real Page-owned realm prerequisite"
|
|
);
|
|
}
|
|
}
|
|
|
|
async fn finish_child_document_after_parser_script_for_page_executor_test(
|
|
vm: &mut crate::runtime::PageVmTaskExecutorTestHarness,
|
|
label: &str,
|
|
) {
|
|
for expected in [
|
|
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
|
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
|
ChildFrameSemanticTurnKind::DocumentLifecycle,
|
|
ChildFrameSemanticTurnKind::HostLoad,
|
|
] {
|
|
assert_eq!(
|
|
vm.run_next_child_frame_semantic_turn().await,
|
|
Some(expected),
|
|
"{label} should advance {expected:?} after its parser script"
|
|
);
|
|
}
|
|
}
|
|
|
|
fn dynamic_import_job_in_vm(
|
|
vm: &mut ScriptVm,
|
|
specifier: &str,
|
|
base_url: Url,
|
|
phase: ModuleImportPhase,
|
|
) -> NativeModuleGraphJob {
|
|
NativeModuleGraphJob::dynamic_import(dynamic_import_request_in_vm(
|
|
vm, specifier, base_url, phase,
|
|
))
|
|
}
|
|
|
|
#[test]
|
|
fn reaction_data_slots_preserve_u64_values_above_js_safe_integer() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let reaction_id = (1_u64 << 53) + 123;
|
|
let data = NativeDynamicModuleReactionDataDeclaration {
|
|
reaction_id: v8::BigInt::new_from_u64(scope, reaction_id),
|
|
}
|
|
.bind(scope)
|
|
.expect("reaction data declaration should bind");
|
|
|
|
assert_eq!(
|
|
get_u64_reaction_data_slot(scope, data, DYNAMIC_MODULE_REACTION_ID_SLOT),
|
|
Some(reaction_id)
|
|
);
|
|
|
|
let scheduler_lane_id = (1_u64 << 53) + 125;
|
|
let local_window_id = (1_u64 << 53) + 126;
|
|
let document_id = (1_u64 << 53) + 127;
|
|
let realm_id = -17_i64;
|
|
let document_owner = FrameDocumentTaskOwner::new(
|
|
FrameSchedulerLaneId(scheduler_lane_id),
|
|
LocalWindowId(local_window_id),
|
|
DocumentId(document_id),
|
|
);
|
|
let main_data = NativeModuleScriptReactionDataDeclaration {
|
|
module_script_reaction_id: v8::BigInt::new_from_u64(scope, reaction_id),
|
|
scheduler_lane_id: v8::BigInt::new_from_u64(scope, scheduler_lane_id),
|
|
local_window_id: v8::BigInt::new_from_u64(scope, local_window_id),
|
|
document_id: v8::BigInt::new_from_u64(scope, document_id),
|
|
}
|
|
.bind(scope)
|
|
.expect("main reaction data declaration should bind");
|
|
assert_eq!(
|
|
native_module_script_reaction_data(scope, main_data.into()),
|
|
Some((document_owner, reaction_id)),
|
|
"callback data must preserve the exact main Document without Number coercion"
|
|
);
|
|
let child_data = NativeChildModuleScriptReactionDataDeclaration {
|
|
module_script_reaction_id: v8::BigInt::new_from_u64(scope, reaction_id),
|
|
scheduler_lane_id: v8::BigInt::new_from_u64(scope, scheduler_lane_id),
|
|
local_window_id: v8::BigInt::new_from_u64(scope, local_window_id),
|
|
document_id: v8::BigInt::new_from_u64(scope, document_id),
|
|
realm_id: v8::BigInt::new_from_i64(scope, realm_id),
|
|
}
|
|
.bind(scope)
|
|
.expect("child reaction data declaration should bind");
|
|
assert_eq!(
|
|
native_child_module_script_reaction_data(scope, child_data.into()),
|
|
Some((document_owner, FrameRealmId(realm_id), reaction_id)),
|
|
"callback data must preserve the exact child Document and realm without Number coercion"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_module_instantiate_errors_use_stage_appropriate_constructors() {
|
|
let javascript_error = super::native_module_instantiate_load_error(
|
|
"javascript link failed".to_owned(),
|
|
None,
|
|
false,
|
|
);
|
|
assert_eq!(javascript_error.stage(), ModuleLoadStage::Instantiate);
|
|
assert_eq!(
|
|
javascript_error.error_constructor(),
|
|
Some(ScriptErrorConstructorKind::SyntaxError)
|
|
);
|
|
|
|
let wasm_error = super::native_module_instantiate_load_error(
|
|
"WebAssembly link failed".to_owned(),
|
|
None,
|
|
true,
|
|
);
|
|
assert_eq!(
|
|
wasm_error.error_constructor(),
|
|
Some(ScriptErrorConstructorKind::WebAssemblyLinkError)
|
|
);
|
|
|
|
let caught_syntax_error = super::native_module_instantiate_load_error(
|
|
"mixed graph failed".to_owned(),
|
|
Some(ScriptErrorConstructorKind::SyntaxError),
|
|
true,
|
|
);
|
|
assert_eq!(
|
|
caught_syntax_error.error_constructor(),
|
|
Some(ScriptErrorConstructorKind::SyntaxError),
|
|
"an exact V8 exception constructor must win over the module-kind fallback"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dynamic_import_fetch_completion_requires_owner_facade() {
|
|
let mut vm = new_test_vm("https://app.example.test/page.html");
|
|
let document_owner = vm
|
|
.current_main_document_task_owner()
|
|
.expect("dynamic import test must have a main document owner");
|
|
vm.eval("import('./dynamic.mjs'); 'queued'")
|
|
.expect("dynamic import should queue from current document isolate");
|
|
|
|
let queued_job = vm
|
|
.document_runtime
|
|
.take_next_native_dynamic_module_import()
|
|
.expect("dynamic import callback should enqueue one graph job");
|
|
let queued_owner = queued_job
|
|
.dynamic_import_request()
|
|
.expect("dynamic graph job must retain its request")
|
|
.owner();
|
|
assert_eq!(queued_owner.task_owner(), document_owner);
|
|
assert_eq!(
|
|
queued_owner.execution_context_owner(),
|
|
crate::native_bridge::WindowExecutionContextOwner::Frame(
|
|
document_owner.local_window_id
|
|
),
|
|
"dynamic import acceptance must capture the exact current Window execution context"
|
|
);
|
|
vm.document_runtime
|
|
.resume_native_dynamic_module_import_front(queued_job);
|
|
|
|
assert!(
|
|
matches!(
|
|
vm.run_next_native_dynamic_module_owner_action_selected_task_body(),
|
|
MainNativeModuleSelectedTaskApplication::Applied(_)
|
|
),
|
|
"dynamic import should start its module graph through the selected body"
|
|
);
|
|
assert!(
|
|
vm.has_inflight_dynamic_module_fetch(),
|
|
"dynamic import root fetch should be suspended in owner state"
|
|
);
|
|
|
|
vm.complete_native_module_graph_fetch(dynamic_import_completion(
|
|
0,
|
|
"https://app.example.test/dynamic.mjs",
|
|
))
|
|
.expect("bare graph completion helper should tolerate stale completions");
|
|
assert!(
|
|
vm.has_inflight_dynamic_module_fetch(),
|
|
"bare graph completion helper must not consume dynamic import owner fetches"
|
|
);
|
|
assert!(
|
|
vm.runtime_observable_lifecycle_errors_for_testing()
|
|
.iter()
|
|
.any(|message| message.contains(
|
|
"native module graph fetch completion 0 arrived without an in-flight module graph job"
|
|
)),
|
|
"bare helper should record the unmatched completion instead of consuming owner state"
|
|
);
|
|
|
|
let target = vm
|
|
.current_main_dynamic_import_graph_fetch_target(0)
|
|
.expect("dynamic import must expose its exact resolver target");
|
|
let completion = dynamic_import_completion(0, "https://app.example.test/dynamic.mjs");
|
|
let actions = vm
|
|
.complete_current_main_dynamic_import_graph_fetch_result(target, completion.result)
|
|
.expect("owner facade should complete dynamic import fetch");
|
|
assert!(
|
|
actions.into_parts().0.is_empty(),
|
|
"dynamic import owner completion should not synthesize module-script run actions"
|
|
);
|
|
assert!(
|
|
!vm.has_inflight_dynamic_module_fetch(),
|
|
"owner facade should consume dynamic import fetch state"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dynamic_import_fetch_failures_reject_joined_and_cached_imports() {
|
|
for shared_dependency in [false, true] {
|
|
let mut vm = new_test_vm("https://app.example.test/page.html");
|
|
let second_specifier = if shared_dependency {
|
|
"./second.mjs"
|
|
} else {
|
|
"./first.mjs"
|
|
};
|
|
vm.eval(&format!(
|
|
r#"
|
|
globalThis.__fetchErrors = [];
|
|
globalThis.__failedImport = specifier => import(specifier).then(
|
|
() => __fetchErrors.push(null),
|
|
error => __fetchErrors.push(error)
|
|
);
|
|
__failedImport('./first.mjs');
|
|
__failedImport({second_specifier:?});
|
|
"queued"
|
|
"#
|
|
))
|
|
.expect("both imports should enqueue their graph jobs");
|
|
for _ in 0..2 {
|
|
assert!(matches!(
|
|
vm.run_next_native_dynamic_module_owner_action_selected_task_body(),
|
|
MainNativeModuleSelectedTaskApplication::Applied(_)
|
|
));
|
|
}
|
|
|
|
let (failed_load_id, failed_url) = if shared_dependency {
|
|
for (load_id, root) in [(0, "first"), (1, "second")] {
|
|
let target = vm
|
|
.current_main_dynamic_import_graph_fetch_target(load_id)
|
|
.expect("each root should have its own fetch");
|
|
let completion = dynamic_import_completion_with_source(
|
|
load_id,
|
|
&format!("https://app.example.test/{root}.mjs"),
|
|
"import './shared.mjs';",
|
|
);
|
|
vm.complete_current_main_dynamic_import_graph_fetch_result(
|
|
target,
|
|
completion.result,
|
|
)
|
|
.expect("the roots should start or join the shared dependency fetch");
|
|
}
|
|
(2, "https://app.example.test/shared.mjs")
|
|
} else {
|
|
(0, "https://app.example.test/first.mjs")
|
|
};
|
|
assert_eq!(vm.eval("String(__fetchErrors.length)").unwrap(), "0");
|
|
let target = vm
|
|
.current_main_dynamic_import_graph_fetch_target(failed_load_id)
|
|
.expect("the shared request should retain its fetch owner");
|
|
vm.complete_current_main_dynamic_import_graph_fetch_result(
|
|
target,
|
|
Err("HTTP 404".to_owned()),
|
|
)
|
|
.expect("fetch failure should reject both graph clients");
|
|
|
|
let key = ModuleMapKey::java_script(url::Url::parse(failed_url).unwrap());
|
|
let entry = vm.document_runtime.native_module_entry_id(&key).unwrap();
|
|
assert_eq!(
|
|
vm.document_runtime.native_module_entry_state(entry),
|
|
ModuleMapEntryState::Failed,
|
|
"a failed request must settle the shared module map entry"
|
|
);
|
|
assert_eq!(
|
|
vm.eval("JSON.stringify([__fetchErrors.length, __fetchErrors.every(e => e instanceof TypeError), new Set(__fetchErrors).size])")
|
|
.unwrap(),
|
|
"[2,true,2]",
|
|
"both concurrent imports must reject with distinct TypeErrors"
|
|
);
|
|
|
|
vm.eval("__failedImport('./first.mjs'); 'queued'").unwrap();
|
|
assert!(matches!(
|
|
vm.run_next_native_dynamic_module_owner_action_selected_task_body(),
|
|
MainNativeModuleSelectedTaskApplication::Applied(_)
|
|
));
|
|
// The selected body leaves Promise reactions to its task-end checkpoint.
|
|
vm.perform_script_task_checkpoint(None)
|
|
.expect("the cached import task should dispatch its rejection reaction");
|
|
assert_eq!(
|
|
vm.eval("JSON.stringify([__fetchErrors.length, __fetchErrors.every(e => e instanceof TypeError), new Set(__fetchErrors).size])")
|
|
.unwrap(),
|
|
"[3,true,3]",
|
|
"a cached fetch failure must reject a later import with a fresh TypeError"
|
|
);
|
|
assert!(!vm.has_inflight_dynamic_module_fetch());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn module_reaction_source_consumes_exactly_one_current_target_per_turn() {
|
|
let mut vm = new_test_vm("https://module-reaction-one-turn.test/page.html");
|
|
let document_owner = vm
|
|
.current_main_document_task_owner()
|
|
.expect("module reaction fixture requires a main Document owner");
|
|
for reaction_id in [71, 72] {
|
|
vm._context_host
|
|
.borrow_mut()
|
|
.queue_document_module_script_evaluation_fulfilled(document_owner, reaction_id);
|
|
}
|
|
|
|
assert_eq!(
|
|
vm.run_page_module_reaction_body_for_test()
|
|
.expect("first module-reaction turn"),
|
|
Some(crate::page_task_queue::PageModuleReactionTargetEffect::DiscardedMissingReaction)
|
|
);
|
|
assert!(
|
|
vm.has_page_module_reaction_for_executor_test(),
|
|
"the second accepted reaction must remain queued after one bounded turn"
|
|
);
|
|
assert_eq!(
|
|
vm.run_page_module_reaction_body_for_test()
|
|
.expect("second module-reaction turn"),
|
|
Some(crate::page_task_queue::PageModuleReactionTargetEffect::DiscardedMissingReaction)
|
|
);
|
|
assert!(!vm.has_page_module_reaction_for_executor_test());
|
|
}
|
|
|
|
#[test]
|
|
fn dynamic_import_owner_survives_main_document_open_in_same_execution_context() {
|
|
let mut vm = new_test_vm("https://dynamic-reaction-owner.test/page.html");
|
|
let request = dynamic_import_request_in_vm(
|
|
&mut vm,
|
|
"./pending.mjs",
|
|
Url::parse("https://dynamic-reaction-owner.test/page.html").expect("base URL"),
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
let import_owner = request.owner();
|
|
let retired_document_owner = import_owner.task_owner();
|
|
|
|
vm.eval("document.open(); 'replaced'")
|
|
.expect("document.open should rotate the main document owner");
|
|
assert_ne!(
|
|
vm.current_main_document_task_owner(),
|
|
Some(retired_document_owner),
|
|
"test setup must retire the original document owner"
|
|
);
|
|
assert!(
|
|
vm.dynamic_module_import_owner_is_current(import_owner),
|
|
"document.open must preserve the ScriptState-like dynamic-import owner"
|
|
);
|
|
assert_eq!(
|
|
import_owner.execution_context_owner(),
|
|
crate::native_bridge::WindowExecutionContextOwner::Frame(
|
|
vm.current_main_document_task_owner()
|
|
.expect("replacement owner")
|
|
.local_window_id
|
|
)
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn child_replacement_retires_registered_dynamic_import_reaction() {
|
|
let mut vm = new_test_vm("https://dynamic-reaction-child.test/page.html");
|
|
vm.eval(
|
|
r#"
|
|
(() => {
|
|
const root = document.documentElement || document.appendChild(document.createElement("html"));
|
|
const body = document.body || root.appendChild(document.createElement("body"));
|
|
const frame = document.createElement("iframe");
|
|
frame.srcdoc = "<script>parent.__dynamicReactionChildReady = true;<\/script>";
|
|
body.appendChild(frame);
|
|
})()
|
|
"#,
|
|
)
|
|
.expect("dynamic reaction child setup should evaluate");
|
|
commit_child_document_and_run_parser_script_for_dynamic_import_test(
|
|
&mut vm,
|
|
"dynamic reaction child",
|
|
)
|
|
.await;
|
|
finish_child_document_after_parser_script_for_dynamic_import_test(
|
|
&mut vm,
|
|
"dynamic reaction child",
|
|
)
|
|
.await;
|
|
let child_context_id = vm
|
|
.live_child_default_runtime_realm_inventory()
|
|
.into_iter()
|
|
.map(|realm| realm.context_id)
|
|
.next()
|
|
.expect("dynamic reaction child realm should exist");
|
|
let child_handle = vm
|
|
.child_frame_realm_store
|
|
.get(&child_context_id)
|
|
.expect("dynamic reaction child realm record should exist")
|
|
.child_handle;
|
|
let document_owner = current_child_dynamic_import_owner(&mut vm, child_handle);
|
|
let (request, target) = dynamic_import_reaction_parts_in_vm(&mut vm, document_owner);
|
|
let reaction_id = vm
|
|
.document_runtime
|
|
.reserve_native_dynamic_module_evaluation_reaction(request, target);
|
|
vm._context_host
|
|
.borrow_mut()
|
|
.queue_native_dynamic_module_evaluation_fulfilled_for_owner_for_test(
|
|
document_owner,
|
|
reaction_id,
|
|
);
|
|
assert_eq!(
|
|
vm.document_runtime
|
|
.native_dynamic_module_evaluation_reaction_owner(reaction_id),
|
|
Some(document_owner),
|
|
"test must reserve a real V8-backed reaction before replacement"
|
|
);
|
|
assert!(vm.has_page_module_reaction_for_executor_test());
|
|
|
|
vm.eval("document.querySelector('iframe').srcdoc = '<p>replacement</p>'; 'queued'")
|
|
.expect("child replacement should queue");
|
|
assert_eq!(
|
|
vm.run_next_child_frame_semantic_turn_for_test().await,
|
|
Some(ChildFrameSemanticTurnKind::NavigationCommit),
|
|
"child replacement should commit through NavigationCommit"
|
|
);
|
|
|
|
assert_eq!(
|
|
vm.document_runtime
|
|
.native_dynamic_module_evaluation_reaction_owner(reaction_id),
|
|
None,
|
|
"owner transaction must drop the retired reaction's V8 context, resolver, and module"
|
|
);
|
|
assert!(
|
|
vm.has_page_module_reaction_for_executor_test(),
|
|
"stable Page source must retain the old exact-owner reaction until arbitration"
|
|
);
|
|
assert_eq!(
|
|
vm.run_page_module_reaction_body_for_test()
|
|
.expect("stale reaction arbitration should succeed"),
|
|
Some(crate::page_task_queue::PageModuleReactionTargetEffect::IgnoredStaleOwner),
|
|
"replacement must discard the queued reaction without applying it to the new realm"
|
|
);
|
|
assert!(!vm.has_page_module_reaction_for_executor_test());
|
|
assert!(
|
|
!vm.dynamic_module_import_owner_is_current(document_owner),
|
|
"navigation must retire the reaction's exact child execution context"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dynamic_import_body_commits_module_before_task_end_document_open_reentry() {
|
|
let mut vm = new_test_vm("https://dynamic-reentry.test/page.html");
|
|
let original_owner = vm
|
|
.current_main_document_task_owner()
|
|
.expect("dynamic reentry test must have a main document owner");
|
|
vm.eval(
|
|
r#"
|
|
globalThis.__dynamicEvaluationGate = new Promise(resolve => {
|
|
globalThis.__resolveDynamicEvaluationGate = resolve;
|
|
});
|
|
import('./dynamic.mjs').then(() => {
|
|
document.open();
|
|
document.write('<p id="replacement">replacement</p>');
|
|
document.close();
|
|
});
|
|
'queued'
|
|
"#,
|
|
)
|
|
.expect("dynamic import should queue");
|
|
assert!(
|
|
matches!(
|
|
vm.run_next_native_dynamic_module_owner_action_selected_task_body(),
|
|
MainNativeModuleSelectedTaskApplication::Applied(_)
|
|
),
|
|
"dynamic import graph should start a root fetch through the selected body"
|
|
);
|
|
let target = vm
|
|
.current_main_dynamic_import_graph_fetch_target(0)
|
|
.expect("dynamic TLA import must expose its exact resolver target");
|
|
let completion = dynamic_import_completion_with_source(
|
|
0,
|
|
"https://dynamic-reentry.test/dynamic.mjs",
|
|
"export const value = await globalThis.__dynamicEvaluationGate;",
|
|
);
|
|
vm.complete_current_main_dynamic_import_graph_fetch_result(target, completion.result)
|
|
.expect("dynamic TLA graph completion should start evaluation");
|
|
assert_eq!(
|
|
vm.current_main_document_task_owner(),
|
|
Some(original_owner),
|
|
"pending TLA must not replace the document before fulfillment"
|
|
);
|
|
|
|
vm.eval("globalThis.__resolveDynamicEvaluationGate(); 'resolved'")
|
|
.expect("dynamic TLA gate should resolve");
|
|
assert!(
|
|
vm.has_page_module_reaction_for_executor_test(),
|
|
"TLA fulfillment should queue the registered dynamic-import reaction"
|
|
);
|
|
assert_eq!(
|
|
vm.run_page_module_reaction_body_for_test()
|
|
.expect("dynamic import fulfillment body should run"),
|
|
Some(
|
|
crate::page_task_queue::PageModuleReactionTargetEffect::AppliedToCurrentOwner(
|
|
crate::page_task_queue::PageModuleReactionCurrentEffect::DynamicImportPromiseSettled,
|
|
)
|
|
),
|
|
"fulfilled reaction body should settle one exact import Promise"
|
|
);
|
|
assert_eq!(
|
|
vm.current_main_document_task_owner(),
|
|
Some(original_owner),
|
|
"the body must leave user Promise reactions for selected-task completion"
|
|
);
|
|
vm.perform_script_task_checkpoint(None)
|
|
.expect("selected-task checkpoint should run");
|
|
assert_ne!(
|
|
vm.current_main_document_task_owner(),
|
|
Some(original_owner),
|
|
"the task-end checkpoint should run the user reaction and replace the document"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn selected_dynamic_import_rejection_body_defers_user_reaction_to_task_end() {
|
|
let mut vm = new_test_vm("https://dynamic-rejection-body.test/page.html");
|
|
let context_host = vm._context_host.clone();
|
|
let request = vm
|
|
.renderer_document_isolate
|
|
.with_entered_renderer_document_isolate(|isolate| {
|
|
let scope = pin!(v8::HandleScope::new(isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Local::new(scope, &vm.page_default_context);
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let owner = context_host
|
|
.borrow()
|
|
.current_dynamic_module_import_owner(scope, None)
|
|
.expect("test ScriptVm must have a current dynamic-import owner");
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
let promise = resolver.get_promise(scope);
|
|
let global = scope.get_current_context().global(scope);
|
|
assert_eq!(
|
|
global.set(
|
|
scope,
|
|
v8str(scope, "__selectedDynamicImportPromise").into(),
|
|
promise.into(),
|
|
),
|
|
Some(true),
|
|
);
|
|
Ok(PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
owner,
|
|
"./failed.mjs",
|
|
Url::parse("https://dynamic-rejection-body.test/page.html").expect("base URL"),
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
))
|
|
})
|
|
.expect("selected rejection request should be created");
|
|
vm.eval_without_microtask_checkpoint_for_test(
|
|
r#"
|
|
globalThis.__selectedDynamicImportReactions = [];
|
|
__selectedDynamicImportPromise.catch(() => {
|
|
__selectedDynamicImportReactions.push("rejected");
|
|
});
|
|
"attached"
|
|
"#,
|
|
)
|
|
.expect("rejection observer should attach without running a checkpoint");
|
|
|
|
vm.reject_native_dynamic_module_import_with_error_selected_task_body(
|
|
request,
|
|
&ModuleLoadError::new(ModuleLoadStage::Fetch, "forced selected-task failure"),
|
|
)
|
|
.expect("selected rejection body should settle the exact Promise");
|
|
assert_eq!(
|
|
vm.eval_without_microtask_checkpoint_for_test(
|
|
"__selectedDynamicImportReactions.join('|')",
|
|
)
|
|
.expect("body-only observation should succeed"),
|
|
"",
|
|
"the rejection body must not run the user Promise reaction"
|
|
);
|
|
|
|
vm.perform_script_task_checkpoint(None)
|
|
.expect("selected-task checkpoint should run");
|
|
assert_eq!(
|
|
vm.eval_without_microtask_checkpoint_for_test(
|
|
"__selectedDynamicImportReactions.join('|')",
|
|
)
|
|
.expect("task-end observation should succeed"),
|
|
"rejected",
|
|
"the task-end checkpoint must run the deferred user reaction"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dynamic_import_owner_action_survives_main_document_open_for_rejection() {
|
|
let mut vm = new_test_vm("https://dynamic-action-owner.test/page.html");
|
|
let base_url = Url::parse("https://dynamic-action-owner.test/page.html").expect("base URL");
|
|
let request = dynamic_import_request_in_vm(
|
|
&mut vm,
|
|
"./failed.mjs",
|
|
base_url,
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
let retired_owner = request.owner();
|
|
|
|
vm.eval("document.open(); 'replaced'")
|
|
.expect("document.open should rotate the main document owner");
|
|
assert!(
|
|
vm.dynamic_module_import_owner_is_current(retired_owner),
|
|
"document.open must preserve the request's execution-context owner"
|
|
);
|
|
|
|
let outcome = vm
|
|
.run_main_dynamic_import_owner_action_with_body(
|
|
FrameDocumentDynamicImportOwnerAction::fetch_failed(
|
|
request,
|
|
ModuleLoadError::new(ModuleLoadStage::Fetch, "forced stale fetch failure"),
|
|
),
|
|
&mut ScriptVmCheckpointingMainNativeModuleTaskBody,
|
|
)
|
|
.expect("stale dynamic import rejection should be consumed");
|
|
|
|
assert!(outcome.made_progress());
|
|
assert!(!outcome.stale_owner_was_dropped());
|
|
assert!(
|
|
outcome.dynamic_import_was_rejected(),
|
|
"same-execution-context replacement must settle the original resolver"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn child_dynamic_import_resume_pending_job_reports_followup_progress() {
|
|
let mut vm = new_test_vm("https://child-dynamic-resume.test/page.html");
|
|
let job = {
|
|
let _js_runtime = crate::JsRuntime::initialize();
|
|
let mut isolate = v8::Isolate::new(Default::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
let request = PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
DynamicModuleImportOwner::main_for_test(),
|
|
"./dynamic-resume.js",
|
|
Url::parse("https://child-dynamic-resume.test/page.html").expect("base URL"),
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
NativeModuleGraphJob::dynamic_import(request)
|
|
};
|
|
|
|
let followup = vm.apply_child_dynamic_import_followup(
|
|
FrameDocumentDynamicImportGraphAdvanceFollowup::ResumePendingJob(
|
|
FrameDocumentDynamicImportPendingJobResume::new(job),
|
|
),
|
|
);
|
|
|
|
assert!(followup.made_progress());
|
|
assert!(followup.dynamic_import_job_was_resumed());
|
|
assert!(
|
|
vm.document_runtime
|
|
.take_next_native_dynamic_module_import()
|
|
.is_some(),
|
|
"ResumePendingJob should push the dynamic import job back to the runtime queue"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn child_dynamic_import_missing_joined_fetch_followup_reports_warning_progress() {
|
|
let mut vm = new_test_vm("https://child-dynamic-missing-fetch.test/page.html");
|
|
|
|
let followup = vm.apply_child_dynamic_import_followup(
|
|
FrameDocumentDynamicImportGraphAdvanceFollowup::RecordMissingJoinedTerminalFetch(
|
|
FrameDocumentDynamicImportMissingJoinedTerminalFetch::new(
|
|
FrameDocumentOwner::new(LocalWindowId(2), DocumentId(3)),
|
|
FrameRealmId(4),
|
|
55,
|
|
),
|
|
),
|
|
);
|
|
|
|
assert!(followup.made_progress());
|
|
assert!(followup.terminal_warning_was_recorded());
|
|
assert!(
|
|
vm.runtime_observable_lifecycle_errors_for_testing()
|
|
.iter()
|
|
.any(|message| message
|
|
.contains("child dynamic import fetch finish 55 for FrameDocumentOwner")),
|
|
"missing joined fetch follow-up should record a diagnostic warning"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn child_dynamic_import_need_fetches_queues_waiting_owner_action() {
|
|
let loader = ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
|
let mut vm = crate::runtime::PageVmTaskExecutorTestHarness::new(
|
|
Url::parse("https://parent-dynamic-owner.test/page.html").expect("page URL"),
|
|
&loader,
|
|
);
|
|
|
|
vm.eval(
|
|
r#"
|
|
(() => {
|
|
const root = document.documentElement || document.appendChild(document.createElement("html"));
|
|
const body = document.body || root.appendChild(document.createElement("body"));
|
|
const frame = document.createElement("iframe");
|
|
frame.srcdoc = `
|
|
<base href="https://child-dynamic-owner.test/nested/frame.html">
|
|
<script>parent.__childDynamicImportOwnerReady = true;<\/script>
|
|
`;
|
|
body.appendChild(frame);
|
|
})()
|
|
"#,
|
|
)
|
|
.expect("child dynamic import owner setup should evaluate");
|
|
commit_child_document_and_run_parser_script_for_page_executor_test(
|
|
&mut vm,
|
|
"child dynamic import waiting setup frame",
|
|
)
|
|
.await;
|
|
finish_child_document_after_parser_script_for_page_executor_test(
|
|
&mut vm,
|
|
"child dynamic import waiting setup frame",
|
|
)
|
|
.await;
|
|
|
|
let child_context_id = vm
|
|
.live_child_default_runtime_realm_inventory()
|
|
.into_iter()
|
|
.map(|realm| realm.context_id)
|
|
.next()
|
|
.expect("child default execution context should be created");
|
|
let child_handle = vm
|
|
.child_frame_realm_store
|
|
.get(&child_context_id)
|
|
.expect("child realm record should exist")
|
|
.child_handle;
|
|
let child_initiator_url = vm
|
|
.child_browsing_context_module_request_initiator_url(child_handle)
|
|
.expect("child document should expose a module request initiator URL");
|
|
let base_url =
|
|
Url::parse("https://child-dynamic-owner.test/nested/frame.html").expect("base URL");
|
|
let fetch_request = NativeModuleGraphFetchRequest::new_for_test(
|
|
base_url.join("dynamic-root.js").expect("dynamic URL"),
|
|
base_url,
|
|
ModuleFetchMetadata::default(),
|
|
ModuleKind::JavaScript,
|
|
);
|
|
let document_owner = current_child_dynamic_import_owner(&mut vm, child_handle);
|
|
let job = {
|
|
let _js_runtime = crate::JsRuntime::initialize();
|
|
let mut isolate = v8::Isolate::new(Default::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
let request = PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
document_owner,
|
|
"./dynamic-root.js",
|
|
child_initiator_url,
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
NativeModuleGraphJob::dynamic_import(request)
|
|
};
|
|
|
|
vm.continue_child_native_dynamic_module_import_after_tree_advance(
|
|
job,
|
|
NativeModuleGraphJobAdvance::NeedFetches(vec![fetch_request]),
|
|
)
|
|
.expect("child dynamic import waiting action should queue graph work");
|
|
|
|
let task = vm
|
|
.take_dynamic_import_owner_action_for_routing_test()
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"child dynamic import should queue a dynamic import owner action follow-up; warnings: {:?}",
|
|
vm.runtime_observable_lifecycle_errors_for_testing()
|
|
)
|
|
});
|
|
assert!(
|
|
matches!(
|
|
task.action(),
|
|
crate::frame_owner_model::FrameDocumentDynamicImportOwnerAction::Waiting { .. }
|
|
),
|
|
"NeedFetches should queue Waiting as a later dynamic-import owner action"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn native_dynamic_terminal_fanout_reports_child_ready_followup() {
|
|
let loader = ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
|
let mut vm = crate::runtime::PageVmTaskExecutorTestHarness::new(
|
|
Url::parse("https://parent-dynamic-generic-ready.test/page.html").expect("page URL"),
|
|
&loader,
|
|
);
|
|
|
|
vm.eval(
|
|
r#"
|
|
(() => {
|
|
const root = document.documentElement || document.appendChild(document.createElement("html"));
|
|
const body = document.body || root.appendChild(document.createElement("body"));
|
|
const frame = document.createElement("iframe");
|
|
frame.srcdoc = `
|
|
<base href="https://child-dynamic-generic-ready.test/nested/frame.html">
|
|
<script>parent.__childDynamicImportGenericReadyOwnerReady = true;<\/script>
|
|
`;
|
|
body.appendChild(frame);
|
|
})()
|
|
"#,
|
|
)
|
|
.expect("child dynamic import generic ready setup should evaluate");
|
|
commit_child_document_and_run_parser_script_for_page_executor_test(
|
|
&mut vm,
|
|
"child dynamic import generic ready setup frame",
|
|
)
|
|
.await;
|
|
finish_child_document_after_parser_script_for_page_executor_test(
|
|
&mut vm,
|
|
"child dynamic import generic ready setup frame",
|
|
)
|
|
.await;
|
|
|
|
let child_context_id = vm
|
|
.live_child_default_runtime_realm_inventory()
|
|
.into_iter()
|
|
.map(|realm| realm.context_id)
|
|
.next()
|
|
.expect("child default execution context should be created");
|
|
let child_handle = vm
|
|
.child_frame_realm_store
|
|
.get(&child_context_id)
|
|
.expect("child realm record should exist")
|
|
.child_handle;
|
|
let child_initiator_url = vm
|
|
.child_browsing_context_module_request_initiator_url(child_handle)
|
|
.expect("child document should expose a module request initiator URL");
|
|
let document_owner = current_child_dynamic_import_owner(&mut vm, child_handle);
|
|
|
|
let job = {
|
|
let _js_runtime = crate::JsRuntime::initialize();
|
|
let mut isolate = v8::Isolate::new(Default::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
let request = PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
document_owner,
|
|
"./dynamic-generic-complete.js",
|
|
child_initiator_url,
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
NativeModuleGraphJob::dynamic_import(request)
|
|
};
|
|
let graph = ModuleGraphHandle {
|
|
root_entry: ModuleEntryId::for_test(42),
|
|
entries: vec![ModuleEntryId::for_test(42)],
|
|
};
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_ready_import(crate::module_runtime::NativeDynamicModuleImportReady {
|
|
job,
|
|
graph,
|
|
});
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("generic child dynamic import fanout should queue owner action");
|
|
let followup = outcome.child_followup();
|
|
assert_eq!(
|
|
outcome.ready_import_count(),
|
|
1,
|
|
"fanout outcome should expose that a ready dynamic import was handled"
|
|
);
|
|
assert!(
|
|
followup.dynamic_import_owner_action_was_queued(),
|
|
"fanout should report the child dynamic-import owner action follow-up"
|
|
);
|
|
|
|
let task = vm
|
|
.take_dynamic_import_owner_action_for_routing_test()
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"generic child dynamic import completion should queue a ready owner action; warnings: {:?}",
|
|
vm.runtime_observable_lifecycle_errors_for_testing()
|
|
)
|
|
});
|
|
assert!(
|
|
matches!(
|
|
task.action(),
|
|
crate::frame_owner_model::FrameDocumentDynamicImportOwnerAction::Ready(_)
|
|
),
|
|
"generic child Complete fallback must queue Ready instead of settling inline"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn native_dynamic_terminal_fanout_does_not_rebind_to_replacement_child_owner() {
|
|
let mut vm = new_test_vm("https://parent-dynamic-stale-ready.test/page.html");
|
|
vm.eval(
|
|
r#"
|
|
(() => {
|
|
const root = document.documentElement || document.appendChild(document.createElement("html"));
|
|
const body = document.body || root.appendChild(document.createElement("body"));
|
|
const frame = document.createElement("iframe");
|
|
frame.srcdoc = "<script>parent.__staleDynamicImportChildReady = true;<\/script>";
|
|
body.appendChild(frame);
|
|
})()
|
|
"#,
|
|
)
|
|
.expect("stale child dynamic import setup should evaluate");
|
|
commit_child_document_and_run_parser_script_for_dynamic_import_test(
|
|
&mut vm,
|
|
"original stale dynamic import child",
|
|
)
|
|
.await;
|
|
finish_child_document_after_parser_script_for_dynamic_import_test(
|
|
&mut vm,
|
|
"original stale dynamic import child",
|
|
)
|
|
.await;
|
|
let child_context_id = vm
|
|
.live_child_default_runtime_realm_inventory()
|
|
.into_iter()
|
|
.map(|realm| realm.context_id)
|
|
.next()
|
|
.expect("stale dynamic import test should create a child realm");
|
|
let child_handle = vm
|
|
.child_frame_realm_store
|
|
.get(&child_context_id)
|
|
.expect("stale dynamic import child realm record should exist")
|
|
.child_handle;
|
|
let document_owner = current_child_dynamic_import_owner(&mut vm, child_handle);
|
|
let base_url =
|
|
Url::parse("https://child-dynamic-stale-ready.test/nested/frame.html").expect("url");
|
|
|
|
let job = {
|
|
let _js_runtime = crate::JsRuntime::initialize();
|
|
let mut isolate = v8::Isolate::new(Default::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
let request = PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
document_owner,
|
|
"./dynamic-stale-complete.js",
|
|
base_url,
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
NativeModuleGraphJob::dynamic_import(request)
|
|
};
|
|
vm.eval("document.querySelector('iframe').srcdoc = '<p>replacement child</p>'; 'queued'")
|
|
.expect("replacement child navigation should queue");
|
|
assert_eq!(
|
|
vm.run_next_child_frame_semantic_turn_for_test().await,
|
|
Some(ChildFrameSemanticTurnKind::NavigationCommit),
|
|
"child replacement must rotate the exact document owner before terminal fanout"
|
|
);
|
|
let (_captured_handle, retired_task_owner, _retired_realm_id) = document_owner
|
|
.child_parts()
|
|
.expect("test request must retain its child owner");
|
|
let replacement_task_owner = vm
|
|
._context_host
|
|
.borrow()
|
|
.current_child_document_task_owner(child_handle)
|
|
.expect("NavigationCommit should install the replacement document owner");
|
|
assert_ne!(
|
|
replacement_task_owner, retired_task_owner,
|
|
"replacement must preserve the frame handle while changing document identity"
|
|
);
|
|
let graph = ModuleGraphHandle {
|
|
root_entry: ModuleEntryId::for_test(77),
|
|
entries: vec![ModuleEntryId::for_test(77)],
|
|
};
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_ready_import(crate::module_runtime::NativeDynamicModuleImportReady {
|
|
job,
|
|
graph,
|
|
});
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("retired child dynamic import ready fanout should be handled");
|
|
let followup = outcome.child_followup();
|
|
|
|
assert_eq!(
|
|
outcome.ready_import_count(),
|
|
1,
|
|
"fanout outcome should expose that the retired ready import was consumed"
|
|
);
|
|
assert!(
|
|
!followup.made_progress(),
|
|
"stale work must not synthesize a child task-source wake"
|
|
);
|
|
assert!(
|
|
!followup.dynamic_import_owner_action_was_queued(),
|
|
"retired child work must not be rebound to the replacement document owner"
|
|
);
|
|
assert!(
|
|
vm.runtime_observable_lifecycle_errors_for_testing()
|
|
.iter()
|
|
.any(|warning| {
|
|
warning.contains("child dynamic import continuation")
|
|
&& warning.contains("owner is no longer current")
|
|
}),
|
|
"stale exact-owner drop should remain diagnosable without a synthetic wake"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn child_dynamic_import_graph_error_queues_failed_owner_action() {
|
|
let loader = ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
|
let mut vm = crate::runtime::PageVmTaskExecutorTestHarness::new(
|
|
Url::parse("https://parent-dynamic-failed.test/page.html").expect("page URL"),
|
|
&loader,
|
|
);
|
|
|
|
vm.eval(
|
|
r#"
|
|
(() => {
|
|
const root = document.documentElement || document.appendChild(document.createElement("html"));
|
|
const body = document.body || root.appendChild(document.createElement("body"));
|
|
const frame = document.createElement("iframe");
|
|
frame.srcdoc = `
|
|
<base href="https://child-dynamic-failed.test/nested/frame.html">
|
|
<script>parent.__childDynamicImportFailedOwnerReady = true;<\/script>
|
|
`;
|
|
body.appendChild(frame);
|
|
})()
|
|
"#,
|
|
)
|
|
.expect("child dynamic import failed owner setup should evaluate");
|
|
commit_child_document_and_run_parser_script_for_page_executor_test(
|
|
&mut vm,
|
|
"child dynamic import failed setup frame",
|
|
)
|
|
.await;
|
|
finish_child_document_after_parser_script_for_page_executor_test(
|
|
&mut vm,
|
|
"child dynamic import failed setup frame",
|
|
)
|
|
.await;
|
|
|
|
let child_context_id = vm
|
|
.live_child_default_runtime_realm_inventory()
|
|
.into_iter()
|
|
.map(|realm| realm.context_id)
|
|
.next()
|
|
.expect("child default execution context should be created");
|
|
let child_handle = vm
|
|
.child_frame_realm_store
|
|
.get(&child_context_id)
|
|
.expect("child realm record should exist")
|
|
.child_handle;
|
|
let child_initiator_url = vm
|
|
.child_browsing_context_module_request_initiator_url(child_handle)
|
|
.expect("child document should expose a module request initiator URL");
|
|
let document_owner = current_child_dynamic_import_owner(&mut vm, child_handle);
|
|
|
|
let job = {
|
|
let _js_runtime = crate::JsRuntime::initialize();
|
|
let mut isolate = v8::Isolate::new(Default::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let resolver = v8::PromiseResolver::new(scope).expect("promise resolver");
|
|
let request = PendingDynamicModuleImport::new(
|
|
v8::Global::new(scope, scope.get_current_context()),
|
|
v8::Global::new(scope, resolver),
|
|
document_owner,
|
|
"./dynamic-failed.js",
|
|
child_initiator_url,
|
|
ModuleAttributesKey::empty(),
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
NativeModuleGraphJob::dynamic_import(request)
|
|
};
|
|
let error = ModuleLoadError::new(ModuleLoadStage::Resolve, "forced graph failure");
|
|
|
|
let followup = vm
|
|
.enqueue_child_dynamic_import_graph_advance_failed_owner_action(job, error)
|
|
.expect("child dynamic import failed action should queue graph failure work");
|
|
assert!(
|
|
followup.dynamic_import_owner_action_was_queued(),
|
|
"graph failure should report the queued child dynamic-import owner action"
|
|
);
|
|
|
|
let task = vm
|
|
.take_dynamic_import_owner_action_for_routing_test()
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"child dynamic import should queue a failed owner action follow-up; warnings: {:?}",
|
|
vm.runtime_observable_lifecycle_errors_for_testing()
|
|
)
|
|
});
|
|
assert!(
|
|
matches!(
|
|
task.action(),
|
|
crate::frame_owner_model::FrameDocumentDynamicImportOwnerAction::Reject(_)
|
|
),
|
|
"graph errors should queue Reject as a later dynamic-import owner action"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_dynamic_terminal_fanout_reports_scheduled_fetch_effects() {
|
|
let mut vm = new_test_vm("https://app-dynamic-schedule.test/page.html");
|
|
let base_url = Url::parse("https://app-dynamic-schedule.test/page.html").expect("base URL");
|
|
let fetch_request = NativeModuleGraphFetchRequest::new_for_test(
|
|
base_url
|
|
.join("dynamic-dependency.js")
|
|
.expect("dynamic dependency URL"),
|
|
base_url.clone(),
|
|
ModuleFetchMetadata::default(),
|
|
ModuleKind::JavaScript,
|
|
);
|
|
let job = dynamic_import_job_in_vm(
|
|
&mut vm,
|
|
"./dynamic-dependency.js",
|
|
base_url,
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
let scheduled = vm
|
|
.document_runtime
|
|
.suspend_native_dynamic_module_import_fetches(
|
|
vec![fetch_request],
|
|
Vec::new(),
|
|
job,
|
|
vec![None],
|
|
);
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.extend_scheduled_dynamic_import_fetches(scheduled);
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("native dynamic import fanout should schedule fetches");
|
|
|
|
assert_eq!(
|
|
outcome.scheduled_dynamic_import_fetch_count(),
|
|
1,
|
|
"fanout outcome should expose concrete fetch scheduling progress"
|
|
);
|
|
assert!(
|
|
!outcome.child_followup().made_progress(),
|
|
"main dynamic fetch scheduling must not synthesize child frame follow-up work"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_dynamic_waiting_without_joined_clients_reports_job_resume_effect() {
|
|
let mut vm = new_test_vm("https://app-dynamic-resume.test/page.html");
|
|
let base_url = Url::parse("https://app-dynamic-resume.test/page.html").expect("base URL");
|
|
let job = dynamic_import_job_in_vm(
|
|
&mut vm,
|
|
"./dynamic-resume.js",
|
|
base_url,
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
|
|
let outcome = vm
|
|
.continue_main_native_dynamic_module_import_after_tree_advance_with_body(
|
|
job,
|
|
NativeModuleGraphJobAdvance::WaitingForFetches,
|
|
&mut ScriptVmCheckpointingMainNativeModuleTaskBody,
|
|
)
|
|
.expect("main dynamic waiting advance should resume the pending job");
|
|
|
|
assert_eq!(
|
|
outcome.dynamic_import_job_resumed_count(),
|
|
1,
|
|
"main WaitingForFetches without joined clients should expose job-resume progress"
|
|
);
|
|
assert!(
|
|
!outcome.child_followup().made_progress(),
|
|
"main dynamic job resume must not synthesize child frame follow-up work"
|
|
);
|
|
assert!(
|
|
vm.document_runtime
|
|
.take_next_native_dynamic_module_import()
|
|
.is_some(),
|
|
"the resumed job should be available on the runtime dynamic-import queue"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_dynamic_terminal_fanout_reports_failed_fetch_rejection_effect() {
|
|
let mut vm = new_test_vm("https://app-dynamic-fetch-failure.test/page.html");
|
|
let base_url =
|
|
Url::parse("https://app-dynamic-fetch-failure.test/page.html").expect("base URL");
|
|
let request = dynamic_import_request_in_vm(
|
|
&mut vm,
|
|
"./dynamic-fetch-failure.js",
|
|
base_url,
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
let failure = DynamicModuleFetchFailure::for_test(
|
|
request,
|
|
ModuleLoadError::new(ModuleLoadStage::Fetch, "forced dynamic fetch failure"),
|
|
);
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_failed_fetch(failure);
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("failed dynamic fetch fanout should reject the import");
|
|
|
|
assert_eq!(outcome.failed_fetch_rejected_count(), 1);
|
|
assert!(
|
|
!outcome.child_followup().made_progress(),
|
|
"main dynamic fetch failure must not synthesize child frame follow-up work"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_dynamic_terminal_fanout_reports_graph_advance_rejection_effect() {
|
|
let mut vm = new_test_vm("https://app-dynamic-graph-failure.test/page.html");
|
|
let base_url =
|
|
Url::parse("https://app-dynamic-graph-failure.test/page.html").expect("base URL");
|
|
let job = dynamic_import_job_in_vm(
|
|
&mut vm,
|
|
"./dynamic-graph-failure.js",
|
|
base_url,
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_graph_advance_failure(
|
|
job,
|
|
ModuleLoadError::new(ModuleLoadStage::Resolve, "forced dynamic graph failure"),
|
|
);
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("graph failure fanout should reject the import");
|
|
|
|
assert_eq!(outcome.graph_advance_failure_handled_count(), 1);
|
|
assert!(
|
|
!outcome.child_followup().made_progress(),
|
|
"main dynamic graph failure must not synthesize child frame follow-up work"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_dynamic_terminal_fanout_reports_source_import_rejection_effect() {
|
|
let mut vm = new_test_vm("https://app-dynamic-source.test/page.html");
|
|
let base_url = Url::parse("https://app-dynamic-source.test/page.html").expect("base URL");
|
|
let module_key = ModuleMapKey::java_script(
|
|
base_url
|
|
.join("dynamic-source.js")
|
|
.expect("dynamic source URL"),
|
|
);
|
|
let root_entry = vm.document_runtime.insert_native_module_source(
|
|
module_key,
|
|
ModuleSource::text("export const value = 1;".to_owned()),
|
|
);
|
|
let job = dynamic_import_job_in_vm(
|
|
&mut vm,
|
|
"./dynamic-source.js",
|
|
base_url,
|
|
ModuleImportPhase::Source,
|
|
);
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_ready_import(crate::module_runtime::NativeDynamicModuleImportReady {
|
|
job,
|
|
graph: ModuleGraphHandle {
|
|
root_entry,
|
|
entries: vec![root_entry],
|
|
},
|
|
});
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("source-phase dynamic import fanout should reject non-Wasm source import");
|
|
|
|
assert_eq!(outcome.ready_import_count(), 1);
|
|
assert_eq!(outcome.source_import_rejected_count(), 1);
|
|
assert!(
|
|
!outcome.child_followup().made_progress(),
|
|
"main source-phase dynamic import rejection must not synthesize child follow-up work"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_dynamic_terminal_fanout_reports_evaluation_rejection_effect() {
|
|
let mut vm = new_test_vm("https://app-dynamic-eval.test/page.html");
|
|
let base_url = Url::parse("https://app-dynamic-eval.test/page.html").expect("base URL");
|
|
let module_key = ModuleMapKey::java_script(
|
|
base_url
|
|
.join("dynamic-eval.js")
|
|
.expect("dynamic evaluation URL"),
|
|
);
|
|
let root_entry = vm.document_runtime.insert_native_module_source(
|
|
module_key,
|
|
ModuleSource::text("export const value = 1;".to_owned()),
|
|
);
|
|
let job = dynamic_import_job_in_vm(
|
|
&mut vm,
|
|
"./dynamic-eval.js",
|
|
base_url,
|
|
ModuleImportPhase::Evaluation,
|
|
);
|
|
let mut fanout = NativeDynamicModuleTerminalFanout::default();
|
|
fanout.push_ready_import(crate::module_runtime::NativeDynamicModuleImportReady {
|
|
job,
|
|
graph: ModuleGraphHandle {
|
|
root_entry,
|
|
entries: vec![root_entry],
|
|
},
|
|
});
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("evaluation dynamic import fanout should reject a non-ready root");
|
|
|
|
assert_eq!(outcome.ready_import_count(), 1);
|
|
assert_eq!(outcome.evaluation_import_rejected_count(), 1);
|
|
assert!(
|
|
!outcome.child_followup().made_progress(),
|
|
"main evaluation dynamic import rejection must not synthesize child follow-up work"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn native_dynamic_terminal_fanout_reports_unexpected_complete_warning_followup() {
|
|
let mut vm = new_test_vm("https://app-dynamic-warning.test/page.html");
|
|
let fanout = NativeDynamicModuleTerminalFanout::from_owner_advance(
|
|
DynamicModuleFetchOwnerAdvance::RestoredAfterUnexpectedComplete,
|
|
);
|
|
|
|
let outcome = vm
|
|
.handle_dynamic_module_terminal_fanout_for_owner(fanout)
|
|
.expect("native dynamic import fanout should record unexpected-complete warning");
|
|
let followup = outcome.child_followup();
|
|
|
|
assert!(followup.made_progress());
|
|
assert!(followup.terminal_warning_was_recorded());
|
|
assert!(
|
|
!followup.dynamic_import_owner_action_was_queued(),
|
|
"warning-only fanout must not enqueue a dynamic-import owner action"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn preserve_current_exception_keeps_v8_exception_message() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
|
|
let message =
|
|
v8::String::new(&scope, "original syntax failure").expect("v8 string allocation");
|
|
let exception = v8::Exception::syntax_error(&scope, message);
|
|
scope.throw_exception(exception);
|
|
assert!(preserve_current_v8_module_exception(&mut scope).is_none());
|
|
|
|
let exception = scope
|
|
.exception()
|
|
.expect("original exception should still be pending");
|
|
let message = exception
|
|
.to_string(&scope)
|
|
.expect("exception should stringify")
|
|
.to_rust_string_lossy(&scope);
|
|
assert!(message.contains("original syntax failure"));
|
|
}
|
|
|
|
#[test]
|
|
fn wasm_compile_with_options_preserves_v8_compile_exception() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let scope = try_catch.init();
|
|
let truncated_wasm_module = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01];
|
|
|
|
let module = v8::WasmModuleObject::compile_with_options(
|
|
&scope,
|
|
&truncated_wasm_module,
|
|
v8::WasmCompileOptions {
|
|
js_string_builtins: true,
|
|
imported_string_constants_module: None,
|
|
},
|
|
);
|
|
|
|
assert!(module.is_none());
|
|
let exception = scope
|
|
.exception()
|
|
.expect("wasm compile exception should be rethrown to caller scope");
|
|
let message = exception
|
|
.to_string(&scope)
|
|
.expect("wasm compile exception should stringify")
|
|
.to_rust_string_lossy(&scope);
|
|
assert!(message.contains("CompileError"), "{message}");
|
|
assert!(message.contains("WebAssembly.Module"), "{message}");
|
|
assert!(!message.contains("unknown wasm compile exception"));
|
|
}
|
|
|
|
#[test]
|
|
fn wasm_compile_with_options_does_not_use_page_webassembly_constructor() {
|
|
ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let patch_source = v8str(
|
|
scope,
|
|
r#"
|
|
WebAssembly.Module = function() {
|
|
throw new Error("patched WebAssembly.Module should not be used");
|
|
};
|
|
"#,
|
|
);
|
|
let patch_script =
|
|
v8::Script::compile(scope, patch_source, None).expect("patch script should compile");
|
|
crate::script_execution::execute_compiled_script(scope, patch_script)
|
|
.expect("patch script should run");
|
|
|
|
let try_catch = pin!(v8::TryCatch::new(scope));
|
|
let scope = try_catch.init();
|
|
let truncated_wasm_module = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01];
|
|
|
|
let module = v8::WasmModuleObject::compile_with_options(
|
|
&scope,
|
|
&truncated_wasm_module,
|
|
v8::WasmCompileOptions {
|
|
js_string_builtins: true,
|
|
imported_string_constants_module: None,
|
|
},
|
|
);
|
|
|
|
assert!(module.is_none());
|
|
let exception = scope
|
|
.exception()
|
|
.expect("wasm compile exception should be pending");
|
|
let message = exception
|
|
.to_string(&scope)
|
|
.expect("wasm compile exception should stringify")
|
|
.to_rust_string_lossy(&scope);
|
|
assert!(message.contains("CompileError"), "{message}");
|
|
assert!(
|
|
!message.contains("patched WebAssembly.Module should not be used"),
|
|
"{message}"
|
|
);
|
|
}
|
|
}
|