use std::collections::HashMap;
use std::net::TcpStream;
use std::time::{SystemTime, UNIX_EPOCH};
use log::warn;
use serde::Serialize;
use serde_json::{Map, Value};
use self::network_parent::{NetworkParentActor, NetworkParentActorMsg};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
use crate::actors::watcher::target_configuration::{
TargetConfigurationActor, TargetConfigurationActorMsg,
};
use crate::actors::watcher::thread_configuration::{
ThreadConfigurationActor, ThreadConfigurationActorMsg,
};
use crate::protocol::JsonPacketStream;
use crate::{EmptyReplyMsg, StreamId};
pub mod network_parent;
pub mod target_configuration;
pub mod thread_configuration;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionContext {
is_server_target_switching_enabled: bool,
supported_targets: HashMap<&'static str, bool>,
supported_resources: HashMap<&'static str, bool>,
context_type: SessionContextType,
}
impl SessionContext {
pub fn new(context_type: SessionContextType) -> Self {
Self {
is_server_target_switching_enabled: false,
supported_targets: HashMap::from([
("frame", true),
("process", false),
("worker", false),
("service_worker", false),
("shared_worker", false),
]),
supported_resources: HashMap::from([
("console-message", true),
("css-change", true),
("css-message", false),
("css-registered-properties", false),
("document-event", false),
("Cache", false),
("cookies", false),
("error-message", true),
("extension-storage", false),
("indexed-db", false),
("local-storage", false),
("session-storage", false),
("platform-message", false),
("network-event", false),
("network-event-stacktrace", false),
("reflow", false),
("stylesheet", false),
("source", false),
("thread-state", false),
("server-sent-event", false),
("websocket", false),
("jstracer-trace", false),
("jstracer-state", false),
("last-private-context-exit", false),
]),
context_type,
}
}
}
#[derive(Serialize)]
pub enum SessionContextType {
BrowserElement,
_ContextProcess,
_WebExtension,
_Worker,
_All,
}
#[derive(Serialize)]
struct WatchTargetsReply {
from: String,
#[serde(rename = "type")]
type_: String,
target: BrowsingContextActorMsg,
}
#[derive(Serialize)]
struct GetParentBrowsingContextIDReply {
from: String,
#[serde(rename = "browsingContextID")]
browsing_context_id: u32,
}
#[derive(Serialize)]
struct GetNetworkParentActorReply {
from: String,
network: NetworkParentActorMsg,
}
#[derive(Serialize)]
struct GetTargetConfigurationActorReply {
from: String,
configuration: TargetConfigurationActorMsg,
}
#[derive(Serialize)]
struct GetThreadConfigurationActorReply {
from: String,
configuration: ThreadConfigurationActorMsg,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DocumentEvent {
#[serde(rename = "hasNativeConsoleAPI")]
has_native_console_api: Option<bool>,
name: String,
#[serde(rename = "newURI")]
new_uri: Option<String>,
time: u64,
title: Option<String>,
url: Option<String>,
}
#[derive(Serialize)]
struct WatcherTraits {
resources: HashMap<&'static str, bool>,
#[serde(flatten)]
targets: HashMap<&'static str, bool>,
}
#[derive(Serialize)]
pub struct WatcherActorMsg {
actor: String,
traits: WatcherTraits,
}
pub struct WatcherActor {
name: String,
browsing_context_actor: String,
network_parent: String,
target_configuration: String,
thread_configuration: String,
session_context: SessionContext,
}
impl Actor for WatcherActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
Ok(match msg_type {
"watchTargets" => {
let msg = WatchTargetsReply {
from: self.name(),
type_: "target-available-form".into(),
target: target.encodable(),
};
let _ = stream.write_json_packet(&msg);
target.frame_update(stream);
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"watchResources" => {
let Some(resource_types) = msg.get("resourceTypes") else {
return Ok(ActorMessageStatus::Ignored);
};
let Some(resource_types) = resource_types.as_array() else {
return Ok(ActorMessageStatus::Ignored);
};
for resource in resource_types {
let Some(resource) = resource.as_str() else {
continue;
};
match resource {
"document-event" => {
for &name in ["dom-loading", "dom-interactive", "dom-complete"].iter() {
let event = DocumentEvent {
has_native_console_api: Some(true),
name: name.into(),
new_uri: None,
time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
as u64,
title: Some(target.title.borrow().clone()),
url: Some(target.url.borrow().clone()),
};
target.resource_available(event, "document-event".into());
}
},
"console-message" | "error-message" => {},
_ => warn!("resource {} not handled yet", resource),
}
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
}
ActorMessageStatus::Processed
},
"getParentBrowsingContextID" => {
let browsing_context_id = target.browsing_context_id.index.0.get();
let msg = GetParentBrowsingContextIDReply {
from: self.name(),
browsing_context_id,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"getNetworkParentActor" => {
let network_parent = registry.find::<NetworkParentActor>(&self.network_parent);
let msg = GetNetworkParentActorReply {
from: self.name(),
network: network_parent.encodable(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"getTargetConfigurationActor" => {
let target_configuration =
registry.find::<TargetConfigurationActor>(&self.target_configuration);
let msg = GetTargetConfigurationActorReply {
from: self.name(),
configuration: target_configuration.encodable(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"getThreadConfigurationActor" => {
let thread_configuration =
registry.find::<ThreadConfigurationActor>(&self.thread_configuration);
let msg = GetThreadConfigurationActorReply {
from: self.name(),
configuration: thread_configuration.encodable(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
}
}
impl WatcherActor {
pub fn new(
actors: &mut ActorRegistry,
browsing_context_actor: String,
session_context: SessionContext,
) -> Self {
let network_parent = NetworkParentActor::new(actors.new_name("network-parent"));
let target_configuration =
TargetConfigurationActor::new(actors.new_name("target-configuration"));
let thread_configuration =
ThreadConfigurationActor::new(actors.new_name("thread-configuration"));
let watcher = Self {
name: actors.new_name("watcher"),
browsing_context_actor,
network_parent: network_parent.name(),
target_configuration: target_configuration.name(),
thread_configuration: thread_configuration.name(),
session_context,
};
actors.register(Box::new(network_parent));
actors.register(Box::new(target_configuration));
actors.register(Box::new(thread_configuration));
watcher
}
pub fn encodable(&self) -> WatcherActorMsg {
WatcherActorMsg {
actor: self.name(),
traits: WatcherTraits {
resources: self.session_context.supported_resources.clone(),
targets: self.session_context.supported_targets.clone(),
},
}
}
}