use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use tracing::{debug, trace};
use viewpoint_cdp::CdpConnection;
use viewpoint_cdp::protocol::runtime::{
ExecutionContextCreatedEvent, ExecutionContextDestroyedEvent, ExecutionContextId,
};
pub const MAIN_WORLD_KEY: &str = "";
#[derive(Debug)]
pub struct ExecutionContextRegistry {
session_id: String,
connection: Arc<CdpConnection>,
contexts: RwLock<HashMap<String, HashMap<String, ExecutionContextId>>>,
context_to_frame: RwLock<HashMap<ExecutionContextId, String>>,
}
impl ExecutionContextRegistry {
pub fn new(connection: Arc<CdpConnection>, session_id: String) -> Self {
Self {
session_id,
connection,
contexts: RwLock::new(HashMap::new()),
context_to_frame: RwLock::new(HashMap::new()),
}
}
pub fn start_listening(self: &Arc<Self>) {
let registry = Arc::clone(self);
let mut events = registry.connection.subscribe_events();
let session_id = registry.session_id.clone();
tokio::spawn(async move {
while let Ok(event) = events.recv().await {
if event.session_id.as_deref() != Some(&session_id) {
continue;
}
match event.method.as_str() {
"Runtime.executionContextCreated" => {
if let Some(params) = event.params.as_ref() {
if let Ok(created_event) = serde_json::from_value::<
ExecutionContextCreatedEvent,
>(params.clone())
{
registry.handle_context_created(created_event);
}
}
}
"Runtime.executionContextDestroyed" => {
if let Some(params) = event.params.as_ref() {
if let Ok(destroyed_event) =
serde_json::from_value::<ExecutionContextDestroyedEvent>(
params.clone(),
)
{
registry.handle_context_destroyed(destroyed_event);
}
}
}
_ => {}
}
}
});
}
fn handle_context_created(&self, event: ExecutionContextCreatedEvent) {
let context = &event.context;
let context_id = context.id;
let (frame_id, is_default) = if let Some(aux_data) = &context.aux_data {
let frame_id = aux_data.frame_id.clone();
let is_default = aux_data.is_default.unwrap_or(false);
(frame_id, is_default)
} else {
trace!(
context_id = context_id,
name = %context.name,
"Execution context created without auxData"
);
return;
};
let Some(frame_id) = frame_id else {
trace!(
context_id = context_id,
name = %context.name,
"Execution context created without frame_id"
);
return;
};
let world_name = if is_default {
MAIN_WORLD_KEY.to_string()
} else {
context.name.clone()
};
debug!(
frame_id = %frame_id,
context_id = context_id,
world_name = %world_name,
is_default = is_default,
"Execution context created"
);
{
let mut contexts = self.contexts.write();
let frame_contexts = contexts.entry(frame_id.clone()).or_default();
frame_contexts.insert(world_name, context_id);
}
{
let mut context_to_frame = self.context_to_frame.write();
context_to_frame.insert(context_id, frame_id);
}
}
fn handle_context_destroyed(&self, event: ExecutionContextDestroyedEvent) {
let context_id = event.execution_context_id;
let frame_id = {
let mut context_to_frame = self.context_to_frame.write();
context_to_frame.remove(&context_id)
};
if let Some(frame_id) = frame_id {
let mut contexts = self.contexts.write();
if let Some(frame_contexts) = contexts.get_mut(&frame_id) {
frame_contexts.retain(|_, &mut id| id != context_id);
if frame_contexts.is_empty() {
contexts.remove(&frame_id);
}
debug!(
frame_id = %frame_id,
context_id = context_id,
"Execution context destroyed"
);
}
} else {
trace!(
context_id = context_id,
"Execution context destroyed (not tracked)"
);
}
}
pub fn main_world_context(&self, frame_id: &str) -> Option<ExecutionContextId> {
let contexts = self.contexts.read();
contexts
.get(frame_id)
.and_then(|frame_contexts| frame_contexts.get(MAIN_WORLD_KEY).copied())
}
pub fn get_context(&self, frame_id: &str, world_name: &str) -> Option<ExecutionContextId> {
let contexts = self.contexts.read();
contexts
.get(frame_id)
.and_then(|frame_contexts| frame_contexts.get(world_name).copied())
}
pub fn set_context(&self, frame_id: &str, world_name: &str, context_id: ExecutionContextId) {
{
let mut contexts = self.contexts.write();
let frame_contexts = contexts.entry(frame_id.to_string()).or_default();
frame_contexts.insert(world_name.to_string(), context_id);
}
{
let mut context_to_frame = self.context_to_frame.write();
context_to_frame.insert(context_id, frame_id.to_string());
}
}
pub fn clear_frame_contexts(&self, frame_id: &str) {
let contexts_to_remove: Vec<ExecutionContextId> = {
let contexts = self.contexts.read();
contexts
.get(frame_id)
.map(|frame_contexts| frame_contexts.values().copied().collect())
.unwrap_or_default()
};
{
let mut context_to_frame = self.context_to_frame.write();
for context_id in contexts_to_remove {
context_to_frame.remove(&context_id);
}
}
{
let mut contexts = self.contexts.write();
contexts.remove(frame_id);
}
debug!(frame_id = %frame_id, "Cleared all execution contexts for frame");
}
}