mirror of
https://github.com/lexmount/moli.git
synced 2026-10-09 00:01:06 +00:00
fix(devtools): unify page inspector command ingress
This commit is contained in:
@@ -137,10 +137,12 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: &str,
|
||||
) -> anyhow::Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::AccessibilityTreePayloadsForObjectId {
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
})
|
||||
self.start_page_command(
|
||||
RendererPageCommand::accessibility_tree_payloads_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id.to_owned(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn start_accessibility_node_and_ancestor_payloads_for_object_id(
|
||||
@@ -149,10 +151,10 @@ impl Page {
|
||||
object_id: &str,
|
||||
) -> anyhow::Result<PendingPageCommand> {
|
||||
self.start_page_command(
|
||||
RendererPageCommand::AccessibilityNodeAndAncestorPayloadsForObjectId {
|
||||
RendererPageCommand::accessibility_node_and_ancestor_payloads_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
},
|
||||
object_id.to_owned(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -163,11 +165,11 @@ impl Page {
|
||||
fetch_relatives: bool,
|
||||
) -> anyhow::Result<PendingPageCommand> {
|
||||
self.start_page_command(
|
||||
RendererPageCommand::AccessibilityPartialTreePayloadsForObjectId {
|
||||
RendererPageCommand::accessibility_partial_tree_payloads_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
object_id.to_owned(),
|
||||
fetch_relatives,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ pub struct PendingRuntimeInspectorCommandDispatch {
|
||||
|
||||
/// Identifies which execution owner consumed one routable Inspector command.
|
||||
///
|
||||
/// `Pause` commands complete inside V8's nested message loop while the Page
|
||||
/// actor is blocked. Callers must not synchronously re-enter that Page before
|
||||
/// exposing the Inspector response, because only the client can resume it.
|
||||
/// `Inspector` claims complete either in V8's interrupt callback or its nested
|
||||
/// message loop while the Page actor is blocked. Callers must not synchronously
|
||||
/// re-enter that Page before exposing the Inspector response.
|
||||
pub enum CompletedRuntimeInspectorCommandDispatch {
|
||||
Owner(Box<CompletedPageCommand>),
|
||||
Pause,
|
||||
Inspector,
|
||||
Canceled,
|
||||
}
|
||||
|
||||
@@ -349,8 +349,8 @@ impl PendingRuntimeInspectorCommandDispatch {
|
||||
RendererRuntimeInspectorCommandClaim::Owner => Err(anyhow::anyhow!(
|
||||
"runtime inspector command was claimed by owner without an owner task"
|
||||
)),
|
||||
RendererRuntimeInspectorCommandClaim::Pause => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Pause)
|
||||
RendererRuntimeInspectorCommandClaim::Inspector => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Inspector)
|
||||
}
|
||||
RendererRuntimeInspectorCommandClaim::Canceled => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Canceled)
|
||||
@@ -369,8 +369,8 @@ impl PendingRuntimeInspectorCommandDispatch {
|
||||
.await
|
||||
.map(Box::new)
|
||||
.map(CompletedRuntimeInspectorCommandDispatch::Owner),
|
||||
RendererRuntimeInspectorCommandClaim::Pause => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Pause)
|
||||
RendererRuntimeInspectorCommandClaim::Inspector => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Inspector)
|
||||
}
|
||||
RendererRuntimeInspectorCommandClaim::Canceled => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Canceled)
|
||||
@@ -386,8 +386,8 @@ impl PendingRuntimeInspectorCommandDispatch {
|
||||
completion,
|
||||
)))
|
||||
}
|
||||
RendererRuntimeInspectorCommandClaim::Pause => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Pause)
|
||||
RendererRuntimeInspectorCommandClaim::Inspector => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Inspector)
|
||||
}
|
||||
RendererRuntimeInspectorCommandClaim::Canceled => {
|
||||
Ok(CompletedRuntimeInspectorCommandDispatch::Canceled)
|
||||
|
||||
@@ -16,8 +16,9 @@ use crate::RendererOutputFence;
|
||||
use crate::renderer::{
|
||||
RendererDomDebuggerDomBreakpointResolution, RendererDomDebuggerEventListenerBreakpoint,
|
||||
RendererDomDebuggerEventListenersResolution, RendererDomDebuggerXhrBreakpoint,
|
||||
RendererPageCommand, RendererPageReply, RendererPerformanceMetricSnapshot,
|
||||
RendererRuntimeHeapUsage, RendererRuntimeInspectorResponseSender,
|
||||
RendererInspectorCommandRoute, RendererPageCommand, RendererPageReply,
|
||||
RendererPerformanceMetricSnapshot, RendererRuntimeHeapUsage,
|
||||
RendererRuntimeInspectorResponseSender,
|
||||
};
|
||||
|
||||
fn dedupe_runtime_context_created_events(events: &mut Vec<RuntimeContextRestoreEvent>) {
|
||||
@@ -473,10 +474,36 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
raw_json: String,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::DispatchRuntimeProtocolMessage {
|
||||
self.start_runtime_protocol_message_for_inspector_session_with_route(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
raw_json,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
pub fn start_runtime_interrupt_protocol_message_for_inspector_session(
|
||||
&self,
|
||||
inspector_session_id: Option<String>,
|
||||
raw_json: String,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_runtime_protocol_message_for_inspector_session_with_route(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
raw_json,
|
||||
)
|
||||
}
|
||||
|
||||
fn start_runtime_protocol_message_for_inspector_session_with_route(
|
||||
&self,
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
raw_json: String,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::dispatch_runtime_protocol_message(
|
||||
inspector_session_id,
|
||||
route,
|
||||
raw_json,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn start_runtime_protocol_message_with_deferred_response(
|
||||
@@ -496,38 +523,54 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_runtime_protocol_message_for_inspector_session_with_deferred_response_and_route(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
)
|
||||
}
|
||||
|
||||
fn start_runtime_protocol_message_for_inspector_session_with_deferred_response_and_route(
|
||||
&self,
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_deferred_response(
|
||||
inspector_session_id,
|
||||
route,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn start_routable_runtime_protocol_message_for_inspector_session(
|
||||
&self,
|
||||
inspector_session_id: Option<String>,
|
||||
inspector_route: RendererInspectorCommandRoute,
|
||||
owner_context_resolution_action: Option<String>,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
) -> Result<PendingRuntimeInspectorCommandDispatch> {
|
||||
let owner_fallback_inspector_session_id = inspector_session_id.clone();
|
||||
let route = self.handle.enqueue_routable_runtime_inspector_command(
|
||||
inspector_session_id,
|
||||
inspector_route,
|
||||
owner_context_resolution_action,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
);
|
||||
let owner_pending = if route.requires_owner_fallback() {
|
||||
let command_id = route.command_id();
|
||||
match self.start_page_command(
|
||||
RendererPageCommand::DispatchQueuedRuntimeInspectorCommand {
|
||||
command_id,
|
||||
inspector_session_id: owner_fallback_inspector_session_id,
|
||||
},
|
||||
) {
|
||||
let command = RendererPageCommand::dispatch_queued_runtime_inspector_command(
|
||||
route.metadata().clone(),
|
||||
command_id,
|
||||
);
|
||||
match self.start_page_command(command) {
|
||||
Ok(pending) => Some(pending),
|
||||
Err(error) => {
|
||||
self.handle.cancel_queued_runtime_inspector_command(
|
||||
@@ -567,11 +610,12 @@ impl Page {
|
||||
raw_json: String,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_context_resolution(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
action,
|
||||
raw_json,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -597,12 +641,13 @@ impl Page {
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_context_resolution_and_deferred_response(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
action,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -697,9 +742,9 @@ impl Page {
|
||||
&self,
|
||||
inspector_session_id: Option<&str>,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::RuntimeEnableEvents {
|
||||
inspector_session_id: inspector_session_id.map(str::to_owned),
|
||||
})
|
||||
self.start_page_command(RendererPageCommand::runtime_enable_events(
|
||||
inspector_session_id.map(str::to_owned),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_runtime_enable_events(
|
||||
@@ -747,10 +792,10 @@ impl Page {
|
||||
) -> Result<bool> {
|
||||
let pause_guard = self.handle.arm_runtime_inspector_session_detach();
|
||||
let reply = self
|
||||
.dispatch_page_command_async(RendererPageCommand::DetachRuntimeInspectorSession {
|
||||
inspector_session_id: inspector_session_id.map(str::to_owned),
|
||||
.dispatch_page_command_async(RendererPageCommand::detach_runtime_inspector_session(
|
||||
inspector_session_id.map(str::to_owned),
|
||||
pause_guard,
|
||||
})
|
||||
))
|
||||
.await?;
|
||||
expect_page_reply!(
|
||||
reply,
|
||||
@@ -821,12 +866,12 @@ impl Page {
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::DomDebuggerGetEventListeners {
|
||||
self.start_page_command(RendererPageCommand::dom_debugger_get_event_listeners(
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
depth,
|
||||
pierce,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_dom_debugger_get_event_listeners(
|
||||
@@ -937,12 +982,12 @@ impl Page {
|
||||
execution_context_name: Option<&str>,
|
||||
execution_context_id: Option<i64>,
|
||||
) -> Result<()> {
|
||||
let command = RendererPageCommand::AddRuntimeBinding {
|
||||
let command = RendererPageCommand::add_runtime_binding(
|
||||
inspector_session_id,
|
||||
name: name.to_owned(),
|
||||
execution_context_name: execution_context_name.map(str::to_owned),
|
||||
name.to_owned(),
|
||||
execution_context_name.map(str::to_owned),
|
||||
execution_context_id,
|
||||
};
|
||||
);
|
||||
self.dispatch_unit_page_command_async(command, "add runtime binding")
|
||||
.await
|
||||
}
|
||||
@@ -1047,13 +1092,14 @@ impl Page {
|
||||
stored_runtime_bindings: &[RuntimeBindingRegistration],
|
||||
session_runtime_bindings: &[RuntimeBindingRegistration],
|
||||
) -> Result<Option<RendererOutputFence>> {
|
||||
let pending = self.start_page_command(RendererPageCommand::ApplyRuntimeProtocolState {
|
||||
inspector_session_id: inspector_session_id.clone(),
|
||||
session_restore_snapshots: session_restore_snapshots.to_vec(),
|
||||
isolated_worlds: isolated_worlds.to_vec(),
|
||||
stored_runtime_bindings: stored_runtime_bindings.to_vec(),
|
||||
session_runtime_bindings: session_runtime_bindings.to_vec(),
|
||||
})?;
|
||||
let pending =
|
||||
self.start_page_command(RendererPageCommand::apply_runtime_protocol_state(
|
||||
inspector_session_id.clone(),
|
||||
session_restore_snapshots.to_vec(),
|
||||
isolated_worlds.to_vec(),
|
||||
stored_runtime_bindings.to_vec(),
|
||||
session_runtime_bindings.to_vec(),
|
||||
))?;
|
||||
let completion = pending.wait().await?;
|
||||
let output = self.finish_page_command_turn(completion);
|
||||
let (completion, predecessor) = output.into_completion_and_predecessor();
|
||||
|
||||
@@ -99,13 +99,14 @@ pub use moli_renderer_v8::{
|
||||
RendererDomFocusOutcome, RendererDomMutationEvent, RendererDomMutationEventBatch,
|
||||
RendererDomSnapshotCaptureOptions, RendererDomSnapshotCapturePayload, RendererDragData,
|
||||
RendererDragDataItem, RendererDraggedDirectory, RendererDraggedFile, RendererFrameToken,
|
||||
RendererInputDispatchOutcome, RendererJavaScriptDialogCompletion, RendererJavaScriptDialogId,
|
||||
RendererJavaScriptDialogResult, RendererJavaScriptDialogSource, RendererLayoutMetrics,
|
||||
RendererLifecycleEpoch, RendererLifecycleEventStamp, RendererLifecycleStartReason,
|
||||
RendererLifecycleTerminationStamp, RendererMainDocumentCommit,
|
||||
RendererPageCommandPostResponseContinuation, RendererPageCreationArtifacts,
|
||||
RendererPageCreationDiagnostics, RendererPageDiagnosticsSnapshot, RendererPageDumpFormat,
|
||||
RendererPageDumpOptions, RendererPageDumpStripOptions, RendererPendingDownloadActivation,
|
||||
RendererInputDispatchOutcome, RendererInspectorCommandRoute,
|
||||
RendererJavaScriptDialogCompletion, RendererJavaScriptDialogId, RendererJavaScriptDialogResult,
|
||||
RendererJavaScriptDialogSource, RendererLayoutMetrics, RendererLifecycleEpoch,
|
||||
RendererLifecycleEventStamp, RendererLifecycleStartReason, RendererLifecycleTerminationStamp,
|
||||
RendererMainDocumentCommit, RendererPageCommandPostResponseContinuation,
|
||||
RendererPageCreationArtifacts, RendererPageCreationDiagnostics,
|
||||
RendererPageDiagnosticsSnapshot, RendererPageDumpFormat, RendererPageDumpOptions,
|
||||
RendererPageDumpStripOptions, RendererPendingDownloadActivation,
|
||||
RendererPendingDownloadResponse, RendererPendingFileChooserActivation,
|
||||
RendererPendingJavaScriptDialog, RendererPendingPopupActivation,
|
||||
RendererPendingSameDocumentNavigation, RendererPendingTopLevelHistoryTraversal,
|
||||
|
||||
@@ -372,10 +372,12 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: &str,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::ComputedStylePropertiesForObjectId {
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
})
|
||||
self.start_page_command(
|
||||
RendererPageCommand::computed_style_properties_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id.to_owned(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn finish_computed_style_properties(
|
||||
@@ -399,10 +401,10 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: &str,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::NodeHasGeometryForObjectId {
|
||||
self.start_page_command(RendererPageCommand::node_has_geometry_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
})
|
||||
object_id.to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn start_scroll_node_into_view_if_needed_for_object_id_in_inspector_session(
|
||||
@@ -411,11 +413,11 @@ impl Page {
|
||||
object_id: &str,
|
||||
rect: Option<moli_page_types::DomScrollIntoViewRect>,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::ScrollObjectNodeIntoViewIfNeeded {
|
||||
self.start_page_command(RendererPageCommand::scroll_object_node_into_view_if_needed(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
object_id.to_owned(),
|
||||
rect,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
pub fn start_scroll_backend_node_into_view_if_needed(
|
||||
@@ -482,10 +484,10 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: &str,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::ClientRectForObjectId {
|
||||
self.start_page_command(RendererPageCommand::client_rect_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
})
|
||||
object_id.to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_client_rect_for_object_id(
|
||||
@@ -546,10 +548,10 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: &str,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::DocumentGeometryForObjectId {
|
||||
self.start_page_command(RendererPageCommand::document_geometry_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
})
|
||||
object_id.to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_document_geometry_for_object_id(
|
||||
@@ -714,10 +716,10 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::FocusDocumentNodeForObjectId {
|
||||
self.start_page_command(RendererPageCommand::focus_document_node_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
pub fn start_autofill_trigger(
|
||||
@@ -773,12 +775,12 @@ impl Page {
|
||||
files: Vec<super::SelectedFile>,
|
||||
append: bool,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::SetFileInputFilesForObjectId {
|
||||
self.start_page_command(RendererPageCommand::set_file_input_files_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
object_id.to_owned(),
|
||||
files,
|
||||
append,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_set_file_input_files_for_object_id(
|
||||
@@ -802,13 +804,13 @@ impl Page {
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::DocumentNodeSnapshotForObjectId {
|
||||
self.start_page_command(RendererPageCommand::document_node_snapshot_for_object_id(
|
||||
inspector_session_id,
|
||||
include_whitespace,
|
||||
object_id: object_id.to_owned(),
|
||||
object_id.to_owned(),
|
||||
depth,
|
||||
pierce,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_document_node_snapshot_for_object_id(
|
||||
@@ -1420,11 +1422,11 @@ impl Page {
|
||||
object_id: &str,
|
||||
include_shadow_dom: bool,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::OuterHtmlForObjectId {
|
||||
self.start_page_command(RendererPageCommand::outer_html_for_object_id(
|
||||
inspector_session_id,
|
||||
object_id: object_id.to_owned(),
|
||||
object_id.to_owned(),
|
||||
include_shadow_dom,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_outer_html_for_object_id(
|
||||
@@ -1570,12 +1572,14 @@ impl Page {
|
||||
execution_context_id: Option<i64>,
|
||||
object_group: Option<&str>,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::ResolveRuntimeObjectForBackendNodeId {
|
||||
inspector_session_id,
|
||||
backend_node_id,
|
||||
execution_context_id,
|
||||
object_group: object_group.map(str::to_owned),
|
||||
})
|
||||
self.start_page_command(
|
||||
RendererPageCommand::resolve_runtime_object_for_backend_node_id(
|
||||
inspector_session_id,
|
||||
backend_node_id,
|
||||
execution_context_id,
|
||||
object_group.map(str::to_owned),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn finish_resolve_runtime_object_for_backend_node_id(
|
||||
@@ -1593,10 +1597,10 @@ impl Page {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::ResolveBlobObject {
|
||||
self.start_page_command(RendererPageCommand::resolve_blob_object(
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_resolve_blob_object(
|
||||
|
||||
@@ -17,10 +17,11 @@ pub(crate) use moli_renderer_v8::{
|
||||
RendererDomDebuggerDomBreakpointResolution, RendererDomDebuggerEventListenerBreakpoint,
|
||||
RendererDomDebuggerEventListenersResolution, RendererDomDebuggerXhrBreakpoint,
|
||||
RendererDomFocusOutcome, RendererDomSnapshotCaptureOptions, RendererDomSnapshotCapturePayload,
|
||||
RendererLayoutMetrics, RendererOwnerCommand, RendererOwnerHandle, RendererOwnerReply,
|
||||
RendererPageCommand, RendererPageCommandPending, RendererPageCookieFacadeSnapshotReply,
|
||||
RendererPageDumpOptions, RendererPageHandle, RendererPageReply, RendererPageState,
|
||||
RendererPendingDownloadActivation, RendererPerformanceMetricSnapshot, RendererRuntimeHeapUsage,
|
||||
RendererInspectorCommandRoute, RendererLayoutMetrics, RendererOwnerCommand,
|
||||
RendererOwnerHandle, RendererOwnerReply, RendererPageCommand, RendererPageCommandPending,
|
||||
RendererPageCookieFacadeSnapshotReply, RendererPageDumpOptions, RendererPageHandle,
|
||||
RendererPageReply, RendererPageState, RendererPendingDownloadActivation,
|
||||
RendererPerformanceMetricSnapshot, RendererRuntimeHeapUsage,
|
||||
RendererRuntimeInspectorResponseSender, RendererRuntimeRemoteObjectResolution,
|
||||
ScriptRunOutcome,
|
||||
};
|
||||
|
||||
@@ -461,17 +461,21 @@ impl PageWireAction {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum DebuggerWireAction {
|
||||
Interruptible,
|
||||
ExecutionControl,
|
||||
IoExecutionControl,
|
||||
MainThreadExecutionControl,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl DebuggerWireAction {
|
||||
fn is_interruptible(self) -> bool {
|
||||
matches!(self, Self::Interruptible | Self::ExecutionControl)
|
||||
matches!(self, Self::Interruptible | Self::IoExecutionControl)
|
||||
}
|
||||
|
||||
fn executes_page_javascript(self) -> bool {
|
||||
self == Self::ExecutionControl
|
||||
matches!(
|
||||
self,
|
||||
Self::IoExecutionControl | Self::MainThreadExecutionControl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,8 +520,10 @@ impl DebuggerWireAction {
|
||||
| "setBreakpoint"
|
||||
| "setBreakpointByUrl"
|
||||
| "setBreakpointsActive" => Self::Interruptible,
|
||||
"continueToLocation" | "restartFrame" | "resume" | "stepInto" | "stepOut"
|
||||
| "stepOver" => Self::ExecutionControl,
|
||||
"resume" => Self::IoExecutionControl,
|
||||
"continueToLocation" | "restartFrame" | "stepInto" | "stepOut" | "stepOver" => {
|
||||
Self::MainThreadExecutionControl
|
||||
}
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
@@ -673,12 +679,7 @@ mod tests {
|
||||
"Debugger.getStackTrace",
|
||||
"Debugger.pause",
|
||||
"Debugger.removeBreakpoint",
|
||||
"Debugger.continueToLocation",
|
||||
"Debugger.restartFrame",
|
||||
"Debugger.resume",
|
||||
"Debugger.stepInto",
|
||||
"Debugger.stepOut",
|
||||
"Debugger.stepOver",
|
||||
"Debugger.setBreakpoint",
|
||||
"Debugger.setBreakpointByUrl",
|
||||
"Debugger.setBreakpointsActive",
|
||||
@@ -695,7 +696,12 @@ mod tests {
|
||||
}
|
||||
|
||||
for method in [
|
||||
"Debugger.continueToLocation",
|
||||
"Debugger.enable",
|
||||
"Debugger.restartFrame",
|
||||
"Debugger.stepInto",
|
||||
"Debugger.stepOut",
|
||||
"Debugger.stepOver",
|
||||
"Performance.enable",
|
||||
"Runtime.getIsolateId",
|
||||
] {
|
||||
|
||||
@@ -1452,6 +1452,166 @@ async fn websocket_cdp_debugger_pause_interrupts_in_flight_runtime_evaluate() {
|
||||
abort_test_cdp_server(protocol_server).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_cdp_io_terminate_interrupts_busy_main_thread_and_skips_main_follower() {
|
||||
let (cdp_addr, protocol_server) = spawn_test_protocol_server().await;
|
||||
let (mut socket, _) = connect_async(format!(
|
||||
"ws://{cdp_addr}/devtools/browser/{DEFAULT_BROWSER_ID}"
|
||||
))
|
||||
.await
|
||||
.expect("connect to cdp websocket");
|
||||
let session_id = cdp_create_default_session_and_navigate(
|
||||
&mut socket,
|
||||
"data:text/html,<body>IO interrupt</body>",
|
||||
)
|
||||
.await;
|
||||
|
||||
let enabled = send_cdp_command(
|
||||
&mut socket,
|
||||
6,
|
||||
"Debugger.enable",
|
||||
Some(&session_id),
|
||||
json!({}),
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
enabled
|
||||
.iter()
|
||||
.any(|message| message["id"] == json!(6_u64) && message.get("error").is_none()),
|
||||
"Debugger.enable should succeed: {enabled:#?}"
|
||||
);
|
||||
|
||||
socket
|
||||
.send(WsMessage::Text(
|
||||
json!({
|
||||
"id": 7_u64,
|
||||
"method": "Runtime.evaluate",
|
||||
"sessionId": session_id,
|
||||
"params": {
|
||||
"expression": "debugger; for (;;) {}",
|
||||
"returnByValue": true,
|
||||
}
|
||||
})
|
||||
.to_string()
|
||||
.into(),
|
||||
))
|
||||
.await
|
||||
.expect("send the non-yielding MainThread Runtime.evaluate");
|
||||
let mut observed = recv_until_match(&mut socket, |message| {
|
||||
message["sessionId"].as_str() == Some(session_id.as_str())
|
||||
&& message["method"] == json!("Debugger.paused")
|
||||
})
|
||||
.await;
|
||||
assert!(
|
||||
observed.iter().all(|message| message["id"] != json!(7_u64)),
|
||||
"the busy Runtime.evaluate must still be in flight at its debugger barrier: {observed:#?}"
|
||||
);
|
||||
|
||||
observed.extend(
|
||||
send_cdp_command(
|
||||
&mut socket,
|
||||
8,
|
||||
"Debugger.resume",
|
||||
Some(&session_id),
|
||||
json!({}),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
if observed.iter().all(|message| {
|
||||
message["sessionId"].as_str() != Some(session_id.as_str())
|
||||
|| message["method"] != json!("Debugger.resumed")
|
||||
}) {
|
||||
observed.extend(
|
||||
recv_until_match(&mut socket, |message| {
|
||||
message["sessionId"].as_str() == Some(session_id.as_str())
|
||||
&& message["method"] == json!("Debugger.resumed")
|
||||
})
|
||||
.await,
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
observed.iter().all(|message| message["id"] != json!(7_u64)),
|
||||
"the resumed Runtime.evaluate must enter its non-yielding loop: {observed:#?}"
|
||||
);
|
||||
|
||||
// This MainThread follower is deliberately queued before the IO command.
|
||||
// An interrupt callback must skip it, dispatch terminateExecution, and
|
||||
// leave the follower for ordinary owner dispatch after V8 unwinds.
|
||||
socket
|
||||
.send(WsMessage::Text(
|
||||
json!({
|
||||
"id": 9_u64,
|
||||
"method": "Runtime.evaluate",
|
||||
"sessionId": session_id,
|
||||
"params": {
|
||||
"expression": "6 * 7",
|
||||
"returnByValue": true,
|
||||
}
|
||||
})
|
||||
.to_string()
|
||||
.into(),
|
||||
))
|
||||
.await
|
||||
.expect("queue the MainThread follower");
|
||||
let terminated = tokio::time::timeout(
|
||||
Duration::from_secs(10),
|
||||
send_cdp_command(
|
||||
&mut socket,
|
||||
10,
|
||||
"Runtime.terminateExecution",
|
||||
Some(&session_id),
|
||||
json!({}),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("IO terminateExecution must interrupt non-yielding MainThread JavaScript");
|
||||
observed.extend(terminated);
|
||||
|
||||
let mut saw_busy_response = observed.iter().any(|message| message["id"] == json!(7_u64));
|
||||
let mut saw_follower_response = observed.iter().any(|message| message["id"] == json!(9_u64));
|
||||
if !saw_busy_response || !saw_follower_response {
|
||||
observed.extend(
|
||||
recv_until_match(&mut socket, |message| {
|
||||
saw_busy_response |= message["id"] == json!(7_u64);
|
||||
saw_follower_response |= message["id"] == json!(9_u64);
|
||||
saw_busy_response && saw_follower_response
|
||||
})
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
let terminate_response = observed
|
||||
.iter()
|
||||
.find(|message| message["id"] == json!(10_u64))
|
||||
.expect("terminateExecution response");
|
||||
assert_eq!(
|
||||
terminate_response["result"],
|
||||
json!({}),
|
||||
"terminateExecution must complete through the IO V8 interrupt: {observed:#?}"
|
||||
);
|
||||
let busy_response = observed
|
||||
.iter()
|
||||
.find(|message| message["id"] == json!(7_u64))
|
||||
.expect("terminated Runtime.evaluate response");
|
||||
assert!(
|
||||
busy_response.get("error").is_some()
|
||||
|| busy_response["result"]["exceptionDetails"].is_object(),
|
||||
"the non-yielding evaluation must report termination: {busy_response:#?}"
|
||||
);
|
||||
let follower_response = observed
|
||||
.iter()
|
||||
.find(|message| message["id"] == json!(9_u64))
|
||||
.expect("MainThread follower response");
|
||||
assert_eq!(
|
||||
follower_response["result"]["result"]["value"],
|
||||
json!(42),
|
||||
"the skipped MainThread follower must run normally after termination: {observed:#?}"
|
||||
);
|
||||
|
||||
let _ = socket.close(None).await;
|
||||
abort_test_cdp_server(protocol_server).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_cdp_raw_client_runtime_evaluate_immediately_after_page_navigate_succeeds() {
|
||||
// Regression test for the raw-CDP race: a raw client can pipeline
|
||||
@@ -4989,7 +5149,7 @@ async fn websocket_cdp_debugger_step_out_responds_before_resumed_and_caller_paus
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_cdp_debugger_pause_allows_auxiliary_runtime_commands() {
|
||||
async fn websocket_cdp_debugger_pause_allows_auxiliary_main_thread_commands() {
|
||||
let (cdp_addr, protocol_server) = spawn_test_protocol_server().await;
|
||||
let (mut socket, _) = connect_async(format!(
|
||||
"ws://{cdp_addr}/devtools/browser/{DEFAULT_BROWSER_ID}"
|
||||
@@ -5074,10 +5234,9 @@ async fn websocket_cdp_debugger_pause_allows_auxiliary_runtime_commands() {
|
||||
!observed.iter().any(|message| message["id"] == json!(6_u64)),
|
||||
"Runtime.evaluate must remain pending while the renderer owner is paused: {observed:#?}"
|
||||
);
|
||||
// V8's nested inspector loop owns this command while the Page actor is
|
||||
// blocked by the first evaluate. Its response and pause-bridge events are
|
||||
// owner-independent; trying to capture a Page-owner output snapshot before
|
||||
// publishing the response deadlocks because the client cannot resume.
|
||||
// Chromium's normal debugger loop pumps its main-thread DevTools receiver.
|
||||
// This command must therefore reach the auxiliary V8 session even though
|
||||
// the ordinary Page owner turn that entered the pause has not returned.
|
||||
let auxiliary_evaluate = tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
send_cdp_command(
|
||||
@@ -5089,14 +5248,14 @@ async fn websocket_cdp_debugger_pause_allows_auxiliary_runtime_commands() {
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("auxiliary Runtime.evaluate must complete while the Page owner is paused");
|
||||
.expect("auxiliary Main Runtime.evaluate must complete in the debugger loop");
|
||||
assert!(
|
||||
auxiliary_evaluate.iter().any(|message| {
|
||||
message["id"] == json!(7_u64)
|
||||
&& message["sessionId"] == json!(auxiliary_session_id)
|
||||
&& message["result"]["result"]["value"] == json!(42)
|
||||
}),
|
||||
"pause-loop Runtime.evaluate should complete before resume: {auxiliary_evaluate:#?}"
|
||||
"pause-loop Main Runtime.evaluate should complete before resume: {auxiliary_evaluate:#?}"
|
||||
);
|
||||
|
||||
let auxiliary_object = tokio::time::timeout(
|
||||
@@ -5110,7 +5269,7 @@ async fn websocket_cdp_debugger_pause_allows_auxiliary_runtime_commands() {
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("object-valued Runtime.evaluate must complete while the Page owner is paused");
|
||||
.expect("object-valued Main Runtime.evaluate must complete in the debugger loop");
|
||||
assert!(
|
||||
auxiliary_object.iter().any(|message| {
|
||||
message["id"] == json!(51_u64)
|
||||
@@ -5120,8 +5279,7 @@ async fn websocket_cdp_debugger_pause_allows_auxiliary_runtime_commands() {
|
||||
.as_str()
|
||||
.is_some_and(|object_id| !object_id.is_empty())
|
||||
}),
|
||||
"pause-loop object response must not enter Page-owner node normalization: \
|
||||
{auxiliary_object:#?}"
|
||||
"pause-loop object response must remain owner-independent: {auxiliary_object:#?}"
|
||||
);
|
||||
|
||||
socket
|
||||
@@ -5131,9 +5289,9 @@ async fn websocket_cdp_debugger_pause_allows_auxiliary_runtime_commands() {
|
||||
"method": "Runtime.evaluate",
|
||||
"sessionId": auxiliary_session_id,
|
||||
"params": {
|
||||
"expression": "Promise.resolve(43)",
|
||||
"awaitPromise": true,
|
||||
"returnByValue": true
|
||||
"expression": "Promise.resolve(43)",
|
||||
"awaitPromise": true,
|
||||
"returnByValue": true
|
||||
}
|
||||
})
|
||||
.to_string()
|
||||
|
||||
@@ -15,7 +15,8 @@ use moli_core::{
|
||||
DocumentNodeObjectSnapshot, DocumentNodeRuntimeObjectResolution,
|
||||
MAX_INSPECTOR_PROTOCOL_VALUE_DEPTH, RendererAgentAttachmentId, RendererCommandTurnOutput,
|
||||
RendererDomBidiNodeBindingResolution, RendererDomBidiNodeSharedIdResolution,
|
||||
RendererRuntimeCommandOutput, RendererRuntimeInspectorMessage, RendererRuntimeRealmInfo,
|
||||
RendererInspectorCommandRoute, RendererRuntimeCommandOutput,
|
||||
RendererRuntimeInspectorMessage, RendererRuntimeRealmInfo,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4231,7 +4232,7 @@ impl CdpConnection {
|
||||
Some(RendererRuntimeInspectorMessage::protocol(message))
|
||||
}
|
||||
|
||||
fn runtime_inspector_pause_active_for_session_owner(
|
||||
pub(crate) fn runtime_inspector_pause_active_for_session_owner(
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
) -> bool {
|
||||
@@ -4275,9 +4276,18 @@ impl CdpConnection {
|
||||
} else {
|
||||
self.runtime_session_owner_page_mut(session_id)?
|
||||
};
|
||||
let pending = page
|
||||
.start_runtime_protocol_message_for_inspector_session(inspector_session_id, raw_json)
|
||||
.map_err(|error| format!("runtime inspector dispatch failed: {error}"))?;
|
||||
let pending = if allow_suspended_document_access {
|
||||
page.start_runtime_interrupt_protocol_message_for_inspector_session(
|
||||
inspector_session_id,
|
||||
raw_json,
|
||||
)
|
||||
} else {
|
||||
page.start_runtime_protocol_message_for_inspector_session(
|
||||
inspector_session_id,
|
||||
raw_json,
|
||||
)
|
||||
}
|
||||
.map_err(|error| format!("runtime inspector dispatch failed: {error}"))?;
|
||||
Ok(PendingRuntimeProtocolMessageDispatch {
|
||||
session_id: session_id.map(str::to_owned),
|
||||
route,
|
||||
@@ -4340,6 +4350,11 @@ impl CdpConnection {
|
||||
};
|
||||
let pending = match page.start_routable_runtime_protocol_message_for_inspector_session(
|
||||
inspector_session_id,
|
||||
if allow_suspended_document_access {
|
||||
RendererInspectorCommandRoute::Io
|
||||
} else {
|
||||
RendererInspectorCommandRoute::MainThread
|
||||
},
|
||||
None,
|
||||
raw_json,
|
||||
response_sender,
|
||||
@@ -4415,6 +4430,7 @@ impl CdpConnection {
|
||||
};
|
||||
let pending = match page.start_routable_runtime_protocol_message_for_inspector_session(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
Some(action.to_owned()),
|
||||
raw_json,
|
||||
response_sender,
|
||||
@@ -4444,7 +4460,7 @@ impl CdpConnection {
|
||||
moli_core::page::CompletedRuntimeInspectorCommandDispatch::Owner(completion) => {
|
||||
*completion
|
||||
}
|
||||
moli_core::page::CompletedRuntimeInspectorCommandDispatch::Pause
|
||||
moli_core::page::CompletedRuntimeInspectorCommandDispatch::Inspector
|
||||
| moli_core::page::CompletedRuntimeInspectorCommandDispatch::Canceled => {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -4604,7 +4620,7 @@ impl CdpConnection {
|
||||
moli_core::page::CompletedRuntimeInspectorCommandDispatch::Owner(completion) => {
|
||||
*completion
|
||||
}
|
||||
moli_core::page::CompletedRuntimeInspectorCommandDispatch::Pause
|
||||
moli_core::page::CompletedRuntimeInspectorCommandDispatch::Inspector
|
||||
| moli_core::page::CompletedRuntimeInspectorCommandDispatch::Canceled => {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -247,7 +247,9 @@ pub use runtime::{
|
||||
RendererDomSearchRegistration, RendererDomSearchResultNode, RendererDomSearchResultsResolution,
|
||||
RendererDomSnapshotCaptureOptions, RendererDomSnapshotCapturePayload, RendererDragData,
|
||||
RendererDragDataItem, RendererDraggedDirectory, RendererDraggedFile, RendererFrameToken,
|
||||
RendererGeometryQuad, RendererInputDispatchOutcome, RendererInspectorProtocolConfiguration,
|
||||
RendererGeometryQuad, RendererInputDispatchOutcome, RendererInspectorCommandEnvelope,
|
||||
RendererInspectorCommandMetadata, RendererInspectorCommandRoute,
|
||||
RendererInspectorFirstDispatchLifecycle, RendererInspectorProtocolConfiguration,
|
||||
RendererInspectorProtocolConfigurationCommand, RendererInspectorSessionRestoreSnapshot,
|
||||
RendererJavaScriptDialogCompletion, RendererJavaScriptDialogId, RendererJavaScriptDialogResult,
|
||||
RendererJavaScriptDialogSource, RendererLayoutMetrics, RendererLifecycleDecider,
|
||||
|
||||
@@ -329,7 +329,9 @@ pub use self::page_surface::{
|
||||
RendererDomSearchRegistration, RendererDomSearchResultNode, RendererDomSearchResultsResolution,
|
||||
RendererDomSnapshotCaptureOptions, RendererDomSnapshotCapturePayload, RendererDragData,
|
||||
RendererDragDataItem, RendererDraggedDirectory, RendererDraggedFile, RendererGeometryQuad,
|
||||
RendererInputDispatchOutcome, RendererInspectorProtocolConfiguration,
|
||||
RendererInputDispatchOutcome, RendererInspectorCommandEnvelope,
|
||||
RendererInspectorCommandMetadata, RendererInspectorCommandRoute,
|
||||
RendererInspectorFirstDispatchLifecycle, RendererInspectorProtocolConfiguration,
|
||||
RendererInspectorProtocolConfigurationCommand, RendererInspectorSessionRestoreSnapshot,
|
||||
RendererJavaScriptDialogId, RendererJavaScriptDialogSource, RendererLayoutMetrics,
|
||||
RendererMainDocumentCommit, RendererMoliDomMemoryDiagnostics, RendererMoliMemoryDiagnostics,
|
||||
|
||||
@@ -86,7 +86,7 @@ use crate::service_worker_runtime::{
|
||||
use crate::shared_worker_runtime::{
|
||||
SharedWorkerRuntimeOwnerWake, shared_worker_owner_wake_channel,
|
||||
};
|
||||
use moli_page_types::{DevToolsSessionKey, LayoutPolicy};
|
||||
use moli_page_types::LayoutPolicy;
|
||||
use std::collections::VecDeque;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
@@ -374,7 +374,13 @@ pub(super) struct RenderRuntimePendingTurn {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct PageCommandFirstDispatchIdentity {
|
||||
token: RendererPageToken,
|
||||
lane: PageCommandFirstDispatchLane,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub(super) struct PageCommandFirstDispatchLane {
|
||||
inspector_session: DevToolsSessionKey,
|
||||
route: RendererInspectorCommandRoute,
|
||||
}
|
||||
|
||||
impl RenderRuntimePendingTurn {
|
||||
@@ -669,12 +675,21 @@ impl RenderRuntimeTurn {
|
||||
&self,
|
||||
) -> Option<PageCommandFirstDispatchIdentity> {
|
||||
match self {
|
||||
Self::RunLivePageCommand { token, command, .. } => command
|
||||
.first_dispatch_inspector_session()
|
||||
.map(|inspector_session| PageCommandFirstDispatchIdentity {
|
||||
token: *token,
|
||||
inspector_session,
|
||||
}),
|
||||
Self::RunLivePageCommand { token, command, .. } => {
|
||||
command.inspector_metadata().map(|metadata| {
|
||||
debug_assert_eq!(
|
||||
metadata.first_dispatch_lifecycle(),
|
||||
RendererInspectorFirstDispatchLifecycle::OrderedUntilFirstDispatch,
|
||||
);
|
||||
PageCommandFirstDispatchIdentity {
|
||||
token: *token,
|
||||
lane: PageCommandFirstDispatchLane {
|
||||
inspector_session: metadata.session().clone(),
|
||||
route: metadata.route(),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -800,7 +815,7 @@ fn live_page_command_should_follow_pending_navigation(command: &RendererPageComm
|
||||
}
|
||||
|
||||
fn live_page_command_requires_materialized_child_realms(command: &RendererPageCommand) -> bool {
|
||||
matches!(command, RendererPageCommand::RuntimeEnableEvents { .. })
|
||||
matches!(command, RendererPageCommand::Inspector(envelope) if envelope.requires_materialized_child_realms())
|
||||
}
|
||||
|
||||
const fn page_creation_navigation_reply_policy(
|
||||
@@ -2442,12 +2457,11 @@ impl RendererOwnerHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// The stable Page slot owns each Inspector session's command arrival
|
||||
/// order. Only the command that owns its session's first-dispatch lane
|
||||
/// enters the owner-wide runnable queue. Other sessions and non-Inspector
|
||||
/// Page commands remain independent. The pause bridge is still the
|
||||
/// interrupt path used by commands such as `Debugger.resume` once V8 is
|
||||
/// paused.
|
||||
/// The stable Page slot owns each Inspector `(session, route)` arrival
|
||||
/// order. Chromium intentionally separates main-thread and IO ingress,
|
||||
/// so each route has its own FIFO lane. Only the command that owns that
|
||||
/// lane enters the owner-wide runnable queue; other routes, sessions and
|
||||
/// non-Inspector Page commands remain independent.
|
||||
fn admit_page_command_first_dispatch(
|
||||
turn: RenderRuntimePendingTurn,
|
||||
pending_turns: &mut RenderRuntimePendingTurnQueue,
|
||||
@@ -2458,7 +2472,7 @@ impl RendererOwnerHandle {
|
||||
};
|
||||
if let Some(admitted) = admit_page_command_first_dispatch_on_bound_owner_local_store(
|
||||
identity.token,
|
||||
identity.inspector_session,
|
||||
identity.lane,
|
||||
turn,
|
||||
) {
|
||||
pending_turns.push_back(admitted);
|
||||
@@ -2471,7 +2485,7 @@ impl RendererOwnerHandle {
|
||||
) {
|
||||
if let Some(next) = complete_page_command_first_dispatch_on_bound_owner_local_store(
|
||||
identity.token,
|
||||
&identity.inspector_session,
|
||||
&identity.lane,
|
||||
) {
|
||||
pending_turns.push_back(next);
|
||||
}
|
||||
@@ -7055,37 +7069,46 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_dispatch_identity_uses_the_exact_renderer_inspector_session() {
|
||||
fn first_dispatch_identity_uses_the_exact_inspector_session_and_route() {
|
||||
let token = RendererPageToken::new_for_testing(PageId::new_for_testing(8));
|
||||
let attached = RenderRuntimeTurn::RunLivePageCommand {
|
||||
token,
|
||||
command: RendererPageCommand::DispatchQueuedRuntimeInspectorCommand {
|
||||
command_id: 1,
|
||||
inspector_session_id: Some("SID-aux".to_owned()),
|
||||
},
|
||||
command: RendererPageCommand::dispatch_queued_runtime_inspector_command(
|
||||
RendererInspectorCommandMetadata::new(
|
||||
Some("SID-aux".to_owned()),
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
),
|
||||
1,
|
||||
),
|
||||
capture_policy: RendererPageStateCapturePolicy::ProtocolTurn,
|
||||
};
|
||||
assert_eq!(
|
||||
attached.live_page_command_first_dispatch_identity(),
|
||||
Some(PageCommandFirstDispatchIdentity {
|
||||
token,
|
||||
inspector_session: DevToolsSessionKey::Attached("SID-aux".to_owned()),
|
||||
lane: PageCommandFirstDispatchLane {
|
||||
inspector_session: DevToolsSessionKey::Attached("SID-aux".to_owned()),
|
||||
route: RendererInspectorCommandRoute::MainThread,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
let primary = RenderRuntimeTurn::RunLivePageCommand {
|
||||
token,
|
||||
command: RendererPageCommand::DispatchQueuedRuntimeInspectorCommand {
|
||||
command_id: 2,
|
||||
inspector_session_id: None,
|
||||
},
|
||||
command: RendererPageCommand::dispatch_queued_runtime_inspector_command(
|
||||
RendererInspectorCommandMetadata::new(None, RendererInspectorCommandRoute::Io),
|
||||
2,
|
||||
),
|
||||
capture_policy: RendererPageStateCapturePolicy::ProtocolTurn,
|
||||
};
|
||||
assert_eq!(
|
||||
primary.live_page_command_first_dispatch_identity(),
|
||||
Some(PageCommandFirstDispatchIdentity {
|
||||
token,
|
||||
inspector_session: DevToolsSessionKey::Primary,
|
||||
lane: PageCommandFirstDispatchLane {
|
||||
inspector_session: DevToolsSessionKey::Primary,
|
||||
route: RendererInspectorCommandRoute::Io,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -7101,6 +7124,43 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_inspector_commands_join_raw_commands_at_the_structural_boundary() {
|
||||
let token = RendererPageToken::new_for_testing(PageId::new_for_testing(9));
|
||||
let turn_for = |command| RenderRuntimeTurn::RunLivePageCommand {
|
||||
token,
|
||||
command,
|
||||
capture_policy: RendererPageStateCapturePolicy::ProtocolTurn,
|
||||
};
|
||||
let enable = turn_for(RendererPageCommand::runtime_enable_events(Some(
|
||||
"SID-shared-lane".to_owned(),
|
||||
)));
|
||||
let raw_main = turn_for(RendererPageCommand::dispatch_runtime_protocol_message(
|
||||
Some("SID-shared-lane".to_owned()),
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
r#"{"id":1,"method":"Runtime.disable"}"#.to_owned(),
|
||||
));
|
||||
let raw_io = turn_for(RendererPageCommand::dispatch_runtime_protocol_message(
|
||||
Some("SID-shared-lane".to_owned()),
|
||||
RendererInspectorCommandRoute::Io,
|
||||
r#"{"id":2,"method":"Runtime.terminateExecution"}"#.to_owned(),
|
||||
));
|
||||
|
||||
let enable_identity = enable
|
||||
.live_page_command_first_dispatch_identity()
|
||||
.expect("Runtime.enable must enter the Inspector boundary");
|
||||
let main_identity = raw_main
|
||||
.live_page_command_first_dispatch_identity()
|
||||
.expect("raw main-thread dispatch must enter the Inspector boundary");
|
||||
let io_identity = raw_io
|
||||
.live_page_command_first_dispatch_identity()
|
||||
.expect("raw IO dispatch must enter the Inspector boundary");
|
||||
|
||||
assert_eq!(enable_identity, main_identity);
|
||||
assert_ne!(enable_identity, io_identity);
|
||||
assert_eq!(io_identity.lane.route, RendererInspectorCommandRoute::Io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacement_view_waiter_ignores_generic_activity_and_wrong_document_settlement() {
|
||||
let condition = RenderRuntimeParkCondition::ReplacementDocumentViewSettlement {
|
||||
|
||||
@@ -166,12 +166,15 @@ impl RendererPageHandle {
|
||||
pub fn enqueue_routable_runtime_inspector_command(
|
||||
&self,
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
owner_context_resolution_action: Option<String>,
|
||||
raw_json: String,
|
||||
response: RendererRuntimeInspectorResponseSender,
|
||||
) -> crate::script_vm::inspector_pause::RendererRuntimeInspectorCommandRoute {
|
||||
self.inspector_pause_bridge.enqueue_command(
|
||||
self.devtools_agent_token,
|
||||
inspector_session_id,
|
||||
route,
|
||||
owner_context_resolution_action,
|
||||
raw_json,
|
||||
response,
|
||||
|
||||
@@ -17,7 +17,9 @@ use super::navigation::{
|
||||
PageCreationNavigationFailurePublisher, PageCreationResolution, PageCreationRetirement,
|
||||
PageNavigationOwnerFailure, page_creation_navigation_failure_scope,
|
||||
};
|
||||
use super::owner::{RenderRuntimePendingTurn, RendererCreateStreamingRawPageRequest};
|
||||
use super::owner::{
|
||||
PageCommandFirstDispatchLane, RenderRuntimePendingTurn, RendererCreateStreamingRawPageRequest,
|
||||
};
|
||||
use super::owner_deadline_index::OwnerDeadlineIndex;
|
||||
use super::owner_local::RendererAttachedPage;
|
||||
use super::owner_maintenance::{
|
||||
@@ -45,7 +47,6 @@ use crate::script_vm::{
|
||||
RendererPageScriptEnvironment,
|
||||
};
|
||||
use crate::{RendererNavigationReplyPolicy, RendererTopLevelNavigationDispatch};
|
||||
use moli_page_types::DevToolsSessionKey;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
@@ -654,7 +655,7 @@ struct RendererOwnerLocalPageSlot {
|
||||
owner_slot: RendererPageSlotHandle,
|
||||
turn_scheduler: PageTurnScheduler<RendererPageLocalEntry>,
|
||||
page_command_first_dispatch:
|
||||
PageCommandFirstDispatchResidence<DevToolsSessionKey, RenderRuntimePendingTurn>,
|
||||
PageCommandFirstDispatchResidence<PageCommandFirstDispatchLane, RenderRuntimePendingTurn>,
|
||||
owner_maintenance: RendererPageOwnerMaintenanceResidence,
|
||||
task_sources: RendererPageOwnedTaskSources,
|
||||
lifecycle_gate: Option<LifecycleGate>,
|
||||
@@ -1559,24 +1560,24 @@ pub(super) fn take_entry_for_command_on_bound_owner_local_store(
|
||||
|
||||
pub(super) fn admit_page_command_first_dispatch_on_bound_owner_local_store(
|
||||
token: RendererPageToken,
|
||||
inspector_session: DevToolsSessionKey,
|
||||
lane: PageCommandFirstDispatchLane,
|
||||
turn: RenderRuntimePendingTurn,
|
||||
) -> Option<RenderRuntimePendingTurn> {
|
||||
with_bound_render_runtime_owner_local_store_session(|session| {
|
||||
session
|
||||
.store
|
||||
.admit_page_command_first_dispatch(token, inspector_session, turn)
|
||||
.admit_page_command_first_dispatch(token, lane, turn)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn complete_page_command_first_dispatch_on_bound_owner_local_store(
|
||||
token: RendererPageToken,
|
||||
inspector_session: &DevToolsSessionKey,
|
||||
lane: &PageCommandFirstDispatchLane,
|
||||
) -> Option<RenderRuntimePendingTurn> {
|
||||
with_bound_render_runtime_owner_local_store_session(|session| {
|
||||
session
|
||||
.store
|
||||
.complete_page_command_first_dispatch(token, inspector_session)
|
||||
.complete_page_command_first_dispatch(token, lane)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2907,7 +2908,7 @@ impl RendererOwnerLocalStore {
|
||||
fn admit_page_command_first_dispatch(
|
||||
&mut self,
|
||||
token: RendererPageToken,
|
||||
inspector_session: DevToolsSessionKey,
|
||||
lane: PageCommandFirstDispatchLane,
|
||||
turn: RenderRuntimePendingTurn,
|
||||
) -> Option<RenderRuntimePendingTurn> {
|
||||
let Some(page_slot) = self
|
||||
@@ -2919,20 +2920,18 @@ impl RendererOwnerLocalStore {
|
||||
// remains responsible for its protocol reply.
|
||||
return Some(turn);
|
||||
};
|
||||
page_slot
|
||||
.page_command_first_dispatch
|
||||
.admit(inspector_session, turn)
|
||||
page_slot.page_command_first_dispatch.admit(lane, turn)
|
||||
}
|
||||
|
||||
fn complete_page_command_first_dispatch(
|
||||
&mut self,
|
||||
token: RendererPageToken,
|
||||
inspector_session: &DevToolsSessionKey,
|
||||
lane: &PageCommandFirstDispatchLane,
|
||||
) -> Option<RenderRuntimePendingTurn> {
|
||||
self.page_hosts
|
||||
.get_mut(&token.local_host_id)
|
||||
.and_then(|host| host.pages.get_mut(&token.page_id))
|
||||
.and_then(|slot| slot.page_command_first_dispatch.complete(inspector_session))
|
||||
.and_then(|slot| slot.page_command_first_dispatch.complete(lane))
|
||||
}
|
||||
|
||||
fn checkout_entry_for_owner_turn(
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
//!
|
||||
//! A Page entry is temporarily checked out while an owner turn runs, so
|
||||
//! Inspector-session ordering cannot live in `RendererPageLocalEntry`. This
|
||||
//! residence stays in the stable Page slot and admits each session's commands
|
||||
//! in arrival order until they reach their first V8 dispatch. Independent
|
||||
//! sessions on the same Page have independent lanes.
|
||||
//! residence stays in the stable Page slot and admits each `(session, route)`
|
||||
//! stream in arrival order until commands reach their first V8 dispatch.
|
||||
//! Independent sessions and Chromium-style main-thread/IO routes on the same
|
||||
//! Page have independent lanes.
|
||||
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
|
||||
pub(super) struct PageCommandFirstDispatchResidence<Key, Command> {
|
||||
/// Presence of a lane is the stable marker for its active command, which
|
||||
/// has already moved to the renderer owner. The deque retains only later
|
||||
/// same-session commands.
|
||||
/// same-lane commands.
|
||||
lanes: BTreeMap<Key, VecDeque<Command>>,
|
||||
}
|
||||
|
||||
@@ -37,9 +38,9 @@ impl<Key, Command> Default for PageCommandFirstDispatchResidence<Key, Command> {
|
||||
}
|
||||
|
||||
impl<Key: Ord, Command> PageCommandFirstDispatchResidence<Key, Command> {
|
||||
/// Returns the command when it owns its session's active dispatch slot. A
|
||||
/// Returns the command when it owns its lane's active dispatch slot. A
|
||||
/// `None` result means the Page residence retained it behind a predecessor
|
||||
/// from the same session.
|
||||
/// from the same lane.
|
||||
pub(super) fn admit(&mut self, key: Key, command: Command) -> Option<Command> {
|
||||
match self.lanes.entry(key) {
|
||||
std::collections::btree_map::Entry::Vacant(entry) => {
|
||||
@@ -53,7 +54,7 @@ impl<Key: Ord, Command> PageCommandFirstDispatchResidence<Key, Command> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases one session's active command and returns its next FIFO waiter,
|
||||
/// Releases one lane's active command and returns its next FIFO waiter,
|
||||
/// which inherits the lane without an intermediate idle state.
|
||||
pub(super) fn complete(&mut self, key: &Key) -> Option<Command> {
|
||||
debug_assert!(
|
||||
@@ -79,32 +80,68 @@ impl<Key: Ord, Command> PageCommandFirstDispatchResidence<Key, Command> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::RendererInspectorCommandRoute;
|
||||
|
||||
type Lane = (&'static str, RendererInspectorCommandRoute);
|
||||
|
||||
#[test]
|
||||
fn each_inspector_session_owns_an_independent_fifo_dispatch_lane() {
|
||||
fn each_inspector_session_and_route_owns_an_independent_fifo_dispatch_lane() {
|
||||
let mut first_page = PageCommandFirstDispatchResidence::default();
|
||||
let mut other_page = PageCommandFirstDispatchResidence::default();
|
||||
let session_a_main: Lane = ("session-a", RendererInspectorCommandRoute::MainThread);
|
||||
let session_a_io: Lane = ("session-a", RendererInspectorCommandRoute::Io);
|
||||
let session_b_main: Lane = ("session-b", RendererInspectorCommandRoute::MainThread);
|
||||
|
||||
assert_eq!(first_page.admit("session-a", "a-first"), Some("a-first"));
|
||||
assert_eq!(first_page.admit("session-a", "a-second"), None);
|
||||
assert_eq!(
|
||||
first_page.admit("session-b", "b-first"),
|
||||
first_page.admit(session_a_main, "a-main-first"),
|
||||
Some("a-main-first")
|
||||
);
|
||||
assert_eq!(first_page.admit(session_a_main, "a-main-second"), None);
|
||||
assert_eq!(
|
||||
first_page.admit(session_a_io, "a-io-first"),
|
||||
Some("a-io-first"),
|
||||
"an IO command must not wait for main-thread work in the same session"
|
||||
);
|
||||
assert_eq!(
|
||||
first_page.admit(session_b_main, "b-first"),
|
||||
Some("b-first"),
|
||||
"a parked command must not block a different Inspector session"
|
||||
);
|
||||
assert_eq!(first_page.admit("session-b", "b-second"), None);
|
||||
assert_eq!(first_page.admit(session_b_main, "b-second"), None);
|
||||
|
||||
assert_eq!(other_page.admit("session-a", "other"), Some("other"));
|
||||
assert_eq!(other_page.complete(&"session-a"), None);
|
||||
assert_eq!(other_page.admit(session_a_main, "other"), Some("other"));
|
||||
assert_eq!(other_page.complete(&session_a_main), None);
|
||||
|
||||
assert_eq!(first_page.complete(&"session-b"), Some("b-second"));
|
||||
assert_eq!(first_page.complete(&"session-b"), None);
|
||||
assert_eq!(first_page.complete(&"session-a"), Some("a-second"));
|
||||
assert_eq!(first_page.complete(&"session-a"), None);
|
||||
assert_eq!(first_page.complete(&session_b_main), Some("b-second"));
|
||||
assert_eq!(first_page.complete(&session_b_main), None);
|
||||
assert_eq!(first_page.complete(&session_a_io), None);
|
||||
assert_eq!(first_page.complete(&session_a_main), Some("a-main-second"));
|
||||
assert_eq!(first_page.complete(&session_a_main), None);
|
||||
|
||||
assert_eq!(
|
||||
first_page.admit("session-a", "a-after-idle"),
|
||||
first_page.admit(session_a_main, "a-after-idle"),
|
||||
Some("a-after-idle")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retiring_a_page_drains_waiters_and_resets_every_lane() {
|
||||
let mut residence = PageCommandFirstDispatchResidence::default();
|
||||
let main: Lane = ("session-a", RendererInspectorCommandRoute::MainThread);
|
||||
let io: Lane = ("session-a", RendererInspectorCommandRoute::Io);
|
||||
|
||||
assert_eq!(residence.admit(main, "main-active"), Some("main-active"));
|
||||
assert_eq!(residence.admit(main, "main-waiting"), None);
|
||||
assert_eq!(residence.admit(io, "io-active"), Some("io-active"));
|
||||
assert_eq!(residence.admit(io, "io-waiting"), None);
|
||||
|
||||
let mut drained = residence.drain_waiting();
|
||||
drained.sort_unstable();
|
||||
assert_eq!(drained, ["io-waiting", "main-waiting"]);
|
||||
assert_eq!(
|
||||
residence.admit(main, "main-after-retirement"),
|
||||
Some("main-after-retirement"),
|
||||
"retirement must not leave a stale active-lane marker"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::page_surface::RendererInspectorPageCommand;
|
||||
use super::*;
|
||||
|
||||
impl PageVm {
|
||||
@@ -8,6 +9,9 @@ impl PageVm {
|
||||
let throttling_started =
|
||||
renderer_page_command_uses_cpu_throttling(&command).then(std::time::Instant::now);
|
||||
let result = match command {
|
||||
RendererPageCommand::Inspector(command) => {
|
||||
self.dispatch_renderer_inspector_command(command)
|
||||
}
|
||||
RendererPageCommand::EvaluateExpression {
|
||||
expression,
|
||||
await_promise,
|
||||
@@ -126,12 +130,6 @@ impl PageVm {
|
||||
RendererPageCommand::FocusDocumentBackendNode { backend_node_id } => self
|
||||
.focus_document_backend_node(backend_node_id)
|
||||
.map(RendererPageReply::DomFocusOutcome),
|
||||
RendererPageCommand::FocusDocumentNodeForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => self
|
||||
.focus_document_node_for_object_id(inspector_session_id.as_deref(), &object_id)
|
||||
.map(RendererPageReply::DomFocusOutcome),
|
||||
RendererPageCommand::TriggerAutofill(request) => self
|
||||
.trigger_autofill(request)
|
||||
.map(RendererPageReply::AutofillTriggerOutcome),
|
||||
@@ -146,19 +144,6 @@ impl PageVm {
|
||||
} => Ok(RendererPageReply::OptionalBool(
|
||||
self.set_file_input_files_for_backend_node_id(backend_node_id, files, append)?,
|
||||
)),
|
||||
RendererPageCommand::SetFileInputFilesForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
files,
|
||||
append,
|
||||
} => Ok(RendererPageReply::OptionalBool(
|
||||
self.set_file_input_files_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
files,
|
||||
append,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::InsertTextIntoActiveControl(text) => self
|
||||
.insert_text_into_active_control(&text)
|
||||
.map(RendererPageReply::Bool),
|
||||
@@ -181,99 +166,6 @@ impl PageVm {
|
||||
should_insert_text,
|
||||
)
|
||||
.map(RendererPageReply::InputDispatchOutcome),
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessage {
|
||||
inspector_session_id,
|
||||
raw_json,
|
||||
} => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session(
|
||||
inspector_session_id.as_deref(),
|
||||
&raw_json,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
inspector_session_id,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
} => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_deferred_response(
|
||||
inspector_session_id.as_deref(),
|
||||
&raw_json,
|
||||
deferred_response,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererPageCommand::DispatchQueuedRuntimeInspectorCommand { command_id, .. } => self
|
||||
.dispatch_queued_runtime_inspector_command(command_id)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
inspector_session_id,
|
||||
action,
|
||||
raw_json,
|
||||
} => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_context_resolution(
|
||||
inspector_session_id.as_deref(),
|
||||
&action,
|
||||
&raw_json,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
inspector_session_id,
|
||||
action,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
} => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_context_resolution_and_deferred_response(
|
||||
inspector_session_id.as_deref(),
|
||||
&action,
|
||||
&raw_json,
|
||||
deferred_response,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererPageCommand::RuntimeEnableEvents {
|
||||
inspector_session_id,
|
||||
} => self
|
||||
.runtime_enable_events(inspector_session_id.as_deref())
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererPageCommand::ApplyRuntimeProtocolState {
|
||||
inspector_session_id,
|
||||
session_restore_snapshots,
|
||||
isolated_worlds,
|
||||
stored_runtime_bindings,
|
||||
session_runtime_bindings,
|
||||
} => {
|
||||
self.apply_runtime_protocol_state(
|
||||
inspector_session_id.as_deref(),
|
||||
&session_restore_snapshots,
|
||||
&isolated_worlds,
|
||||
&stored_runtime_bindings,
|
||||
&session_runtime_bindings,
|
||||
)?;
|
||||
Ok(RendererPageReply::Unit)
|
||||
}
|
||||
RendererPageCommand::DetachRuntimeInspectorSession {
|
||||
inspector_session_id,
|
||||
pause_guard: _pause_guard,
|
||||
} => Ok(RendererPageReply::Bool(
|
||||
self.detach_runtime_inspector_session(inspector_session_id.as_deref()),
|
||||
)),
|
||||
RendererPageCommand::AddRuntimeBinding {
|
||||
inspector_session_id,
|
||||
name,
|
||||
execution_context_name,
|
||||
execution_context_id,
|
||||
} => {
|
||||
self.add_runtime_binding(
|
||||
inspector_session_id.as_deref(),
|
||||
&name,
|
||||
execution_context_name.as_deref(),
|
||||
execution_context_id,
|
||||
)?;
|
||||
Ok(RendererPageReply::Unit)
|
||||
}
|
||||
RendererPageCommand::CreateIsolatedWorld {
|
||||
name,
|
||||
grant_universal_access,
|
||||
@@ -429,19 +321,6 @@ impl PageVm {
|
||||
self.vm_mut().performance_metric_snapshot()?,
|
||||
)),
|
||||
),
|
||||
RendererPageCommand::DomDebuggerGetEventListeners {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
depth,
|
||||
pierce,
|
||||
} => Ok(RendererPageReply::DomDebuggerEventListeners(
|
||||
self.vm_mut().dom_debugger_event_listeners(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
depth,
|
||||
pierce,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::DomDebuggerConfigureEventListenerBreakpoint {
|
||||
inspector_session_id,
|
||||
breakpoint,
|
||||
@@ -528,15 +407,6 @@ impl PageVm {
|
||||
self.computed_style_properties_for_backend_node_id(backend_node_id)?,
|
||||
))
|
||||
}
|
||||
RendererPageCommand::ComputedStylePropertiesForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::ComputedStyleProperties(
|
||||
self.computed_style_properties_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::SetInlineStyleSheetTextForStyleSheetId {
|
||||
inspector_session_id,
|
||||
style_sheet_id,
|
||||
@@ -548,43 +418,17 @@ impl PageVm {
|
||||
&text,
|
||||
)
|
||||
.map(RendererPageReply::Bool),
|
||||
RendererPageCommand::ScrollObjectNodeIntoViewIfNeeded {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
rect,
|
||||
} => self
|
||||
.scroll_node_into_view_if_needed_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
rect,
|
||||
)
|
||||
.map(RendererPageReply::ScrollIntoViewResult),
|
||||
RendererPageCommand::ScrollBackendNodeIntoViewIfNeeded {
|
||||
backend_node_id,
|
||||
rect,
|
||||
} => self
|
||||
.scroll_backend_node_into_view_if_needed(backend_node_id, rect)
|
||||
.map(RendererPageReply::ScrollIntoViewResult),
|
||||
RendererPageCommand::ClientRectForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::OptionalDocumentNodeClientRect(
|
||||
self.client_rect_for_object_id(inspector_session_id.as_deref(), &object_id)?,
|
||||
)),
|
||||
RendererPageCommand::ClientRectForBackendNodeId { backend_node_id } => Ok(
|
||||
RendererPageReply::OptionalDocumentNodeClientRect(
|
||||
self.client_rect_for_backend_node_id(backend_node_id)?,
|
||||
),
|
||||
),
|
||||
RendererPageCommand::DocumentGeometryForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::OptionalDocumentNodeGeometry(
|
||||
self.document_geometry_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::DocumentGeometryForBackendNodeId { backend_node_id } => Ok(
|
||||
RendererPageReply::OptionalDocumentNodeGeometry(
|
||||
self.document_geometry_for_backend_node_id(backend_node_id)?,
|
||||
@@ -605,12 +449,6 @@ impl PageVm {
|
||||
ignore_pointer_events_none,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::NodeHasGeometryForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::OptionalBool(
|
||||
self.node_has_geometry_for_object_id(inspector_session_id.as_deref(), &object_id)?,
|
||||
)),
|
||||
RendererPageCommand::NodeHasGeometryForBackendNodeId { backend_node_id } => {
|
||||
Ok(RendererPageReply::OptionalBool(
|
||||
self.node_has_geometry_for_backend_node_id(backend_node_id)?,
|
||||
@@ -619,26 +457,6 @@ impl PageVm {
|
||||
RendererPageCommand::RemoveDocumentBackendNodeId { backend_node_id } => self
|
||||
.remove_document_backend_node_id(backend_node_id)
|
||||
.map(RendererPageReply::Bool),
|
||||
RendererPageCommand::DocumentNodeSnapshotForObjectId {
|
||||
inspector_session_id,
|
||||
include_whitespace,
|
||||
object_id,
|
||||
depth,
|
||||
pierce,
|
||||
} => {
|
||||
self.configure_document_dom_agent_session(
|
||||
inspector_session_id.as_deref(),
|
||||
include_whitespace,
|
||||
);
|
||||
Ok(RendererPageReply::OptionalDocumentNodeObjectSnapshot(
|
||||
Box::new(self.document_node_snapshot_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
depth,
|
||||
pierce,
|
||||
)?),
|
||||
))
|
||||
}
|
||||
RendererPageCommand::DocumentNodeSnapshotForBackendNodeId {
|
||||
backend_node_id,
|
||||
depth,
|
||||
@@ -963,35 +781,6 @@ impl PageVm {
|
||||
self.accessibility_node_payload_for_child_frame(&frame_id),
|
||||
))
|
||||
}
|
||||
RendererPageCommand::AccessibilityTreePayloadsForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::OptionalAccessibilityPayloadsForObjectId(
|
||||
self.accessibility_tree_payloads_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::AccessibilityNodeAndAncestorPayloadsForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::OptionalAccessibilityPayloadsForObjectId(
|
||||
self.accessibility_node_and_ancestor_payloads_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::AccessibilityPartialTreePayloadsForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
fetch_relatives,
|
||||
} => Ok(RendererPageReply::OptionalAccessibilityPayloadsForObjectId(
|
||||
self.accessibility_partial_tree_payloads_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
fetch_relatives,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::StyleSheetPayloadForStyleSheetId {
|
||||
inspector_session_id,
|
||||
style_sheet_id,
|
||||
@@ -1017,17 +806,6 @@ impl PageVm {
|
||||
self.outer_html_for_document(include_shadow_dom),
|
||||
))
|
||||
}
|
||||
RendererPageCommand::OuterHtmlForObjectId {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
include_shadow_dom,
|
||||
} => Ok(RendererPageReply::OptionalString(
|
||||
self.outer_html_for_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
include_shadow_dom,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::OuterHtmlForBackendNodeId {
|
||||
backend_node_id,
|
||||
include_shadow_dom,
|
||||
@@ -1046,28 +824,6 @@ impl PageVm {
|
||||
RendererPageCommand::CaptureScreenshot(request) => self
|
||||
.capture_screenshot(request)
|
||||
.map(RendererPageReply::CaptureScreenshot),
|
||||
RendererPageCommand::ResolveRuntimeObjectForBackendNodeId {
|
||||
inspector_session_id,
|
||||
backend_node_id,
|
||||
execution_context_id,
|
||||
object_group,
|
||||
} => Ok(RendererPageReply::RuntimeRemoteObjectResolution(
|
||||
self.resolve_runtime_object_for_backend_node_id(
|
||||
inspector_session_id.as_deref(),
|
||||
backend_node_id,
|
||||
execution_context_id,
|
||||
object_group.as_deref(),
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::ResolveBlobObject {
|
||||
inspector_session_id,
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::BlobUuid(
|
||||
self.vm_mut().blob_uuid_for_runtime_object_id(
|
||||
inspector_session_id.as_deref(),
|
||||
&object_id,
|
||||
)?,
|
||||
)),
|
||||
RendererPageCommand::BlobBytesForUuid { uuid } => Ok(
|
||||
RendererPageReply::OptionalBlobBytes(self.vm().blob_bytes_for_uuid(&uuid)),
|
||||
),
|
||||
@@ -1360,6 +1116,231 @@ impl PageVm {
|
||||
result
|
||||
}
|
||||
|
||||
fn dispatch_renderer_inspector_command(
|
||||
&mut self,
|
||||
envelope: RendererInspectorCommandEnvelope,
|
||||
) -> Result<RendererPageReply> {
|
||||
let (metadata, command) = envelope.into_parts();
|
||||
let inspector_session_id = metadata.session().wire_session_id();
|
||||
match command {
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessage { raw_json } => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session(
|
||||
inspector_session_id,
|
||||
&raw_json,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
raw_json,
|
||||
deferred_response,
|
||||
} => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_deferred_response(
|
||||
inspector_session_id,
|
||||
&raw_json,
|
||||
deferred_response,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererInspectorPageCommand::DispatchQueuedRuntimeInspectorCommand {
|
||||
command_id,
|
||||
} => self
|
||||
.dispatch_queued_runtime_inspector_command(command_id)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
action,
|
||||
raw_json,
|
||||
} => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_context_resolution(
|
||||
inspector_session_id,
|
||||
&action,
|
||||
&raw_json,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
action,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
} => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_context_resolution_and_deferred_response(
|
||||
inspector_session_id,
|
||||
&action,
|
||||
&raw_json,
|
||||
deferred_response,
|
||||
)
|
||||
.map(RendererRuntimeCommandOutput::from_messages)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererInspectorPageCommand::RuntimeEnableEvents => self
|
||||
.runtime_enable_events(inspector_session_id)
|
||||
.map(RendererPageReply::RuntimeInspectorProtocolMessages),
|
||||
RendererInspectorPageCommand::ApplyRuntimeProtocolState {
|
||||
session_restore_snapshots,
|
||||
isolated_worlds,
|
||||
stored_runtime_bindings,
|
||||
session_runtime_bindings,
|
||||
} => {
|
||||
self.apply_runtime_protocol_state(
|
||||
inspector_session_id,
|
||||
&session_restore_snapshots,
|
||||
&isolated_worlds,
|
||||
&stored_runtime_bindings,
|
||||
&session_runtime_bindings,
|
||||
)?;
|
||||
Ok(RendererPageReply::Unit)
|
||||
}
|
||||
RendererInspectorPageCommand::DetachRuntimeInspectorSession {
|
||||
pause_guard: _pause_guard,
|
||||
} => Ok(RendererPageReply::Bool(
|
||||
self.detach_runtime_inspector_session(inspector_session_id),
|
||||
)),
|
||||
RendererInspectorPageCommand::AddRuntimeBinding {
|
||||
name,
|
||||
execution_context_name,
|
||||
execution_context_id,
|
||||
} => {
|
||||
self.add_runtime_binding(
|
||||
inspector_session_id,
|
||||
&name,
|
||||
execution_context_name.as_deref(),
|
||||
execution_context_id,
|
||||
)?;
|
||||
Ok(RendererPageReply::Unit)
|
||||
}
|
||||
RendererInspectorPageCommand::DomDebuggerGetEventListeners {
|
||||
object_id,
|
||||
depth,
|
||||
pierce,
|
||||
} => Ok(RendererPageReply::DomDebuggerEventListeners(
|
||||
self.vm_mut().dom_debugger_event_listeners(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
depth,
|
||||
pierce,
|
||||
)?,
|
||||
)),
|
||||
RendererInspectorPageCommand::ComputedStylePropertiesForObjectId { object_id } => {
|
||||
Ok(RendererPageReply::ComputedStyleProperties(
|
||||
self.computed_style_properties_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
)?,
|
||||
))
|
||||
}
|
||||
RendererInspectorPageCommand::ScrollObjectNodeIntoViewIfNeeded { object_id, rect } => {
|
||||
self.scroll_node_into_view_if_needed_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
rect,
|
||||
)
|
||||
.map(RendererPageReply::ScrollIntoViewResult)
|
||||
}
|
||||
RendererInspectorPageCommand::ClientRectForObjectId { object_id } => {
|
||||
Ok(RendererPageReply::OptionalDocumentNodeClientRect(
|
||||
self.client_rect_for_object_id(inspector_session_id, &object_id)?,
|
||||
))
|
||||
}
|
||||
RendererInspectorPageCommand::DocumentGeometryForObjectId { object_id } => {
|
||||
Ok(RendererPageReply::OptionalDocumentNodeGeometry(
|
||||
self.document_geometry_for_object_id(inspector_session_id, &object_id)?,
|
||||
))
|
||||
}
|
||||
RendererInspectorPageCommand::NodeHasGeometryForObjectId { object_id } => {
|
||||
Ok(RendererPageReply::OptionalBool(
|
||||
self.node_has_geometry_for_object_id(inspector_session_id, &object_id)?,
|
||||
))
|
||||
}
|
||||
RendererInspectorPageCommand::FocusDocumentNodeForObjectId { object_id } => self
|
||||
.focus_document_node_for_object_id(inspector_session_id, &object_id)
|
||||
.map(RendererPageReply::DomFocusOutcome),
|
||||
RendererInspectorPageCommand::SetFileInputFilesForObjectId {
|
||||
object_id,
|
||||
files,
|
||||
append,
|
||||
} => Ok(RendererPageReply::OptionalBool(
|
||||
self.set_file_input_files_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
files,
|
||||
append,
|
||||
)?,
|
||||
)),
|
||||
RendererInspectorPageCommand::DocumentNodeSnapshotForObjectId {
|
||||
include_whitespace,
|
||||
object_id,
|
||||
depth,
|
||||
pierce,
|
||||
} => {
|
||||
self.configure_document_dom_agent_session(
|
||||
inspector_session_id,
|
||||
include_whitespace,
|
||||
);
|
||||
Ok(RendererPageReply::OptionalDocumentNodeObjectSnapshot(
|
||||
Box::new(self.document_node_snapshot_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
depth,
|
||||
pierce,
|
||||
)?),
|
||||
))
|
||||
}
|
||||
RendererInspectorPageCommand::AccessibilityTreePayloadsForObjectId { object_id } => {
|
||||
Ok(RendererPageReply::OptionalAccessibilityPayloadsForObjectId(
|
||||
self.accessibility_tree_payloads_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
)?,
|
||||
))
|
||||
}
|
||||
RendererInspectorPageCommand::AccessibilityNodeAndAncestorPayloadsForObjectId {
|
||||
object_id,
|
||||
} => Ok(RendererPageReply::OptionalAccessibilityPayloadsForObjectId(
|
||||
self.accessibility_node_and_ancestor_payloads_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
)?,
|
||||
)),
|
||||
RendererInspectorPageCommand::AccessibilityPartialTreePayloadsForObjectId {
|
||||
object_id,
|
||||
fetch_relatives,
|
||||
} => Ok(RendererPageReply::OptionalAccessibilityPayloadsForObjectId(
|
||||
self.accessibility_partial_tree_payloads_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
fetch_relatives,
|
||||
)?,
|
||||
)),
|
||||
RendererInspectorPageCommand::OuterHtmlForObjectId {
|
||||
object_id,
|
||||
include_shadow_dom,
|
||||
} => Ok(RendererPageReply::OptionalString(
|
||||
self.outer_html_for_object_id(
|
||||
inspector_session_id,
|
||||
&object_id,
|
||||
include_shadow_dom,
|
||||
)?,
|
||||
)),
|
||||
RendererInspectorPageCommand::ResolveRuntimeObjectForBackendNodeId {
|
||||
backend_node_id,
|
||||
execution_context_id,
|
||||
object_group,
|
||||
} => Ok(RendererPageReply::RuntimeRemoteObjectResolution(
|
||||
self.resolve_runtime_object_for_backend_node_id(
|
||||
inspector_session_id,
|
||||
backend_node_id,
|
||||
execution_context_id,
|
||||
object_group.as_deref(),
|
||||
)?,
|
||||
)),
|
||||
RendererInspectorPageCommand::ResolveBlobObject { object_id } => {
|
||||
Ok(RendererPageReply::BlobUuid(
|
||||
self.vm_mut()
|
||||
.blob_uuid_for_runtime_object_id(inspector_session_id, &object_id)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_cpu_throttling_delay_after_page_command(
|
||||
&self,
|
||||
started: Option<std::time::Instant>,
|
||||
@@ -1387,6 +1368,9 @@ impl PageVm {
|
||||
}
|
||||
|
||||
fn renderer_page_command_uses_cpu_throttling(command: &RendererPageCommand) -> bool {
|
||||
if let RendererPageCommand::Inspector(envelope) = command {
|
||||
return envelope.uses_cpu_throttling();
|
||||
}
|
||||
matches!(
|
||||
command,
|
||||
RendererPageCommand::EvaluateExpression { .. }
|
||||
@@ -1398,12 +1382,6 @@ fn renderer_page_command_uses_cpu_throttling(command: &RendererPageCommand) -> b
|
||||
| RendererPageCommand::DispatchDragEventAtPoint { .. }
|
||||
| RendererPageCommand::InsertTextIntoActiveControl(_)
|
||||
| RendererPageCommand::DispatchKeyEvent { .. }
|
||||
| RendererPageCommand::DispatchRuntimeProtocolMessage { .. }
|
||||
| RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse { .. }
|
||||
| RendererPageCommand::DispatchQueuedRuntimeInspectorCommand { .. }
|
||||
| RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolution { .. }
|
||||
| RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse { .. }
|
||||
| RendererPageCommand::DomDebuggerGetEventListeners { .. }
|
||||
| RendererPageCommand::DomDebuggerConfigureEventListenerBreakpoint { .. }
|
||||
| RendererPageCommand::DomDebuggerConfigureXhrBreakpoint { .. }
|
||||
| RendererPageCommand::DomDebuggerConfigureDomBreakpoint { .. }
|
||||
@@ -1414,7 +1392,6 @@ fn renderer_page_command_uses_cpu_throttling(command: &RendererPageCommand) -> b
|
||||
| RendererPageCommand::MutateDocumentBackendNodeAttribute { .. }
|
||||
| RendererPageCommand::EditDocumentNode { .. }
|
||||
| RendererPageCommand::FocusDocumentBackendNode { .. }
|
||||
| RendererPageCommand::FocusDocumentNodeForObjectId { .. }
|
||||
| RendererPageCommand::SetDocumentContent { .. }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -751,20 +751,23 @@ impl PageVm {
|
||||
let Some(command) = pause_bridge.claim_command_for_owner(command_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let mut first_dispatch = pause_bridge.first_dispatch_guard(&command);
|
||||
let inspector_session = command.metadata.session().clone();
|
||||
let inspector_session_id = inspector_session.wire_session_id();
|
||||
match command.owner_context_resolution_action.as_deref() {
|
||||
Some(action) => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_context_resolution_and_deferred_response(
|
||||
command.inspector_session_id.as_deref(),
|
||||
action,
|
||||
&command.raw_json,
|
||||
command.response,
|
||||
),
|
||||
None => self
|
||||
.dispatch_runtime_protocol_message_for_inspector_session_with_deferred_response(
|
||||
command.inspector_session_id.as_deref(),
|
||||
&command.raw_json,
|
||||
command.response,
|
||||
),
|
||||
Some(action) => self.dispatch_queued_runtime_protocol_message_with_context_resolution(
|
||||
inspector_session_id,
|
||||
action,
|
||||
&command.raw_json,
|
||||
command.response,
|
||||
&mut first_dispatch,
|
||||
),
|
||||
None => self.dispatch_runtime_protocol_message_with_command_output_at_first_dispatch(
|
||||
inspector_session_id,
|
||||
&command.raw_json,
|
||||
command.response,
|
||||
Some(&mut first_dispatch),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,11 +830,65 @@ impl PageVm {
|
||||
)
|
||||
}
|
||||
|
||||
fn dispatch_queued_runtime_protocol_message_with_context_resolution(
|
||||
&mut self,
|
||||
inspector_session_id: Option<&str>,
|
||||
action: &str,
|
||||
raw_json: &str,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
first_dispatch: &mut crate::script_vm::inspector_pause::RendererInspectorFirstDispatchGuard,
|
||||
) -> Result<Vec<RendererRuntimeInspectorMessage>> {
|
||||
if action == "evaluate" {
|
||||
// The child-default fast path dispatches while probing, so expose
|
||||
// its first-dispatch edge before entering that helper.
|
||||
first_dispatch.release();
|
||||
if let Some(messages) = self.try_dispatch_child_default_runtime_evaluate(raw_json)? {
|
||||
let call_id = deferred_response.call_id();
|
||||
if let Some(message) = messages.into_iter().next()
|
||||
&& let Err(message) =
|
||||
deferred_response.send(message.into_v8_inspector_message())
|
||||
{
|
||||
tracing::debug!(
|
||||
call_id,
|
||||
message = ?message,
|
||||
"dropping child-frame runtime response because deferred receiver was closed"
|
||||
);
|
||||
}
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
}
|
||||
let prepared_json =
|
||||
self.prepare_runtime_protocol_message_with_context_resolution(action, raw_json)?;
|
||||
self.dispatch_runtime_protocol_message_with_command_output_at_first_dispatch(
|
||||
inspector_session_id,
|
||||
&prepared_json,
|
||||
deferred_response,
|
||||
Some(first_dispatch),
|
||||
)
|
||||
}
|
||||
|
||||
fn dispatch_runtime_protocol_message_with_command_output(
|
||||
&mut self,
|
||||
inspector_session_id: Option<&str>,
|
||||
raw_json: &str,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
) -> Result<Vec<RendererRuntimeInspectorMessage>> {
|
||||
self.dispatch_runtime_protocol_message_with_command_output_at_first_dispatch(
|
||||
inspector_session_id,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn dispatch_runtime_protocol_message_with_command_output_at_first_dispatch(
|
||||
&mut self,
|
||||
inspector_session_id: Option<&str>,
|
||||
raw_json: &str,
|
||||
mut deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
mut first_dispatch: Option<
|
||||
&mut crate::script_vm::inspector_pause::RendererInspectorFirstDispatchGuard,
|
||||
>,
|
||||
) -> Result<Vec<RendererRuntimeInspectorMessage>> {
|
||||
ensure!(
|
||||
self.pending_runtime_command_output.is_none(),
|
||||
@@ -868,6 +925,9 @@ impl PageVm {
|
||||
lifecycle_target:
|
||||
PageVmRuntimeCommandLifecycleTarget::AwaitingExplicitDocumentReplacement,
|
||||
});
|
||||
if let Some(first_dispatch) = first_dispatch.as_mut() {
|
||||
first_dispatch.release();
|
||||
}
|
||||
let dispatch_result = self
|
||||
.vm_mut()
|
||||
.dispatch_inspector_protocol_message_for_session_with_deferred_response_and_command_output(
|
||||
|
||||
@@ -3528,8 +3528,267 @@ pub enum RendererDomDebuggerDomBreakpointResolution {
|
||||
UnknownType(String),
|
||||
}
|
||||
|
||||
/// Chromium routes Page DevTools work through separate main-thread and IO
|
||||
/// session ingress paths. Ordering is guaranteed within one session and one
|
||||
/// route, while an IO command may overtake main-thread work that has not
|
||||
/// reached its first dispatch yet.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum RendererInspectorCommandRoute {
|
||||
MainThread,
|
||||
Io,
|
||||
}
|
||||
|
||||
/// The command owns its `(Page, session, route)` lane until its first access to
|
||||
/// the frontend V8 Inspector session. Protocol response completion is not part
|
||||
/// of this lifetime: V8 may complete a response asynchronously, and holding the
|
||||
/// lane for that response could prevent a later resume command from running.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RendererInspectorFirstDispatchLifecycle {
|
||||
OrderedUntilFirstDispatch,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RendererInspectorCommandMetadata {
|
||||
session: DevToolsSessionKey,
|
||||
route: RendererInspectorCommandRoute,
|
||||
first_dispatch: RendererInspectorFirstDispatchLifecycle,
|
||||
}
|
||||
|
||||
impl RendererInspectorCommandMetadata {
|
||||
pub fn new(inspector_session_id: Option<String>, route: RendererInspectorCommandRoute) -> Self {
|
||||
Self {
|
||||
session: DevToolsSessionKey::from_wire_session_id(
|
||||
inspector_session_id
|
||||
.as_deref()
|
||||
.filter(|session_id| !session_id.is_empty()),
|
||||
),
|
||||
route,
|
||||
first_dispatch: RendererInspectorFirstDispatchLifecycle::OrderedUntilFirstDispatch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session(&self) -> &DevToolsSessionKey {
|
||||
&self.session
|
||||
}
|
||||
|
||||
pub fn route(&self) -> RendererInspectorCommandRoute {
|
||||
self.route
|
||||
}
|
||||
|
||||
pub fn first_dispatch_lifecycle(&self) -> RendererInspectorFirstDispatchLifecycle {
|
||||
self.first_dispatch
|
||||
}
|
||||
}
|
||||
|
||||
/// Strongly typed DevToolsSession ingress. The payload deliberately does not
|
||||
/// carry another session id: every operation that accesses a frontend
|
||||
/// `V8InspectorSession` must obtain its identity and dispatch policy from this
|
||||
/// envelope.
|
||||
pub struct RendererInspectorCommandEnvelope {
|
||||
metadata: RendererInspectorCommandMetadata,
|
||||
command: RendererInspectorPageCommand,
|
||||
}
|
||||
|
||||
impl RendererInspectorCommandEnvelope {
|
||||
fn new(
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
command: RendererInspectorPageCommand,
|
||||
) -> Self {
|
||||
Self::with_metadata(
|
||||
RendererInspectorCommandMetadata::new(inspector_session_id, route),
|
||||
command,
|
||||
)
|
||||
}
|
||||
|
||||
fn with_metadata(
|
||||
metadata: RendererInspectorCommandMetadata,
|
||||
command: RendererInspectorPageCommand,
|
||||
) -> Self {
|
||||
Self { metadata, command }
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> &RendererInspectorCommandMetadata {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
pub(crate) fn into_parts(
|
||||
self,
|
||||
) -> (
|
||||
RendererInspectorCommandMetadata,
|
||||
RendererInspectorPageCommand,
|
||||
) {
|
||||
(self.metadata, self.command)
|
||||
}
|
||||
|
||||
pub(crate) fn requires_materialized_child_realms(&self) -> bool {
|
||||
matches!(
|
||||
&self.command,
|
||||
RendererInspectorPageCommand::RuntimeEnableEvents
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn cdp_nav_timing_label(&self) -> Option<&'static str> {
|
||||
match &self.command {
|
||||
RendererInspectorPageCommand::DispatchQueuedRuntimeInspectorCommand { .. } => {
|
||||
Some("DispatchQueuedRuntimeInspectorCommand")
|
||||
}
|
||||
RendererInspectorPageCommand::RuntimeEnableEvents => Some("RuntimeEnableEvents"),
|
||||
RendererInspectorPageCommand::ApplyRuntimeProtocolState { .. } => {
|
||||
Some("ApplyRuntimeProtocolState")
|
||||
}
|
||||
RendererInspectorPageCommand::DetachRuntimeInspectorSession { .. } => {
|
||||
Some("DetachRuntimeInspectorSession")
|
||||
}
|
||||
RendererInspectorPageCommand::DocumentNodeSnapshotForObjectId { .. } => {
|
||||
Some("DocumentNodeSnapshotForObjectId")
|
||||
}
|
||||
RendererInspectorPageCommand::AccessibilityTreePayloadsForObjectId { .. } => {
|
||||
Some("AccessibilityTreePayloadsForObjectId")
|
||||
}
|
||||
RendererInspectorPageCommand::AccessibilityNodeAndAncestorPayloadsForObjectId {
|
||||
..
|
||||
} => Some("AccessibilityNodeAndAncestorPayloadsForObjectId"),
|
||||
RendererInspectorPageCommand::AccessibilityPartialTreePayloadsForObjectId {
|
||||
..
|
||||
} => Some("AccessibilityPartialTreePayloadsForObjectId"),
|
||||
RendererInspectorPageCommand::OuterHtmlForObjectId { .. } => {
|
||||
Some("OuterHtmlForObjectId")
|
||||
}
|
||||
RendererInspectorPageCommand::ScrollObjectNodeIntoViewIfNeeded { .. } => {
|
||||
Some("ScrollObjectNodeIntoViewIfNeeded")
|
||||
}
|
||||
RendererInspectorPageCommand::ClientRectForObjectId { .. } => {
|
||||
Some("ClientRectForObjectId")
|
||||
}
|
||||
RendererInspectorPageCommand::DocumentGeometryForObjectId { .. } => {
|
||||
Some("DocumentGeometryForObjectId")
|
||||
}
|
||||
RendererInspectorPageCommand::NodeHasGeometryForObjectId { .. } => {
|
||||
Some("NodeHasGeometryForObjectId")
|
||||
}
|
||||
RendererInspectorPageCommand::SetFileInputFilesForObjectId { .. } => {
|
||||
Some("SetFileInputFilesForObjectId")
|
||||
}
|
||||
RendererInspectorPageCommand::ResolveRuntimeObjectForBackendNodeId { .. } => {
|
||||
Some("ResolveRuntimeObjectForBackendNodeId")
|
||||
}
|
||||
RendererInspectorPageCommand::ResolveBlobObject { .. } => Some("ResolveBlobObject"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn uses_cpu_throttling(&self) -> bool {
|
||||
matches!(
|
||||
&self.command,
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessage { .. }
|
||||
| RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse { .. }
|
||||
| RendererInspectorPageCommand::DispatchQueuedRuntimeInspectorCommand { .. }
|
||||
| RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithContextResolution { .. }
|
||||
| RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse { .. }
|
||||
| RendererInspectorPageCommand::DomDebuggerGetEventListeners { .. }
|
||||
| RendererInspectorPageCommand::FocusDocumentNodeForObjectId { .. }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum RendererInspectorPageCommand {
|
||||
DispatchRuntimeProtocolMessage {
|
||||
raw_json: String,
|
||||
},
|
||||
DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
},
|
||||
DispatchQueuedRuntimeInspectorCommand {
|
||||
command_id: u64,
|
||||
},
|
||||
DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
action: String,
|
||||
raw_json: String,
|
||||
},
|
||||
DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
action: String,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
},
|
||||
RuntimeEnableEvents,
|
||||
ApplyRuntimeProtocolState {
|
||||
session_restore_snapshots: Vec<RendererInspectorSessionRestoreSnapshot>,
|
||||
isolated_worlds: Vec<crate::protocol_types::RuntimeIsolatedWorldDefinition>,
|
||||
stored_runtime_bindings: Vec<crate::protocol_types::RuntimeBindingRegistration>,
|
||||
session_runtime_bindings: Vec<crate::protocol_types::RuntimeBindingRegistration>,
|
||||
},
|
||||
DetachRuntimeInspectorSession {
|
||||
pause_guard: RendererRuntimeInspectorSessionDetachGuard,
|
||||
},
|
||||
AddRuntimeBinding {
|
||||
name: String,
|
||||
execution_context_name: Option<String>,
|
||||
execution_context_id: Option<i64>,
|
||||
},
|
||||
DomDebuggerGetEventListeners {
|
||||
object_id: String,
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
},
|
||||
ComputedStylePropertiesForObjectId {
|
||||
object_id: String,
|
||||
},
|
||||
ScrollObjectNodeIntoViewIfNeeded {
|
||||
object_id: String,
|
||||
rect: Option<moli_page_types::DomScrollIntoViewRect>,
|
||||
},
|
||||
ClientRectForObjectId {
|
||||
object_id: String,
|
||||
},
|
||||
DocumentGeometryForObjectId {
|
||||
object_id: String,
|
||||
},
|
||||
NodeHasGeometryForObjectId {
|
||||
object_id: String,
|
||||
},
|
||||
FocusDocumentNodeForObjectId {
|
||||
object_id: String,
|
||||
},
|
||||
SetFileInputFilesForObjectId {
|
||||
object_id: String,
|
||||
files: Vec<crate::dom::native::SelectedFile>,
|
||||
append: bool,
|
||||
},
|
||||
DocumentNodeSnapshotForObjectId {
|
||||
include_whitespace: bool,
|
||||
object_id: String,
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
},
|
||||
AccessibilityTreePayloadsForObjectId {
|
||||
object_id: String,
|
||||
},
|
||||
AccessibilityNodeAndAncestorPayloadsForObjectId {
|
||||
object_id: String,
|
||||
},
|
||||
AccessibilityPartialTreePayloadsForObjectId {
|
||||
object_id: String,
|
||||
fetch_relatives: bool,
|
||||
},
|
||||
OuterHtmlForObjectId {
|
||||
object_id: String,
|
||||
include_shadow_dom: bool,
|
||||
},
|
||||
ResolveRuntimeObjectForBackendNodeId {
|
||||
backend_node_id: u32,
|
||||
execution_context_id: Option<i64>,
|
||||
object_group: Option<String>,
|
||||
},
|
||||
ResolveBlobObject {
|
||||
object_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub enum RendererPageCommand {
|
||||
Inspector(RendererInspectorCommandEnvelope),
|
||||
EvaluateExpression {
|
||||
expression: String,
|
||||
await_promise: bool,
|
||||
@@ -3616,36 +3875,6 @@ pub enum RendererPageCommand {
|
||||
auto_repeat: bool,
|
||||
should_insert_text: bool,
|
||||
},
|
||||
DispatchRuntimeProtocolMessage {
|
||||
inspector_session_id: Option<String>,
|
||||
raw_json: String,
|
||||
},
|
||||
DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
inspector_session_id: Option<String>,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
},
|
||||
DispatchQueuedRuntimeInspectorCommand {
|
||||
command_id: u64,
|
||||
inspector_session_id: Option<String>,
|
||||
},
|
||||
DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
inspector_session_id: Option<String>,
|
||||
action: String,
|
||||
raw_json: String,
|
||||
},
|
||||
DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
inspector_session_id: Option<String>,
|
||||
action: String,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
},
|
||||
DomDebuggerGetEventListeners {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
},
|
||||
DomDebuggerConfigureEventListenerBreakpoint {
|
||||
inspector_session_id: Option<String>,
|
||||
breakpoint: RendererDomDebuggerEventListenerBreakpoint,
|
||||
@@ -3662,26 +3891,6 @@ pub enum RendererPageCommand {
|
||||
breakpoint_type: String,
|
||||
enabled: bool,
|
||||
},
|
||||
RuntimeEnableEvents {
|
||||
inspector_session_id: Option<String>,
|
||||
},
|
||||
ApplyRuntimeProtocolState {
|
||||
inspector_session_id: Option<String>,
|
||||
session_restore_snapshots: Vec<RendererInspectorSessionRestoreSnapshot>,
|
||||
isolated_worlds: Vec<crate::protocol_types::RuntimeIsolatedWorldDefinition>,
|
||||
stored_runtime_bindings: Vec<crate::protocol_types::RuntimeBindingRegistration>,
|
||||
session_runtime_bindings: Vec<crate::protocol_types::RuntimeBindingRegistration>,
|
||||
},
|
||||
DetachRuntimeInspectorSession {
|
||||
inspector_session_id: Option<String>,
|
||||
pause_guard: RendererRuntimeInspectorSessionDetachGuard,
|
||||
},
|
||||
AddRuntimeBinding {
|
||||
inspector_session_id: Option<String>,
|
||||
name: String,
|
||||
execution_context_name: Option<String>,
|
||||
execution_context_id: Option<i64>,
|
||||
},
|
||||
CreateIsolatedWorld {
|
||||
name: String,
|
||||
grant_universal_access: bool,
|
||||
@@ -3741,35 +3950,18 @@ pub enum RendererPageCommand {
|
||||
ComputedStylePropertiesForBackendNodeId {
|
||||
backend_node_id: u32,
|
||||
},
|
||||
ComputedStylePropertiesForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
SetInlineStyleSheetTextForStyleSheetId {
|
||||
inspector_session_id: Option<String>,
|
||||
style_sheet_id: String,
|
||||
text: String,
|
||||
},
|
||||
ScrollObjectNodeIntoViewIfNeeded {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
rect: Option<moli_page_types::DomScrollIntoViewRect>,
|
||||
},
|
||||
ScrollBackendNodeIntoViewIfNeeded {
|
||||
backend_node_id: u32,
|
||||
rect: Option<moli_page_types::DomScrollIntoViewRect>,
|
||||
},
|
||||
ClientRectForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
ClientRectForBackendNodeId {
|
||||
backend_node_id: u32,
|
||||
},
|
||||
DocumentGeometryForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
DocumentGeometryForBackendNodeId {
|
||||
backend_node_id: u32,
|
||||
},
|
||||
@@ -3780,10 +3972,6 @@ pub enum RendererPageCommand {
|
||||
include_user_agent_shadow_dom: bool,
|
||||
ignore_pointer_events_none: bool,
|
||||
},
|
||||
NodeHasGeometryForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
NodeHasGeometryForBackendNodeId {
|
||||
backend_node_id: u32,
|
||||
},
|
||||
@@ -3801,10 +3989,6 @@ pub enum RendererPageCommand {
|
||||
FocusDocumentBackendNode {
|
||||
backend_node_id: u32,
|
||||
},
|
||||
FocusDocumentNodeForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
TriggerAutofill(RendererAutofillTriggerRequest),
|
||||
ResetNavigationHistory,
|
||||
SetFileInputFilesForBackendNodeId {
|
||||
@@ -3812,19 +3996,6 @@ pub enum RendererPageCommand {
|
||||
files: Vec<crate::dom::native::SelectedFile>,
|
||||
append: bool,
|
||||
},
|
||||
SetFileInputFilesForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
files: Vec<crate::dom::native::SelectedFile>,
|
||||
append: bool,
|
||||
},
|
||||
DocumentNodeSnapshotForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
include_whitespace: bool,
|
||||
object_id: String,
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
},
|
||||
DocumentNodeSnapshotForBackendNodeId {
|
||||
backend_node_id: u32,
|
||||
depth: i32,
|
||||
@@ -3964,19 +4135,6 @@ pub enum RendererPageCommand {
|
||||
AccessibilityNodePayloadForChildFrame {
|
||||
frame_id: String,
|
||||
},
|
||||
AccessibilityTreePayloadsForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
AccessibilityNodeAndAncestorPayloadsForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
AccessibilityPartialTreePayloadsForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
fetch_relatives: bool,
|
||||
},
|
||||
StyleSheetPayloadForStyleSheetId {
|
||||
inspector_session_id: Option<String>,
|
||||
style_sheet_id: String,
|
||||
@@ -3990,11 +4148,6 @@ pub enum RendererPageCommand {
|
||||
OuterHtmlForDocument {
|
||||
include_shadow_dom: bool,
|
||||
},
|
||||
OuterHtmlForObjectId {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
include_shadow_dom: bool,
|
||||
},
|
||||
OuterHtmlForBackendNodeId {
|
||||
backend_node_id: u32,
|
||||
include_shadow_dom: bool,
|
||||
@@ -4005,16 +4158,6 @@ pub enum RendererPageCommand {
|
||||
SerializeHtml,
|
||||
LayoutMetrics,
|
||||
CaptureScreenshot(RendererCaptureScreenshotRequest),
|
||||
ResolveRuntimeObjectForBackendNodeId {
|
||||
inspector_session_id: Option<String>,
|
||||
backend_node_id: u32,
|
||||
execution_context_id: Option<i64>,
|
||||
object_group: Option<String>,
|
||||
},
|
||||
ResolveBlobObject {
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
},
|
||||
BlobBytesForUuid {
|
||||
uuid: String,
|
||||
},
|
||||
@@ -4169,39 +4312,353 @@ pub enum RendererRuntimeRemoteObjectResolution {
|
||||
}
|
||||
|
||||
impl RendererPageCommand {
|
||||
/// Returns the V8 Inspector session whose message order owns this command's
|
||||
/// first-dispatch boundary. Direct Inspector commands and the owner fallback
|
||||
/// for pause-routable commands share the same session lane, preventing
|
||||
/// same-session overtaking without coupling independent sessions.
|
||||
pub(super) fn first_dispatch_inspector_session(&self) -> Option<DevToolsSessionKey> {
|
||||
let inspector_session_id = match self {
|
||||
Self::DispatchRuntimeProtocolMessage {
|
||||
inspector_session_id,
|
||||
..
|
||||
}
|
||||
| Self::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
inspector_session_id,
|
||||
..
|
||||
}
|
||||
| Self::DispatchQueuedRuntimeInspectorCommand {
|
||||
inspector_session_id,
|
||||
..
|
||||
}
|
||||
| Self::DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
inspector_session_id,
|
||||
..
|
||||
}
|
||||
| Self::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
inspector_session_id,
|
||||
..
|
||||
} => inspector_session_id.as_deref(),
|
||||
_ => return None,
|
||||
};
|
||||
Some(DevToolsSessionKey::from_wire_session_id(
|
||||
fn inspector_command(
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
command: RendererInspectorPageCommand,
|
||||
) -> Self {
|
||||
Self::Inspector(RendererInspectorCommandEnvelope::new(
|
||||
inspector_session_id,
|
||||
route,
|
||||
command,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn dispatch_runtime_protocol_message(
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
raw_json: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
route,
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessage { raw_json },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dispatch_runtime_protocol_message_with_deferred_response(
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
route,
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
raw_json,
|
||||
deferred_response,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dispatch_queued_runtime_inspector_command(
|
||||
metadata: RendererInspectorCommandMetadata,
|
||||
command_id: u64,
|
||||
) -> Self {
|
||||
Self::Inspector(RendererInspectorCommandEnvelope::with_metadata(
|
||||
metadata,
|
||||
RendererInspectorPageCommand::DispatchQueuedRuntimeInspectorCommand { command_id },
|
||||
))
|
||||
}
|
||||
|
||||
pub fn dispatch_runtime_protocol_message_with_context_resolution(
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
action: String,
|
||||
raw_json: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
route,
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
action,
|
||||
raw_json,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dispatch_runtime_protocol_message_with_context_resolution_and_deferred_response(
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
action: String,
|
||||
raw_json: String,
|
||||
deferred_response: RendererRuntimeInspectorResponseSender,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
route,
|
||||
RendererInspectorPageCommand::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
action,
|
||||
raw_json,
|
||||
deferred_response,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn runtime_enable_events(inspector_session_id: Option<String>) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::RuntimeEnableEvents,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn apply_runtime_protocol_state(
|
||||
inspector_session_id: Option<String>,
|
||||
session_restore_snapshots: Vec<RendererInspectorSessionRestoreSnapshot>,
|
||||
isolated_worlds: Vec<crate::protocol_types::RuntimeIsolatedWorldDefinition>,
|
||||
stored_runtime_bindings: Vec<crate::protocol_types::RuntimeBindingRegistration>,
|
||||
session_runtime_bindings: Vec<crate::protocol_types::RuntimeBindingRegistration>,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::ApplyRuntimeProtocolState {
|
||||
session_restore_snapshots,
|
||||
isolated_worlds,
|
||||
stored_runtime_bindings,
|
||||
session_runtime_bindings,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn detach_runtime_inspector_session(
|
||||
inspector_session_id: Option<String>,
|
||||
pause_guard: RendererRuntimeInspectorSessionDetachGuard,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
RendererInspectorPageCommand::DetachRuntimeInspectorSession { pause_guard },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_runtime_binding(
|
||||
inspector_session_id: Option<String>,
|
||||
name: String,
|
||||
execution_context_name: Option<String>,
|
||||
execution_context_id: Option<i64>,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::AddRuntimeBinding {
|
||||
name,
|
||||
execution_context_name,
|
||||
execution_context_id,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dom_debugger_get_event_listeners(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::DomDebuggerGetEventListeners {
|
||||
object_id,
|
||||
depth,
|
||||
pierce,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn computed_style_properties_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::ComputedStylePropertiesForObjectId { object_id },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn scroll_object_node_into_view_if_needed(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
rect: Option<moli_page_types::DomScrollIntoViewRect>,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::ScrollObjectNodeIntoViewIfNeeded { object_id, rect },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn client_rect_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::ClientRectForObjectId { object_id },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn document_geometry_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::DocumentGeometryForObjectId { object_id },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn node_has_geometry_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::NodeHasGeometryForObjectId { object_id },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn focus_document_node_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::FocusDocumentNodeForObjectId { object_id },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_file_input_files_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
files: Vec<crate::dom::native::SelectedFile>,
|
||||
append: bool,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::SetFileInputFilesForObjectId {
|
||||
object_id,
|
||||
files,
|
||||
append,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn document_node_snapshot_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
include_whitespace: bool,
|
||||
object_id: String,
|
||||
depth: i32,
|
||||
pierce: bool,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::DocumentNodeSnapshotForObjectId {
|
||||
include_whitespace,
|
||||
object_id,
|
||||
depth,
|
||||
pierce,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn accessibility_tree_payloads_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::AccessibilityTreePayloadsForObjectId { object_id },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn accessibility_node_and_ancestor_payloads_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::AccessibilityNodeAndAncestorPayloadsForObjectId {
|
||||
object_id,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn accessibility_partial_tree_payloads_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
fetch_relatives: bool,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::AccessibilityPartialTreePayloadsForObjectId {
|
||||
object_id,
|
||||
fetch_relatives,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn outer_html_for_object_id(
|
||||
inspector_session_id: Option<String>,
|
||||
object_id: String,
|
||||
include_shadow_dom: bool,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::OuterHtmlForObjectId {
|
||||
object_id,
|
||||
include_shadow_dom,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resolve_runtime_object_for_backend_node_id(
|
||||
inspector_session_id: Option<String>,
|
||||
backend_node_id: u32,
|
||||
execution_context_id: Option<i64>,
|
||||
object_group: Option<String>,
|
||||
) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::ResolveRuntimeObjectForBackendNodeId {
|
||||
backend_node_id,
|
||||
execution_context_id,
|
||||
object_group,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resolve_blob_object(inspector_session_id: Option<String>, object_id: String) -> Self {
|
||||
Self::inspector_command(
|
||||
inspector_session_id,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
RendererInspectorPageCommand::ResolveBlobObject { object_id },
|
||||
)
|
||||
}
|
||||
|
||||
/// Metadata is structurally present for every command that can access a
|
||||
/// frontend V8 Inspector session; no command-variant allowlist is involved.
|
||||
pub fn inspector_metadata(&self) -> Option<&RendererInspectorCommandMetadata> {
|
||||
match self {
|
||||
Self::Inspector(envelope) => Some(envelope.metadata()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn interruptible_by_javascript_dialog(&self) -> bool {
|
||||
#[cfg(test)]
|
||||
if matches!(self, Self::TakeDocumentLifecycleEvents) {
|
||||
@@ -4220,13 +4677,8 @@ impl RendererPageCommand {
|
||||
|
||||
pub(crate) fn cdp_nav_timing_label(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::RuntimeEnableEvents { .. } => Some("RuntimeEnableEvents"),
|
||||
Self::DispatchQueuedRuntimeInspectorCommand { .. } => {
|
||||
Some("DispatchQueuedRuntimeInspectorCommand")
|
||||
}
|
||||
Self::Inspector(envelope) => envelope.cdp_nav_timing_label(),
|
||||
Self::DocumentStorageKeySnapshot => Some("DocumentStorageKeySnapshot"),
|
||||
Self::ApplyRuntimeProtocolState { .. } => Some("ApplyRuntimeProtocolState"),
|
||||
Self::DetachRuntimeInspectorSession { .. } => Some("DetachRuntimeInspectorSession"),
|
||||
Self::CreateIsolatedWorldRuntimeActivity { .. } => {
|
||||
Some("CreateIsolatedWorldRuntimeActivity")
|
||||
}
|
||||
@@ -4242,7 +4694,6 @@ impl RendererPageCommand {
|
||||
Self::ChildFrameDocumentRootNodeReference { .. } => {
|
||||
Some("ChildFrameDocumentRootNodeReference")
|
||||
}
|
||||
Self::DocumentNodeSnapshotForObjectId { .. } => Some("DocumentNodeSnapshotForObjectId"),
|
||||
Self::DocumentNodeSnapshotForBackendNodeId { .. } => {
|
||||
Some("DocumentNodeSnapshotForBackendNodeId")
|
||||
}
|
||||
@@ -4316,36 +4767,20 @@ impl RendererPageCommand {
|
||||
Self::AccessibilityNodePayloadForChildFrame { .. } => {
|
||||
Some("AccessibilityNodePayloadForChildFrame")
|
||||
}
|
||||
Self::AccessibilityTreePayloadsForObjectId { .. } => {
|
||||
Some("AccessibilityTreePayloadsForObjectId")
|
||||
}
|
||||
Self::AccessibilityNodeAndAncestorPayloadsForObjectId { .. } => {
|
||||
Some("AccessibilityNodeAndAncestorPayloadsForObjectId")
|
||||
}
|
||||
Self::AccessibilityPartialTreePayloadsForObjectId { .. } => {
|
||||
Some("AccessibilityPartialTreePayloadsForObjectId")
|
||||
}
|
||||
Self::OuterHtmlForDocument { .. } => Some("OuterHtmlForDocument"),
|
||||
Self::OuterHtmlForObjectId { .. } => Some("OuterHtmlForObjectId"),
|
||||
Self::OuterHtmlForBackendNodeId { .. } => Some("OuterHtmlForBackendNodeId"),
|
||||
Self::RenderPageDump { .. } => Some("RenderPageDump"),
|
||||
Self::SerializeHtml => Some("SerializeHtml"),
|
||||
Self::LayoutMetrics => Some("LayoutMetrics"),
|
||||
Self::CaptureScreenshot(_) => Some("CaptureScreenshot"),
|
||||
Self::ScrollObjectNodeIntoViewIfNeeded { .. } => {
|
||||
Some("ScrollObjectNodeIntoViewIfNeeded")
|
||||
}
|
||||
Self::ScrollBackendNodeIntoViewIfNeeded { .. } => {
|
||||
Some("ScrollBackendNodeIntoViewIfNeeded")
|
||||
}
|
||||
Self::ClientRectForObjectId { .. } => Some("ClientRectForObjectId"),
|
||||
Self::ClientRectForBackendNodeId { .. } => Some("ClientRectForBackendNodeId"),
|
||||
Self::DocumentGeometryForObjectId { .. } => Some("DocumentGeometryForObjectId"),
|
||||
Self::DocumentGeometryForBackendNodeId { .. } => {
|
||||
Some("DocumentGeometryForBackendNodeId")
|
||||
}
|
||||
Self::DocumentHitTest { .. } => Some("DocumentHitTest"),
|
||||
Self::NodeHasGeometryForObjectId { .. } => Some("NodeHasGeometryForObjectId"),
|
||||
Self::NodeHasGeometryForBackendNodeId { .. } => Some("NodeHasGeometryForBackendNodeId"),
|
||||
Self::RemoveDocumentBackendNodeId { .. } => Some("RemoveDocumentBackendNodeId"),
|
||||
Self::EditDocumentNode { .. } => Some("EditDocumentNode"),
|
||||
@@ -4356,11 +4791,6 @@ impl RendererPageCommand {
|
||||
Self::SetFileInputFilesForBackendNodeId { .. } => {
|
||||
Some("SetFileInputFilesForBackendNodeId")
|
||||
}
|
||||
Self::SetFileInputFilesForObjectId { .. } => Some("SetFileInputFilesForObjectId"),
|
||||
Self::ResolveRuntimeObjectForBackendNodeId { .. } => {
|
||||
Some("ResolveRuntimeObjectForBackendNodeId")
|
||||
}
|
||||
Self::ResolveBlobObject { .. } => Some("ResolveBlobObject"),
|
||||
Self::BlobBytesForUuid { .. } => Some("BlobBytesForUuid"),
|
||||
Self::DocumentFrontendNodeIdsForBackendNodeIds { .. } => {
|
||||
Some("DocumentFrontendNodeIdsForBackendNodeIds")
|
||||
@@ -4377,6 +4807,87 @@ impl RendererPageCommand {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod renderer_inspector_command_envelope_tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_metadata(
|
||||
command: &RendererPageCommand,
|
||||
session: DevToolsSessionKey,
|
||||
route: RendererInspectorCommandRoute,
|
||||
) {
|
||||
let metadata = command
|
||||
.inspector_metadata()
|
||||
.expect("every frontend V8 Inspector operation must carry command metadata");
|
||||
assert_eq!(metadata.session(), &session);
|
||||
assert_eq!(metadata.route(), route);
|
||||
assert_eq!(
|
||||
metadata.first_dispatch_lifecycle(),
|
||||
RendererInspectorFirstDispatchLifecycle::OrderedUntilFirstDispatch
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_special_and_runtime_object_commands_share_one_inspector_boundary() {
|
||||
let attached = Some("SID-envelope".to_owned());
|
||||
let main_thread_commands = [
|
||||
RendererPageCommand::runtime_enable_events(attached.clone()),
|
||||
RendererPageCommand::apply_runtime_protocol_state(
|
||||
attached.clone(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
),
|
||||
RendererPageCommand::add_runtime_binding(
|
||||
attached.clone(),
|
||||
"binding".to_owned(),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
RendererPageCommand::computed_style_properties_for_object_id(
|
||||
attached,
|
||||
"runtime-object".to_owned(),
|
||||
),
|
||||
];
|
||||
for command in &main_thread_commands {
|
||||
assert_metadata(
|
||||
command,
|
||||
DevToolsSessionKey::Attached("SID-envelope".to_owned()),
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
);
|
||||
}
|
||||
|
||||
let io_command = RendererPageCommand::dispatch_runtime_protocol_message(
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
r#"{"id":1,"method":"Runtime.terminateExecution"}"#.to_owned(),
|
||||
);
|
||||
assert_metadata(
|
||||
&io_command,
|
||||
DevToolsSessionKey::Primary,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
);
|
||||
|
||||
assert!(
|
||||
RendererPageCommand::PageDiagnosticsSnapshot
|
||||
.inspector_metadata()
|
||||
.is_none(),
|
||||
"ordinary Page commands must remain outside Inspector lanes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_wire_session_id_normalizes_to_the_primary_session() {
|
||||
let command = RendererPageCommand::runtime_enable_events(Some(String::new()));
|
||||
assert_metadata(
|
||||
&command,
|
||||
DevToolsSessionKey::Primary,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct RendererRuntimeRemoteObject {
|
||||
protocol_value: Value,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use super::{
|
||||
ExternalRawDocumentBodyStream, JsLocalExecutor, JsRuntime, JsRuntimeOwner, PageVmInitStage,
|
||||
PreparedRendererDocument, RendererCaptureScreenshotReply, RendererDragData,
|
||||
RendererDraggedDirectory, RendererDraggedFile, RendererInspectorProtocolConfiguration,
|
||||
RendererInspectorSessionRestoreSnapshot, RendererOutputItem, RendererOutputPublication,
|
||||
RendererOutputResidenceIdentity, RendererOutputTransportMessage,
|
||||
RendererOutputTransportReceiver, RendererOutputTransportSender, RendererOwnerAction,
|
||||
RendererPageCommand, RendererPageHandle, RendererPageReply, RendererPageTestingHandle,
|
||||
RendererPendingPopupActivation, RendererPreparedDocumentCommitConfiguration,
|
||||
RendererProtocolObservation, RendererRuntimeCommandOutput, RendererRuntimeInspectorMessage,
|
||||
RendererDraggedDirectory, RendererDraggedFile, RendererInspectorCommandRoute,
|
||||
RendererInspectorProtocolConfiguration, RendererInspectorSessionRestoreSnapshot,
|
||||
RendererOutputItem, RendererOutputPublication, RendererOutputResidenceIdentity,
|
||||
RendererOutputTransportMessage, RendererOutputTransportReceiver, RendererOutputTransportSender,
|
||||
RendererOwnerAction, RendererPageCommand, RendererPageHandle, RendererPageReply,
|
||||
RendererPageTestingHandle, RendererPendingPopupActivation,
|
||||
RendererPreparedDocumentCommitConfiguration, RendererProtocolObservation,
|
||||
RendererRuntimeCommandOutput, RendererRuntimeInspectorMessage,
|
||||
RendererRuntimeInspectorResponseSender,
|
||||
};
|
||||
use crate::local_executor::{is_on_script_execution_lane_for, scope_on_scaffold_js_local_executor};
|
||||
@@ -3753,9 +3754,10 @@ async fn replacement_navigation_releases_old_inspector_deferred_response_callbac
|
||||
let (response_tx, mut response_rx) = oneshot::channel();
|
||||
let (dispatch, _) = page
|
||||
.run_async_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_deferred_response(
|
||||
inspector_session_id,
|
||||
raw_json: serde_json::json!({
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
serde_json::json!({
|
||||
"id": call_id,
|
||||
"method": "Runtime.evaluate",
|
||||
"params": {
|
||||
@@ -3766,11 +3768,11 @@ async fn replacement_navigation_releases_old_inspector_deferred_response_callbac
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
deferred_response: RendererRuntimeInspectorResponseSender::new(
|
||||
RendererRuntimeInspectorResponseSender::new(
|
||||
call_id,
|
||||
response_tx,
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("never-settling Runtime.evaluate should register a deferred callback");
|
||||
@@ -3816,9 +3818,10 @@ async fn replacement_navigation_releases_old_inspector_deferred_response_callbac
|
||||
let (replacement_response_tx, mut replacement_response_rx) = oneshot::channel();
|
||||
let replacement_completion = page
|
||||
.enqueue_async_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
inspector_session_id: Some("session-a".to_owned()),
|
||||
raw_json: serde_json::json!({
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_deferred_response(
|
||||
Some("session-a".to_owned()),
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
serde_json::json!({
|
||||
"id": reused_call_id,
|
||||
"method": "Runtime.evaluate",
|
||||
"params": {
|
||||
@@ -3828,11 +3831,11 @@ async fn replacement_navigation_releases_old_inspector_deferred_response_callbac
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
deferred_response: RendererRuntimeInspectorResponseSender::new(
|
||||
RendererRuntimeInspectorResponseSender::new(
|
||||
reused_call_id,
|
||||
replacement_response_tx,
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
.expect("replacement PageVM command should enqueue")
|
||||
.wait()
|
||||
@@ -3880,9 +3883,10 @@ async fn runtime_binding_replay_cannot_consume_same_id_frontend_deferred_respons
|
||||
let (response_tx, mut response_rx) = oneshot::channel();
|
||||
let (dispatch, _) = page
|
||||
.run_async_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
inspector_session_id: None,
|
||||
raw_json: serde_json::json!({
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_deferred_response(
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
serde_json::json!({
|
||||
"id": colliding_call_id,
|
||||
"method": "Runtime.evaluate",
|
||||
"params": {
|
||||
@@ -3892,11 +3896,11 @@ async fn runtime_binding_replay_cannot_consume_same_id_frontend_deferred_respons
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
deferred_response: RendererRuntimeInspectorResponseSender::new(
|
||||
RendererRuntimeInspectorResponseSender::new(
|
||||
colliding_call_id,
|
||||
response_tx,
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("frontend awaitPromise should remain deferred");
|
||||
@@ -9185,9 +9189,9 @@ async fn runtime_enable_waits_for_queued_child_realm_before_reporting_contexts()
|
||||
})
|
||||
.expect("child realm setup command should enqueue");
|
||||
let enable = page
|
||||
.enqueue_async_command(RendererPageCommand::RuntimeEnableEvents {
|
||||
inspector_session_id: Some("SID-runtime-enable-child-barrier".to_owned()),
|
||||
})
|
||||
.enqueue_async_command(RendererPageCommand::runtime_enable_events(Some(
|
||||
"SID-runtime-enable-child-barrier".to_owned(),
|
||||
)))
|
||||
.expect("Runtime.enable command should enqueue behind child realm setup");
|
||||
|
||||
let setup = setup
|
||||
@@ -9646,13 +9650,13 @@ async fn apply_runtime_protocol_state_keeps_session_binding_replay_scoped() {
|
||||
};
|
||||
|
||||
let (reply, _) = page
|
||||
.run_async_command(RendererPageCommand::ApplyRuntimeProtocolState {
|
||||
inspector_session_id: Some("SID-primary".to_owned()),
|
||||
session_restore_snapshots: Vec::new(),
|
||||
isolated_worlds: Vec::new(),
|
||||
stored_runtime_bindings: vec![stored_only_binding],
|
||||
session_runtime_bindings: Vec::new(),
|
||||
})
|
||||
.run_async_command(RendererPageCommand::apply_runtime_protocol_state(
|
||||
Some("SID-primary".to_owned()),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![stored_only_binding],
|
||||
Vec::new(),
|
||||
))
|
||||
.await
|
||||
.expect("runtime protocol state should apply");
|
||||
assert!(
|
||||
@@ -16177,15 +16181,16 @@ async fn command_turn_output_scope_is_removed_after_command_error() {
|
||||
let (invalid_response_tx, _invalid_response_rx) = oneshot::channel();
|
||||
let invalid = page
|
||||
.enqueue_async_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolutionAndDeferredResponse {
|
||||
inspector_session_id: None,
|
||||
action: "evaluate".to_owned(),
|
||||
raw_json: "{".to_owned(),
|
||||
deferred_response: RendererRuntimeInspectorResponseSender::new(
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_context_resolution_and_deferred_response(
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
"evaluate".to_owned(),
|
||||
"{".to_owned(),
|
||||
RendererRuntimeInspectorResponseSender::new(
|
||||
710_220,
|
||||
invalid_response_tx,
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
.expect("invalid Runtime.evaluate should enqueue")
|
||||
.wait()
|
||||
@@ -16199,9 +16204,10 @@ async fn command_turn_output_scope_is_removed_after_command_error() {
|
||||
let (response_tx, _response_rx) = oneshot::channel();
|
||||
let completion = page
|
||||
.enqueue_async_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
inspector_session_id: None,
|
||||
raw_json: serde_json::json!({
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_deferred_response(
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
serde_json::json!({
|
||||
"id": call_id,
|
||||
"method": "Runtime.evaluate",
|
||||
"params": {
|
||||
@@ -16210,11 +16216,8 @@ async fn command_turn_output_scope_is_removed_after_command_error() {
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
deferred_response: RendererRuntimeInspectorResponseSender::new(
|
||||
call_id,
|
||||
response_tx,
|
||||
),
|
||||
},
|
||||
RendererRuntimeInspectorResponseSender::new(call_id, response_tx),
|
||||
),
|
||||
)
|
||||
.expect("the command after an error should enqueue")
|
||||
.wait()
|
||||
@@ -16260,9 +16263,10 @@ async fn runtime_document_close_completion_parks_lifecycle_until_capability_rele
|
||||
let (response_tx, _response_rx) = oneshot::channel();
|
||||
let completion = page
|
||||
.enqueue_async_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithDeferredResponse {
|
||||
inspector_session_id: None,
|
||||
raw_json: serde_json::json!({
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_deferred_response(
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
serde_json::json!({
|
||||
"id": call_id,
|
||||
"method": "Runtime.evaluate",
|
||||
"params": {
|
||||
@@ -16271,11 +16275,11 @@ async fn runtime_document_close_completion_parks_lifecycle_until_capability_rele
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
deferred_response: RendererRuntimeInspectorResponseSender::new(
|
||||
RendererRuntimeInspectorResponseSender::new(
|
||||
call_id,
|
||||
response_tx,
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
.expect("Runtime.evaluate should enqueue")
|
||||
.wait()
|
||||
@@ -17196,10 +17200,11 @@ async fn dispatch_runtime_protocol_with_output_for_test(
|
||||
) -> anyhow::Result<(Vec<serde_json::Value>, RendererRuntimeCommandOutput)> {
|
||||
let raw_json = serde_json::to_string(&request)?;
|
||||
let output = page
|
||||
.enqueue_async_command(RendererPageCommand::DispatchRuntimeProtocolMessage {
|
||||
inspector_session_id: None,
|
||||
.enqueue_async_command(RendererPageCommand::dispatch_runtime_protocol_message(
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
raw_json,
|
||||
})
|
||||
))
|
||||
.expect("runtime protocol command should enqueue")
|
||||
.wait()
|
||||
.await?;
|
||||
@@ -17224,11 +17229,12 @@ async fn dispatch_runtime_protocol_with_context_resolution_for_test(
|
||||
let raw_json = serde_json::to_string(&request)?;
|
||||
let (reply, _) = page
|
||||
.run_async_command(
|
||||
RendererPageCommand::DispatchRuntimeProtocolMessageWithContextResolution {
|
||||
inspector_session_id: None,
|
||||
action: action.to_owned(),
|
||||
RendererPageCommand::dispatch_runtime_protocol_message_with_context_resolution(
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
action.to_owned(),
|
||||
raw_json,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
match reply {
|
||||
@@ -17263,9 +17269,9 @@ async fn runtime_enable_events_for_inspector_session_for_test(
|
||||
inspector_session_id: Option<&str>,
|
||||
) -> anyhow::Result<Vec<serde_json::Value>> {
|
||||
let (reply, _) = page
|
||||
.run_async_command(RendererPageCommand::RuntimeEnableEvents {
|
||||
inspector_session_id: inspector_session_id.map(str::to_owned),
|
||||
})
|
||||
.run_async_command(RendererPageCommand::runtime_enable_events(
|
||||
inspector_session_id.map(str::to_owned),
|
||||
))
|
||||
.await?;
|
||||
match reply {
|
||||
RendererPageReply::RuntimeInspectorProtocolMessages(output) => Ok(output
|
||||
@@ -17407,12 +17413,12 @@ async fn add_runtime_binding_for_test(
|
||||
execution_context_id: Option<i64>,
|
||||
) -> anyhow::Result<()> {
|
||||
let (reply, _) = page
|
||||
.run_async_command(RendererPageCommand::AddRuntimeBinding {
|
||||
inspector_session_id: None,
|
||||
name: name.to_owned(),
|
||||
execution_context_name: execution_context_name.map(str::to_owned),
|
||||
.run_async_command(RendererPageCommand::add_runtime_binding(
|
||||
None,
|
||||
name.to_owned(),
|
||||
execution_context_name.map(str::to_owned),
|
||||
execution_context_id,
|
||||
})
|
||||
))
|
||||
.await?;
|
||||
match reply {
|
||||
RendererPageReply::Unit => Ok(()),
|
||||
|
||||
@@ -14,9 +14,16 @@ use std::{
|
||||
cell::{Cell, RefCell, UnsafeCell},
|
||||
collections::HashMap,
|
||||
rc::{Rc, Weak},
|
||||
sync::atomic::{AtomicI64, Ordering},
|
||||
sync::atomic::{AtomicI64, AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
static INTERRUPT_LOOPS: RefCell<HashMap<usize, Weak<RendererInspectorPauseLoopLocal>>> =
|
||||
RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
static NEXT_INTERRUPT_ROUTE_ID: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
struct RendererInspectorClient {
|
||||
isolate: UnsafeCell<v8::UnsafeRawIsolatePtr>,
|
||||
context_registry: DocumentInspectorContextRegistry,
|
||||
@@ -35,13 +42,29 @@ struct RendererInspectorPauseSession {
|
||||
pub(super) struct RendererInspectorPauseSessionRegistration {
|
||||
pause_loop: Weak<RendererInspectorPauseLoopLocal>,
|
||||
context_group_id: i32,
|
||||
agent_token: RendererDevToolsAgentToken,
|
||||
session_key: DevToolsSessionKey,
|
||||
session: Weak<v8::inspector::V8InspectorSession>,
|
||||
}
|
||||
|
||||
struct RendererInspectorPauseLoopLocal {
|
||||
bridge: RendererInspectorPauseBridge,
|
||||
interrupt_route_id: Option<usize>,
|
||||
sessions: RefCell<HashMap<(i32, DevToolsSessionKey), RendererInspectorPauseSession>>,
|
||||
interrupt_sessions: RefCell<
|
||||
HashMap<(RendererDevToolsAgentToken, DevToolsSessionKey), RendererInspectorPauseSession>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Drop for RendererInspectorPauseLoopLocal {
|
||||
fn drop(&mut self) {
|
||||
let Some(interrupt_route_id) = self.interrupt_route_id else {
|
||||
return;
|
||||
};
|
||||
let _ = INTERRUPT_LOOPS.try_with(|loops| {
|
||||
loops.borrow_mut().remove(&interrupt_route_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RendererInspectorPauseSessionRegistration {
|
||||
@@ -57,15 +80,38 @@ impl Drop for RendererInspectorPauseSessionRegistration {
|
||||
{
|
||||
sessions.remove(&key);
|
||||
}
|
||||
let interrupt_key = (self.agent_token, self.session_key.clone());
|
||||
let mut interrupt_sessions = pause_loop.interrupt_sessions.borrow_mut();
|
||||
if interrupt_sessions
|
||||
.get(&interrupt_key)
|
||||
.is_some_and(|entry| Weak::ptr_eq(&entry.session, &self.session))
|
||||
{
|
||||
interrupt_sessions.remove(&interrupt_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RendererInspectorPauseLoopLocal {
|
||||
fn new(bridge: RendererInspectorPauseBridge) -> Self {
|
||||
Self {
|
||||
fn new(bridge: RendererInspectorPauseBridge) -> Rc<Self> {
|
||||
let interrupt_route_id = bridge.interrupt_route_id();
|
||||
let pause_loop = Rc::new(Self {
|
||||
bridge,
|
||||
interrupt_route_id,
|
||||
sessions: RefCell::new(HashMap::new()),
|
||||
interrupt_sessions: RefCell::new(HashMap::new()),
|
||||
});
|
||||
if let Some(interrupt_route_id) = interrupt_route_id {
|
||||
let previous = INTERRUPT_LOOPS.with(|loops| {
|
||||
loops
|
||||
.borrow_mut()
|
||||
.insert(interrupt_route_id, Rc::downgrade(&pause_loop))
|
||||
});
|
||||
assert!(
|
||||
previous.is_none(),
|
||||
"renderer Inspector interrupt route IDs must be unique"
|
||||
);
|
||||
}
|
||||
pause_loop
|
||||
}
|
||||
|
||||
fn register_session(
|
||||
@@ -77,18 +123,26 @@ impl RendererInspectorPauseLoopLocal {
|
||||
outbound: InspectorOutbound,
|
||||
) -> RendererInspectorPauseSessionRegistration {
|
||||
let weak_session = Rc::downgrade(session);
|
||||
self.sessions.borrow_mut().insert(
|
||||
(context_group_id.get(), session_key.clone()),
|
||||
RendererInspectorPauseSession {
|
||||
session: weak_session.clone(),
|
||||
outbound,
|
||||
agent_token,
|
||||
session_key: session_key.clone(),
|
||||
},
|
||||
);
|
||||
let route = RendererInspectorPauseSession {
|
||||
session: weak_session.clone(),
|
||||
outbound,
|
||||
agent_token,
|
||||
session_key: session_key.clone(),
|
||||
};
|
||||
self.sessions
|
||||
.borrow_mut()
|
||||
.insert((context_group_id.get(), session_key.clone()), route.clone());
|
||||
// Context groups may overlap briefly during document replacement.
|
||||
// Active-JS interrupts carry the stable renderer-agent identity rather
|
||||
// than a V8 context-group id, so keep an explicit latest-registration
|
||||
// index instead of depending on HashMap iteration order.
|
||||
self.interrupt_sessions
|
||||
.borrow_mut()
|
||||
.insert((agent_token, session_key.clone()), route);
|
||||
RendererInspectorPauseSessionRegistration {
|
||||
pause_loop: Rc::downgrade(self),
|
||||
context_group_id: context_group_id.get(),
|
||||
agent_token,
|
||||
session_key,
|
||||
session: weak_session,
|
||||
}
|
||||
@@ -105,17 +159,35 @@ impl RendererInspectorPauseLoopLocal {
|
||||
}
|
||||
|
||||
fn dispatch_command(&self, context_group_id: i32, command: RendererInspectorPauseCommand) {
|
||||
let session_key = DevToolsSessionKey::from_wire_session_id(
|
||||
command
|
||||
.inspector_session_id
|
||||
.as_deref()
|
||||
.filter(|session_id| !session_id.is_empty()),
|
||||
);
|
||||
let session_key = command.metadata.session().clone();
|
||||
let session = self
|
||||
.sessions
|
||||
.borrow()
|
||||
.get(&(context_group_id, session_key))
|
||||
.filter(|session| session.agent_token == command.agent_token)
|
||||
.cloned();
|
||||
self.dispatch_command_to_session(command, session);
|
||||
}
|
||||
|
||||
fn dispatch_next_io_command_from_interrupt(&self) {
|
||||
let Some(command) = self.bridge.claim_io_command_for_interrupt() else {
|
||||
return;
|
||||
};
|
||||
let session_key = command.metadata.session();
|
||||
let session = self
|
||||
.interrupt_sessions
|
||||
.borrow()
|
||||
.get(&(command.agent_token, session_key.clone()))
|
||||
.cloned();
|
||||
self.dispatch_command_to_session(command, session);
|
||||
}
|
||||
|
||||
fn dispatch_command_to_session(
|
||||
&self,
|
||||
command: RendererInspectorPauseCommand,
|
||||
session: Option<RendererInspectorPauseSession>,
|
||||
) {
|
||||
let mut first_dispatch = self.bridge.first_dispatch_guard(&command);
|
||||
let Some(session) = session else {
|
||||
send_pause_dispatch_error(command.response, "Inspector session is not available");
|
||||
return;
|
||||
@@ -128,6 +200,7 @@ impl RendererInspectorPauseLoopLocal {
|
||||
session
|
||||
.outbound
|
||||
.register_response_callback(command.response);
|
||||
first_dispatch.release();
|
||||
v8_session.dispatch_protocol_message(v8::inspector::StringView::from(
|
||||
command.raw_json.as_bytes(),
|
||||
));
|
||||
@@ -143,6 +216,38 @@ impl RendererInspectorPauseLoopLocal {
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate_interrupt_route_id() -> usize {
|
||||
NEXT_INTERRUPT_ROUTE_ID
|
||||
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
|
||||
current.checked_add(1)
|
||||
})
|
||||
.expect("renderer Inspector interrupt route ID exhausted")
|
||||
}
|
||||
|
||||
unsafe extern "C" fn dispatch_inspector_interrupt(
|
||||
isolate: v8::UnsafeRawIsolatePtr,
|
||||
data: *mut std::ffi::c_void,
|
||||
) {
|
||||
let interrupt_route_id = data as usize;
|
||||
let pause_loop = INTERRUPT_LOOPS
|
||||
.try_with(|loops| {
|
||||
loops
|
||||
.borrow()
|
||||
.get(&interrupt_route_id)
|
||||
.and_then(Weak::upgrade)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let Some(pause_loop) = pause_loop else {
|
||||
return;
|
||||
};
|
||||
let mut isolate_ptr = isolate;
|
||||
let isolate = unsafe { v8::Isolate::ref_from_raw_isolate_ptr_mut(&mut isolate_ptr) };
|
||||
with_scoped_inspector_microtasks(isolate, || {
|
||||
pause_loop.dispatch_next_io_command_from_interrupt();
|
||||
});
|
||||
}
|
||||
|
||||
fn send_pause_dispatch_error(response: RendererRuntimeInspectorResponseSender, message: &str) {
|
||||
let call_id = response.call_id();
|
||||
let _ = response.send(json!({
|
||||
@@ -301,8 +406,12 @@ impl RendererInspectorIsolateBackend {
|
||||
let isolate_ptr = unsafe { isolate.as_raw_isolate_ptr() };
|
||||
let context_registry = DocumentInspectorContextRegistry::default();
|
||||
let unique_id_state = Rc::new(RendererInspectorClientUniqueIdState::new());
|
||||
let pause_bridge = RendererInspectorPauseBridge::default();
|
||||
let pause_loop = Rc::new(RendererInspectorPauseLoopLocal::new(pause_bridge.clone()));
|
||||
let pause_bridge = RendererInspectorPauseBridge::new_interruptible(
|
||||
isolate.thread_safe_handle(),
|
||||
dispatch_inspector_interrupt,
|
||||
allocate_interrupt_route_id(),
|
||||
);
|
||||
let pause_loop = RendererInspectorPauseLoopLocal::new(pause_bridge.clone());
|
||||
let inspector_client =
|
||||
v8::inspector::V8InspectorClient::new(Box::new(RendererInspectorClient::new(
|
||||
isolate_ptr,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
ffi::c_void,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -8,30 +9,50 @@ use parking_lot::{Condvar, Mutex};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::runtime::{
|
||||
PageId, PendingRendererOutputRecord, RendererOutputResidenceIdentity,
|
||||
RendererProtocolObservation, RendererRuntimeCommandCausalIdentity,
|
||||
RendererRuntimeInspectorMessage, RendererRuntimeInspectorMessageBatch,
|
||||
RendererRuntimeInspectorResponseSender, RendererTurnOutputJournal,
|
||||
PageId, PendingRendererOutputRecord, RendererInspectorCommandMetadata,
|
||||
RendererInspectorCommandRoute, RendererOutputResidenceIdentity, RendererProtocolObservation,
|
||||
RendererRuntimeCommandCausalIdentity, RendererRuntimeInspectorMessage,
|
||||
RendererRuntimeInspectorMessageBatch, RendererRuntimeInspectorResponseSender,
|
||||
RendererTurnOutputJournal,
|
||||
};
|
||||
|
||||
pub(crate) struct RendererInspectorPauseCommand {
|
||||
command_id: u64,
|
||||
pub(crate) inspector_session_id: Option<String>,
|
||||
pub(crate) agent_token: RendererDevToolsAgentToken,
|
||||
pub(crate) metadata: RendererInspectorCommandMetadata,
|
||||
pub(crate) owner_context_resolution_action: Option<String>,
|
||||
pub(crate) raw_json: String,
|
||||
pub(crate) response: RendererRuntimeInspectorResponseSender,
|
||||
claim_tx: Option<tokio::sync::oneshot::Sender<RendererRuntimeInspectorCommandClaim>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
struct RendererInspectorFirstDispatchLane {
|
||||
agent_token: RendererDevToolsAgentToken,
|
||||
session: DevToolsSessionKey,
|
||||
route: RendererInspectorCommandRoute,
|
||||
}
|
||||
|
||||
impl RendererInspectorPauseCommand {
|
||||
fn first_dispatch_lane(&self) -> RendererInspectorFirstDispatchLane {
|
||||
RendererInspectorFirstDispatchLane {
|
||||
agent_token: self.agent_token,
|
||||
session: self.metadata.session().clone(),
|
||||
route: self.metadata.route(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RendererRuntimeInspectorCommandClaim {
|
||||
Owner,
|
||||
Pause,
|
||||
Inspector,
|
||||
Canceled,
|
||||
}
|
||||
|
||||
pub struct RendererRuntimeInspectorCommandRoute {
|
||||
command_id: u64,
|
||||
metadata: RendererInspectorCommandMetadata,
|
||||
requires_owner_fallback: bool,
|
||||
claim_rx: Option<tokio::sync::oneshot::Receiver<RendererRuntimeInspectorCommandClaim>>,
|
||||
bridge: RendererInspectorPauseBridge,
|
||||
@@ -42,6 +63,10 @@ impl RendererRuntimeInspectorCommandRoute {
|
||||
self.command_id
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> &RendererInspectorCommandMetadata {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
pub fn requires_owner_fallback(&self) -> bool {
|
||||
self.requires_owner_fallback
|
||||
}
|
||||
@@ -74,6 +99,16 @@ pub(crate) struct RendererInspectorPauseBridge {
|
||||
struct RendererInspectorPauseBridgeShared {
|
||||
state: Mutex<RendererInspectorPauseBridgeState>,
|
||||
command_ready: Condvar,
|
||||
interrupt_route: Option<RendererInspectorInterruptRoute>,
|
||||
}
|
||||
|
||||
type RendererInspectorInterruptCallback =
|
||||
unsafe extern "C" fn(v8::UnsafeRawIsolatePtr, *mut c_void);
|
||||
|
||||
struct RendererInspectorInterruptRoute {
|
||||
isolate: v8::IsolateHandle,
|
||||
callback: RendererInspectorInterruptCallback,
|
||||
callback_data: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -95,6 +130,7 @@ struct RendererInspectorPauseBridgeState {
|
||||
quit_requested: bool,
|
||||
session_detach_arms: usize,
|
||||
target_closed: bool,
|
||||
active_first_dispatch_lanes: HashMap<RendererInspectorFirstDispatchLane, u64>,
|
||||
commands: VecDeque<RendererInspectorPauseCommand>,
|
||||
pending_prefaces: VecDeque<RendererInspectorPausePreface>,
|
||||
paused_sessions_awaiting_resumed: HashSet<(RendererDevToolsAgentToken, DevToolsSessionKey)>,
|
||||
@@ -203,6 +239,28 @@ pub(super) struct RendererInspectorPausePrefaceGuard {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
pub(crate) struct RendererInspectorFirstDispatchGuard {
|
||||
bridge: RendererInspectorPauseBridge,
|
||||
active: Option<(RendererInspectorFirstDispatchLane, u64)>,
|
||||
}
|
||||
|
||||
impl Drop for RendererInspectorFirstDispatchGuard {
|
||||
fn drop(&mut self) {
|
||||
self.release();
|
||||
}
|
||||
}
|
||||
|
||||
impl RendererInspectorFirstDispatchGuard {
|
||||
/// Releases the lane at the call boundary into V8 Inspector. The command
|
||||
/// response and any JavaScript it starts may remain pending after this
|
||||
/// point; neither is part of the first-dispatch lifetime.
|
||||
pub(crate) fn release(&mut self) {
|
||||
if let Some((lane, command_id)) = self.active.take() {
|
||||
self.bridge.finish_first_dispatch(lane, command_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RendererInspectorPausePrefaceGuard {
|
||||
fn drop(&mut self) {
|
||||
self.bridge.cancel_pause_preface(self.id);
|
||||
@@ -217,6 +275,28 @@ pub(super) struct RendererInspectorPauseOutboundRoute {
|
||||
|
||||
impl Default for RendererInspectorPauseBridge {
|
||||
fn default() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl RendererInspectorPauseBridge {
|
||||
pub(super) fn new_interruptible(
|
||||
isolate: v8::IsolateHandle,
|
||||
callback: RendererInspectorInterruptCallback,
|
||||
callback_data: usize,
|
||||
) -> Self {
|
||||
assert_ne!(
|
||||
callback_data, 0,
|
||||
"Inspector interrupt route ID must be non-zero"
|
||||
);
|
||||
Self::new(Some(RendererInspectorInterruptRoute {
|
||||
isolate,
|
||||
callback,
|
||||
callback_data,
|
||||
}))
|
||||
}
|
||||
|
||||
fn new(interrupt_route: Option<RendererInspectorInterruptRoute>) -> Self {
|
||||
Self {
|
||||
shared: Arc::new(RendererInspectorPauseBridgeShared {
|
||||
state: Mutex::new(RendererInspectorPauseBridgeState {
|
||||
@@ -226,6 +306,7 @@ impl Default for RendererInspectorPauseBridge {
|
||||
quit_requested: false,
|
||||
session_detach_arms: 0,
|
||||
target_closed: false,
|
||||
active_first_dispatch_lanes: HashMap::new(),
|
||||
commands: VecDeque::new(),
|
||||
pending_prefaces: VecDeque::new(),
|
||||
paused_sessions_awaiting_resumed: HashSet::new(),
|
||||
@@ -234,6 +315,7 @@ impl Default for RendererInspectorPauseBridge {
|
||||
route: None,
|
||||
}),
|
||||
command_ready: Condvar::new(),
|
||||
interrupt_route,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -248,6 +330,10 @@ impl std::fmt::Debug for RendererInspectorPauseBridge {
|
||||
.field("quit_requested", &state.quit_requested)
|
||||
.field("session_detach_arms", &state.session_detach_arms)
|
||||
.field("target_closed", &state.target_closed)
|
||||
.field(
|
||||
"active_first_dispatch_lanes",
|
||||
&state.active_first_dispatch_lanes,
|
||||
)
|
||||
.field("pending_commands", &state.commands.len())
|
||||
.field("pending_prefaces", &state.pending_prefaces.len())
|
||||
.field(
|
||||
@@ -267,6 +353,13 @@ impl std::fmt::Debug for RendererInspectorPauseBridge {
|
||||
}
|
||||
|
||||
impl RendererInspectorPauseBridge {
|
||||
pub(super) fn interrupt_route_id(&self) -> Option<usize> {
|
||||
self.shared
|
||||
.interrupt_route
|
||||
.as_ref()
|
||||
.map(|route| route.callback_data)
|
||||
}
|
||||
|
||||
pub(super) fn outbound_route(
|
||||
&self,
|
||||
agent_token: RendererDevToolsAgentToken,
|
||||
@@ -304,7 +397,11 @@ impl RendererInspectorPauseBridge {
|
||||
}
|
||||
let call_id = command.response.call_id();
|
||||
let causal_identity = RendererRuntimeCommandCausalIdentity::new(
|
||||
command.inspector_session_id.clone(),
|
||||
command
|
||||
.metadata
|
||||
.session()
|
||||
.wire_session_id()
|
||||
.map(str::to_owned),
|
||||
call_id,
|
||||
);
|
||||
let mut state = self.shared.state.lock();
|
||||
@@ -439,7 +536,9 @@ impl RendererInspectorPauseBridge {
|
||||
|
||||
pub(crate) fn enqueue_command(
|
||||
&self,
|
||||
agent_token: RendererDevToolsAgentToken,
|
||||
inspector_session_id: Option<String>,
|
||||
route: RendererInspectorCommandRoute,
|
||||
owner_context_resolution_action: Option<String>,
|
||||
raw_json: String,
|
||||
response: RendererRuntimeInspectorResponseSender,
|
||||
@@ -451,9 +550,11 @@ impl RendererInspectorPauseBridge {
|
||||
.next_command_id
|
||||
.checked_add(1)
|
||||
.expect("runtime inspector pause command ID overflow");
|
||||
let metadata = RendererInspectorCommandMetadata::new(inspector_session_id, route);
|
||||
let command = RendererInspectorPauseCommand {
|
||||
command_id,
|
||||
inspector_session_id,
|
||||
agent_token,
|
||||
metadata: metadata.clone(),
|
||||
owner_context_resolution_action,
|
||||
raw_json,
|
||||
response,
|
||||
@@ -464,6 +565,7 @@ impl RendererInspectorPauseBridge {
|
||||
fail_pause_command(command, "Inspector target closed while paused");
|
||||
return RendererRuntimeInspectorCommandRoute {
|
||||
command_id,
|
||||
metadata,
|
||||
requires_owner_fallback: false,
|
||||
claim_rx: Some(claim_rx),
|
||||
bridge: self.clone(),
|
||||
@@ -474,18 +576,32 @@ impl RendererInspectorPauseBridge {
|
||||
if pause_active {
|
||||
self.shared.command_ready.notify_one();
|
||||
}
|
||||
drop(state);
|
||||
if route == RendererInspectorCommandRoute::Io {
|
||||
self.request_io_interrupt();
|
||||
}
|
||||
RendererRuntimeInspectorCommandRoute {
|
||||
command_id,
|
||||
// Queue the owner task even while V8 is paused. The nested pause loop
|
||||
// normally claims the command first, making that task a no-op. If a
|
||||
// preceding Debugger.resume exits the loop first, the owner task is
|
||||
// already waiting and can dispatch the still-unclaimed command.
|
||||
metadata,
|
||||
// Every route keeps an owner fallback. IO can win through a V8
|
||||
// interrupt, while both Chromium transport routes can run in the
|
||||
// nested debugger loop. If V8 unwinds first, the already-queued
|
||||
// owner turn can dispatch the still-unclaimed command.
|
||||
requires_owner_fallback: true,
|
||||
claim_rx: Some(claim_rx),
|
||||
bridge: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn request_io_interrupt(&self) {
|
||||
let Some(route) = self.shared.interrupt_route.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let _ = route
|
||||
.isolate
|
||||
.request_interrupt(route.callback, route.callback_data as *mut c_void);
|
||||
}
|
||||
|
||||
pub(crate) fn claim_command_for_owner(
|
||||
&self,
|
||||
command_id: u64,
|
||||
@@ -498,10 +614,27 @@ impl RendererInspectorPauseBridge {
|
||||
.commands
|
||||
.iter()
|
||||
.position(|command| command.command_id == command_id)?;
|
||||
let lane = state.commands[position].first_dispatch_lane();
|
||||
if state.active_first_dispatch_lanes.contains_key(&lane)
|
||||
|| state
|
||||
.commands
|
||||
.iter()
|
||||
.take(position)
|
||||
.any(|command| command.first_dispatch_lane() == lane)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let command = state
|
||||
.commands
|
||||
.remove(position)
|
||||
.expect("queued runtime inspector command position should remain valid");
|
||||
assert!(
|
||||
state
|
||||
.active_first_dispatch_lanes
|
||||
.insert(lane, command.command_id)
|
||||
.is_none(),
|
||||
"a first-dispatch lane must have at most one active command"
|
||||
);
|
||||
Some(claim_pause_command(
|
||||
command,
|
||||
RendererRuntimeInspectorCommandClaim::Owner,
|
||||
@@ -540,16 +673,98 @@ impl RendererInspectorPauseBridge {
|
||||
if state.target_closed || state.quit_requested || state.session_detach_arms != 0 {
|
||||
return None;
|
||||
}
|
||||
if let Some(command) = state.commands.pop_front() {
|
||||
if let Some(position) = state.commands.iter().position(|command| {
|
||||
!state
|
||||
.active_first_dispatch_lanes
|
||||
.contains_key(&command.first_dispatch_lane())
|
||||
}) {
|
||||
let command = state
|
||||
.commands
|
||||
.remove(position)
|
||||
.expect("queued Inspector command position must remain valid");
|
||||
let lane = command.first_dispatch_lane();
|
||||
assert!(
|
||||
state
|
||||
.active_first_dispatch_lanes
|
||||
.insert(lane, command.command_id)
|
||||
.is_none(),
|
||||
"a first-dispatch lane must have at most one active command"
|
||||
);
|
||||
return Some(claim_pause_command(
|
||||
command,
|
||||
RendererRuntimeInspectorCommandClaim::Pause,
|
||||
RendererRuntimeInspectorCommandClaim::Inspector,
|
||||
));
|
||||
}
|
||||
self.shared.command_ready.wait(&mut state);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn claim_io_command_for_interrupt(&self) -> Option<RendererInspectorPauseCommand> {
|
||||
let mut state = self.shared.state.lock();
|
||||
if state.target_closed {
|
||||
return None;
|
||||
}
|
||||
let position = state.commands.iter().position(|command| {
|
||||
command.metadata.route() == RendererInspectorCommandRoute::Io
|
||||
&& !state
|
||||
.active_first_dispatch_lanes
|
||||
.contains_key(&command.first_dispatch_lane())
|
||||
})?;
|
||||
let command = state
|
||||
.commands
|
||||
.remove(position)
|
||||
.expect("queued IO Inspector command position must remain valid");
|
||||
let lane = command.first_dispatch_lane();
|
||||
assert!(
|
||||
state
|
||||
.active_first_dispatch_lanes
|
||||
.insert(lane, command.command_id)
|
||||
.is_none(),
|
||||
"a first-dispatch lane must have at most one active command"
|
||||
);
|
||||
Some(claim_pause_command(
|
||||
command,
|
||||
RendererRuntimeInspectorCommandClaim::Inspector,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn first_dispatch_guard(
|
||||
&self,
|
||||
command: &RendererInspectorPauseCommand,
|
||||
) -> RendererInspectorFirstDispatchGuard {
|
||||
let lane = command.first_dispatch_lane();
|
||||
let state = self.shared.state.lock();
|
||||
assert_eq!(
|
||||
state.active_first_dispatch_lanes.get(&lane),
|
||||
Some(&command.command_id),
|
||||
"a claimed Inspector command must own its first-dispatch lane"
|
||||
);
|
||||
drop(state);
|
||||
RendererInspectorFirstDispatchGuard {
|
||||
bridge: self.clone(),
|
||||
active: Some((lane, command.command_id)),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_first_dispatch(&self, lane: RendererInspectorFirstDispatchLane, command_id: u64) {
|
||||
let has_waiting_io = {
|
||||
let mut state = self.shared.state.lock();
|
||||
assert_eq!(
|
||||
state.active_first_dispatch_lanes.remove(&lane),
|
||||
Some(command_id),
|
||||
"only the active Inspector command may release its first-dispatch lane"
|
||||
);
|
||||
self.shared.command_ready.notify_all();
|
||||
state
|
||||
.commands
|
||||
.iter()
|
||||
.any(|command| command.metadata.route() == RendererInspectorCommandRoute::Io)
|
||||
};
|
||||
if has_waiting_io {
|
||||
self.request_io_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn request_quit(&self) {
|
||||
let mut state = self.shared.state.lock();
|
||||
if state.phase != RendererInspectorPausePhase::Running {
|
||||
@@ -564,7 +779,7 @@ impl RendererInspectorPauseBridge {
|
||||
state.quit_requested = false;
|
||||
// Commands that lost the nested-loop race to Debugger.resume stay
|
||||
// queued. Every live route has an owner fallback queued before it is
|
||||
// exposed to the caller, so normal owner dispatch claims them after V8
|
||||
// exposed to the caller, so owner dispatch claims them after V8
|
||||
// unwinds instead of reporting a synthetic cancellation.
|
||||
}
|
||||
|
||||
@@ -627,7 +842,7 @@ impl RendererInspectorPauseBridge {
|
||||
) {
|
||||
let route = {
|
||||
let state = self.shared.state.lock();
|
||||
if state.target_closed || state.phase == RendererInspectorPausePhase::Running {
|
||||
if state.target_closed {
|
||||
return;
|
||||
}
|
||||
state.route.clone()
|
||||
@@ -644,7 +859,7 @@ impl RendererInspectorPauseBridge {
|
||||
)
|
||||
.resolve()
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("Inspector pause state update must have resolved source identity")
|
||||
panic!("Inspector IO state update must have resolved source identity")
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -899,17 +1114,26 @@ mod tests {
|
||||
|
||||
let (response, _response_rx) = response_sender(41);
|
||||
let command_route = bridge.enqueue_command(
|
||||
RendererDevToolsAgentToken::allocate(),
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
None,
|
||||
r#"{"id":41,"method":"Debugger.stepOut","params":{}}"#.to_owned(),
|
||||
response,
|
||||
);
|
||||
assert_eq!(
|
||||
command_route.metadata().route(),
|
||||
RendererInspectorCommandRoute::MainThread
|
||||
);
|
||||
let command = bridge
|
||||
.wait_for_command()
|
||||
.expect("the nested pause loop should claim stepOut");
|
||||
let first_dispatch = bridge.first_dispatch_guard(&command);
|
||||
assert_eq!(command.metadata, command_route.metadata().clone());
|
||||
let dispatch = bridge.begin_command_dispatch(&command);
|
||||
outbound.mark_command_response(41, true);
|
||||
drop(dispatch);
|
||||
drop(first_dispatch);
|
||||
bridge.leave_pause();
|
||||
let resumed = expect_immediate_command_output(
|
||||
outbound.route_notification(&json!({"method": "Debugger.resumed", "params": {}})),
|
||||
@@ -953,7 +1177,9 @@ mod tests {
|
||||
|
||||
let (response, _response_rx) = response_sender(43);
|
||||
let command_route = bridge.enqueue_command(
|
||||
RendererDevToolsAgentToken::allocate(),
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
None,
|
||||
r#"{"id":43,"method":"Debugger.stepOut","params":{}}"#.to_owned(),
|
||||
response,
|
||||
@@ -961,9 +1187,11 @@ mod tests {
|
||||
let command = bridge
|
||||
.wait_for_command()
|
||||
.expect("the nested pause loop should claim stepOut");
|
||||
let first_dispatch = bridge.first_dispatch_guard(&command);
|
||||
let dispatch = bridge.begin_command_dispatch(&command);
|
||||
outbound.mark_command_response(43, true);
|
||||
drop(dispatch);
|
||||
drop(first_dispatch);
|
||||
bridge.leave_pause();
|
||||
assert!(
|
||||
expect_immediate_command_output(
|
||||
@@ -1000,7 +1228,9 @@ mod tests {
|
||||
|
||||
let (response, _response_rx) = response_sender(42);
|
||||
let command_route = bridge.enqueue_command(
|
||||
RendererDevToolsAgentToken::allocate(),
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
None,
|
||||
r#"{"id":42,"method":"Debugger.stepOut","params":{}}"#.to_owned(),
|
||||
response,
|
||||
@@ -1008,9 +1238,11 @@ mod tests {
|
||||
let command = bridge
|
||||
.wait_for_command()
|
||||
.expect("the nested pause loop should claim stepOut");
|
||||
let first_dispatch = bridge.first_dispatch_guard(&command);
|
||||
let dispatch = bridge.begin_command_dispatch(&command);
|
||||
outbound.mark_command_response(42, false);
|
||||
drop(dispatch);
|
||||
drop(first_dispatch);
|
||||
|
||||
assert!(
|
||||
bridge
|
||||
@@ -1076,55 +1308,252 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn command_queued_while_running_can_be_claimed_by_a_racing_pause() {
|
||||
async fn pause_loop_accepts_main_while_io_can_overtake_on_its_own_lane() {
|
||||
let bridge = RendererInspectorPauseBridge::default();
|
||||
configure_page(&bridge, PageId::new_for_testing(1));
|
||||
let (response, response_rx) = response_sender(7);
|
||||
let route = bridge.enqueue_command(
|
||||
let agent_token = RendererDevToolsAgentToken::allocate();
|
||||
let (main_response, main_response_rx) = response_sender(7);
|
||||
let main_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
Some("evaluate".to_owned()),
|
||||
r#"{"id":7,"method":"Runtime.evaluate","params":{"expression":"42"}}"#.to_owned(),
|
||||
response,
|
||||
main_response,
|
||||
);
|
||||
let (io_response, io_response_rx) = response_sender(8);
|
||||
let io_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":8,"method":"Runtime.terminateExecution"}"#.to_owned(),
|
||||
io_response,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
main_route.metadata().session(),
|
||||
&DevToolsSessionKey::Primary
|
||||
);
|
||||
assert_eq!(
|
||||
main_route.metadata().route(),
|
||||
RendererInspectorCommandRoute::MainThread
|
||||
);
|
||||
assert!(
|
||||
route.requires_owner_fallback(),
|
||||
main_route.requires_owner_fallback(),
|
||||
"the command should initially schedule an owner fallback"
|
||||
);
|
||||
let command_id = route.command_id();
|
||||
|
||||
assert!(expect_immediate_preface(route_paused(&bridge)).is_empty());
|
||||
assert!(bridge.enter_pause());
|
||||
let command = bridge
|
||||
.wait_for_command()
|
||||
.expect("the pause loop should claim the pre-pause command");
|
||||
let io_command = bridge
|
||||
.claim_io_command_for_interrupt()
|
||||
.expect("the IO callback may overtake an earlier independent Main command");
|
||||
let io_first_dispatch = bridge.first_dispatch_guard(&io_command);
|
||||
assert_eq!(io_command.metadata, io_route.metadata().clone());
|
||||
assert_eq!(
|
||||
command.owner_context_resolution_action.as_deref(),
|
||||
Some("evaluate"),
|
||||
"the owner-only context resolution metadata must stay attached to the command"
|
||||
io_route.wait_for_claim().await,
|
||||
Ok(RendererRuntimeInspectorCommandClaim::Inspector)
|
||||
);
|
||||
io_command
|
||||
.response
|
||||
.send(json!({"id": 8, "result": {}}))
|
||||
.expect("IO response receiver should remain open");
|
||||
assert_eq!(io_response_rx.await.expect("IO response").call_id, 8);
|
||||
drop(io_first_dispatch);
|
||||
|
||||
// A normal debugger pause pumps Chromium's main-thread DevTools
|
||||
// receiver too. Main does not interrupt running JavaScript, but it is
|
||||
// eligible once V8 has entered the nested message loop.
|
||||
let main_command = bridge
|
||||
.wait_for_command()
|
||||
.expect("the nested pause loop should claim Main work");
|
||||
let main_first_dispatch = bridge.first_dispatch_guard(&main_command);
|
||||
assert_eq!(main_command.metadata, main_route.metadata().clone());
|
||||
assert_eq!(
|
||||
route.wait_for_claim().await,
|
||||
Ok(RendererRuntimeInspectorCommandClaim::Pause)
|
||||
main_command.owner_context_resolution_action.as_deref(),
|
||||
Some("evaluate"),
|
||||
"owner context-resolution metadata must stay attached"
|
||||
);
|
||||
assert!(
|
||||
bridge.claim_command_for_owner(command_id).is_none(),
|
||||
"the delayed owner fallback must not dispatch a command already claimed by pause"
|
||||
assert_eq!(
|
||||
main_route.wait_for_claim().await,
|
||||
Ok(RendererRuntimeInspectorCommandClaim::Inspector)
|
||||
);
|
||||
command
|
||||
main_command
|
||||
.response
|
||||
.send(json!({"id": 7, "result": {"result": {"value": 42}}}))
|
||||
.expect("test response receiver should remain open");
|
||||
assert_eq!(
|
||||
response_rx.await.expect("test response").call_id,
|
||||
7,
|
||||
"the claimed command must retain its original response sender"
|
||||
);
|
||||
.expect("Main response receiver should remain open");
|
||||
assert_eq!(main_response_rx.await.expect("Main response").call_id, 7);
|
||||
drop(main_first_dispatch);
|
||||
|
||||
bridge.request_quit();
|
||||
assert!(bridge.wait_for_command().is_none());
|
||||
bridge.leave_pause();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_owner_io_dispatch_cannot_be_overtaken_by_its_io_successor() {
|
||||
let bridge = RendererInspectorPauseBridge::default();
|
||||
let agent_token = RendererDevToolsAgentToken::allocate();
|
||||
let (first_response, _first_response_rx) = response_sender(11);
|
||||
let first_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":11,"method":"Debugger.pause"}"#.to_owned(),
|
||||
first_response,
|
||||
);
|
||||
let (second_response, _second_response_rx) = response_sender(12);
|
||||
let second_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":12,"method":"Runtime.terminateExecution"}"#.to_owned(),
|
||||
second_response,
|
||||
);
|
||||
|
||||
let first = bridge
|
||||
.claim_command_for_owner(first_route.command_id())
|
||||
.expect("the first IO command should be owner-claimable while V8 is idle");
|
||||
let first_dispatch = bridge.first_dispatch_guard(&first);
|
||||
assert!(
|
||||
bridge.claim_io_command_for_interrupt().is_none(),
|
||||
"an interrupt callback must not overtake an owner-claimed IO predecessor"
|
||||
);
|
||||
|
||||
drop(first_dispatch);
|
||||
let second = bridge
|
||||
.claim_io_command_for_interrupt()
|
||||
.expect("the IO successor should become claimable after first dispatch");
|
||||
assert_eq!(second.command_id, second_route.command_id());
|
||||
let second_dispatch = bridge.first_dispatch_guard(&second);
|
||||
drop(second_dispatch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_dispatch_fifo_is_scoped_to_the_exact_agent_session_and_route() {
|
||||
let bridge = RendererInspectorPauseBridge::default();
|
||||
let agent_token = RendererDevToolsAgentToken::allocate();
|
||||
let (first_response, _first_response_rx) = response_sender(21);
|
||||
let first_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":21,"method":"Debugger.pause"}"#.to_owned(),
|
||||
first_response,
|
||||
);
|
||||
let (second_response, _second_response_rx) = response_sender(22);
|
||||
let second_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":22,"method":"Runtime.terminateExecution"}"#.to_owned(),
|
||||
second_response,
|
||||
);
|
||||
let (other_response, _other_response_rx) = response_sender(23);
|
||||
let other_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
Some("SID-other".to_owned()),
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":23,"method":"Debugger.pause"}"#.to_owned(),
|
||||
other_response,
|
||||
);
|
||||
|
||||
let first = bridge
|
||||
.claim_io_command_for_interrupt()
|
||||
.expect("the first primary-session IO command should be claimable");
|
||||
assert_eq!(first.command_id, first_route.command_id());
|
||||
let first_dispatch = bridge.first_dispatch_guard(&first);
|
||||
|
||||
let other = bridge
|
||||
.claim_io_command_for_interrupt()
|
||||
.expect("a different session lane must remain independent");
|
||||
assert_eq!(other.command_id, other_route.command_id());
|
||||
let other_dispatch = bridge.first_dispatch_guard(&other);
|
||||
assert!(
|
||||
bridge.claim_io_command_for_interrupt().is_none(),
|
||||
"the primary-session successor must not overtake its active predecessor"
|
||||
);
|
||||
drop(other_dispatch);
|
||||
assert!(
|
||||
bridge.claim_io_command_for_interrupt().is_none(),
|
||||
"releasing another session must not release the primary lane"
|
||||
);
|
||||
|
||||
drop(first_dispatch);
|
||||
let second = bridge
|
||||
.claim_io_command_for_interrupt()
|
||||
.expect("the exact-lane successor should become claimable once");
|
||||
assert_eq!(second.command_id, second_route.command_id());
|
||||
let second_dispatch = bridge.first_dispatch_guard(&second);
|
||||
drop(second_dispatch);
|
||||
assert!(bridge.claim_io_command_for_interrupt().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unclaimed_pause_command_is_handed_to_owner_after_resume() {
|
||||
async fn page_teardown_cancels_waiters_while_an_io_dispatch_is_in_flight() {
|
||||
let bridge = RendererInspectorPauseBridge::default();
|
||||
let page_id = PageId::new_for_testing(1);
|
||||
configure_page(&bridge, page_id);
|
||||
let agent_token = RendererDevToolsAgentToken::allocate();
|
||||
let (active_response, _active_response_rx) = response_sender(31);
|
||||
let active_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":31,"method":"Debugger.pause"}"#.to_owned(),
|
||||
active_response,
|
||||
);
|
||||
let (waiting_response, waiting_response_rx) = response_sender(32);
|
||||
let waiting_route = bridge.enqueue_command(
|
||||
agent_token,
|
||||
None,
|
||||
RendererInspectorCommandRoute::Io,
|
||||
None,
|
||||
r#"{"id":32,"method":"Runtime.terminateExecution"}"#.to_owned(),
|
||||
waiting_response,
|
||||
);
|
||||
|
||||
let active = bridge
|
||||
.claim_io_command_for_interrupt()
|
||||
.expect("the first IO command should enter dispatch");
|
||||
assert_eq!(active.command_id, active_route.command_id());
|
||||
let active_dispatch = bridge.first_dispatch_guard(&active);
|
||||
bridge.detach_page(page_id);
|
||||
|
||||
let canceled = waiting_response_rx
|
||||
.await
|
||||
.expect("teardown must settle the queued IO response");
|
||||
let response = canceled
|
||||
.output
|
||||
.protocol_response(32)
|
||||
.expect("teardown cancellation response");
|
||||
assert_eq!(
|
||||
response["error"]["message"],
|
||||
json!("Inspector page closed while paused")
|
||||
);
|
||||
assert_eq!(waiting_route.command_id(), 2);
|
||||
|
||||
drop(active_dispatch);
|
||||
assert!(
|
||||
bridge
|
||||
.shared
|
||||
.state
|
||||
.lock()
|
||||
.active_first_dispatch_lanes
|
||||
.is_empty(),
|
||||
"an in-flight callback must release its lane safely after teardown"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn main_thread_command_is_handed_to_owner_after_resume() {
|
||||
let bridge = RendererInspectorPauseBridge::default();
|
||||
configure_page(&bridge, PageId::new_for_testing(1));
|
||||
assert!(
|
||||
@@ -1138,7 +1567,9 @@ mod tests {
|
||||
|
||||
let (response, response_rx) = response_sender(9);
|
||||
let route = bridge.enqueue_command(
|
||||
RendererDevToolsAgentToken::allocate(),
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
Some("evaluate".to_owned()),
|
||||
r#"{"id":9,"method":"Runtime.evaluate","params":{"expression":"42"}}"#.to_owned(),
|
||||
response,
|
||||
@@ -1220,7 +1651,9 @@ mod tests {
|
||||
configure_page(&bridge, PageId::new_for_testing(1));
|
||||
let (response, response_rx) = response_sender(8);
|
||||
let route = bridge.enqueue_command(
|
||||
RendererDevToolsAgentToken::allocate(),
|
||||
None,
|
||||
RendererInspectorCommandRoute::MainThread,
|
||||
None,
|
||||
r#"{"id":8,"method":"Runtime.getIsolateId"}"#.to_owned(),
|
||||
response,
|
||||
|
||||
Reference in New Issue
Block a user