use atomic_refcell::AtomicRefCell;
use devtools_traits::DevtoolScriptControlMsg::{self, GetCssDatabase, SimulateColorScheme};
use devtools_traits::DevtoolsPageInfo;
use embedder_traits::Theme;
use malloc_size_of_derive::MallocSizeOf;
use rustc_hash::FxHashMap;
use serde::Serialize;
use serde_json::{Map, Value};
use servo_base::generic_channel::{self, GenericSender, SendError};
use servo_base::id::PipelineId;
use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
use crate::actors::inspector::InspectorActor;
use crate::actors::inspector::accessibility::AccessibilityActor;
use crate::actors::inspector::css_properties::CssPropertiesActor;
use crate::actors::reflow::ReflowActor;
use crate::actors::stylesheets::StyleSheetsActor;
use crate::actors::tab::TabDescriptorActor;
use crate::actors::thread::ThreadActor;
use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId};
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::resource::ResourceAvailable;
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
struct ListWorkersReply {
from: String,
workers: Vec<()>,
}
#[derive(Serialize)]
struct FrameUpdateReply {
from: String,
#[serde(rename = "type")]
type_: String,
frames: Vec<FrameUpdateMsg>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct FrameUpdateMsg {
id: u32,
is_top_level: bool,
url: String,
title: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct BrowsingContextTraits {
frames: bool,
is_browsing_context: bool,
log_in_page: bool,
navigation: bool,
supports_top_level_target_flag: bool,
watchpoints: bool,
}
#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
enum TargetType {
Frame,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct BrowsingContextActorMsg {
actor: String,
title: String,
url: String,
#[serde(rename = "browserId")]
browser_id: u32,
#[serde(rename = "outerWindowID")]
outer_window_id: u32,
#[serde(rename = "browsingContextID")]
browsing_context_id: u32,
is_top_level_target: bool,
traits: BrowsingContextTraits,
accessibility_actor: String,
console_actor: String,
css_properties_actor: String,
inspector_actor: String,
reflow_actor: String,
style_sheets_actor: String,
thread_actor: String,
target_type: TargetType,
}
#[derive(MallocSizeOf)]
pub(crate) struct BrowsingContextActor {
name: String,
pub browser_id: DevtoolsBrowserId,
active_pipeline_id: AtomicRefCell<PipelineId>,
active_outer_window_id: AtomicRefCell<DevtoolsOuterWindowId>,
pub browsing_context_id: DevtoolsBrowsingContextId,
accessibility_name: String,
pub console_name: String,
css_properties_name: String,
pub inspector_name: String,
pub page_info: AtomicRefCell<DevtoolsPageInfo>,
reflow_name: String,
pub style_sheets_name: String,
pub thread_name: String,
script_chans: AtomicRefCell<FxHashMap<PipelineId, GenericSender<DevtoolScriptControlMsg>>>,
pub watcher_name: String,
}
impl ResourceAvailable for BrowsingContextActor {
fn actor_name(&self) -> String {
self.name.clone()
}
}
impl Actor for BrowsingContextActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
_id: StreamId,
) -> Result<(), ActorError> {
match msg_type {
"listFrames" => {
let msg = EmptyReplyMsg { from: self.name() };
request.reply_final(&msg)?
},
"listWorkers" => {
request.reply_final(&ListWorkersReply {
from: self.name(),
workers: vec![],
})?
},
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}
impl BrowsingContextActor {
#[expect(clippy::too_many_arguments)]
pub(crate) fn register(
registry: &ActorRegistry,
console_name: String,
browser_id: DevtoolsBrowserId,
browsing_context_id: DevtoolsBrowsingContextId,
page_info: DevtoolsPageInfo,
pipeline_id: PipelineId,
outer_window_id: DevtoolsOuterWindowId,
script_sender: GenericSender<DevtoolScriptControlMsg>,
) -> String {
let name = registry.new_name::<BrowsingContextActor>();
let accessibility_name = AccessibilityActor::register(registry);
let properties = (|| {
let (properties_sender, properties_receiver) = generic_channel::channel()?;
script_sender.send(GetCssDatabase(properties_sender)).ok()?;
properties_receiver.recv().ok()
})()
.unwrap_or_default();
let css_properties_name = CssPropertiesActor::register(registry, properties);
let inspector_name = InspectorActor::register(registry, name.clone());
let reflow_name = ReflowActor::register(registry);
let style_sheets_name =
StyleSheetsActor::register(registry, script_sender.clone(), name.clone());
let _ = TabDescriptorActor::register(registry, name.clone());
let thread_name =
ThreadActor::register(registry, script_sender.clone(), Some(name.clone()));
let watcher_name = WatcherActor::register(
registry,
name.clone(),
SessionContext::new(SessionContextType::BrowserElement),
);
let mut script_chans = FxHashMap::default();
script_chans.insert(pipeline_id, script_sender);
let actor = BrowsingContextActor {
name: name.clone(),
script_chans: script_chans.into(),
active_pipeline_id: pipeline_id.into(),
active_outer_window_id: outer_window_id.into(),
browser_id,
browsing_context_id,
accessibility_name,
console_name,
css_properties_name,
inspector_name,
page_info: page_info.into(),
reflow_name,
style_sheets_name,
thread_name,
watcher_name,
};
registry.register::<Self>(actor);
name
}
pub(crate) fn handle_new_global(
&self,
pipeline: PipelineId,
script_sender: GenericSender<DevtoolScriptControlMsg>,
) {
self.script_chans
.borrow_mut()
.insert(pipeline, script_sender);
}
pub(crate) fn title_changed(&self, pipeline_id: PipelineId, title: String) {
if pipeline_id != self.pipeline_id() {
return;
}
self.page_info.borrow_mut().title = title;
}
pub(crate) fn frame_update(&self, request: &mut ClientRequest) {
let _ = request.write_json_packet(&FrameUpdateReply {
from: self.name(),
type_: "frameUpdate".into(),
frames: vec![FrameUpdateMsg {
id: self.browsing_context_id.value(),
is_top_level: true,
title: self.title(),
url: self.url(),
}],
});
}
pub fn simulate_color_scheme(&self, theme: Theme) -> Result<(), ()> {
self.script_chan()
.send(SimulateColorScheme(self.pipeline_id(), theme))
.map_err(|_| ())
}
pub(crate) fn pipeline_id(&self) -> PipelineId {
*self.active_pipeline_id.borrow()
}
pub(crate) fn title(&self) -> String {
self.page_info.borrow().title.clone()
}
pub(crate) fn url(&self) -> String {
self.page_info.borrow().url.clone().into_string()
}
pub(crate) fn update_pipeline(
&self,
pipeline_id: PipelineId,
outer_window_id: DevtoolsOuterWindowId,
page_info: DevtoolsPageInfo,
) {
*self.active_pipeline_id.borrow_mut() = pipeline_id;
*self.active_outer_window_id.borrow_mut() = outer_window_id;
*self.page_info.borrow_mut() = page_info;
}
pub(crate) fn outer_window_id(&self) -> DevtoolsOuterWindowId {
*self.active_outer_window_id.borrow()
}
pub(crate) fn script_chan(&self) -> GenericSender<DevtoolScriptControlMsg> {
self.script_chans
.borrow()
.get(&self.pipeline_id())
.unwrap()
.clone()
}
pub(crate) fn instruct_script_to_send_live_updates(&self, should_send_updates: bool) {
let result = self
.script_chan()
.send(DevtoolScriptControlMsg::WantsLiveNotifications(
self.pipeline_id(),
should_send_updates,
));
debug_assert!(matches!(result, Ok(_) | Err(SendError::Disconnected)));
}
}
impl ActorEncode<BrowsingContextActorMsg> for BrowsingContextActor {
fn encode(&self, _: &ActorRegistry) -> BrowsingContextActorMsg {
BrowsingContextActorMsg {
actor: self.name(),
traits: BrowsingContextTraits {
is_browsing_context: true,
frames: true,
log_in_page: false,
navigation: true,
supports_top_level_target_flag: true,
watchpoints: true,
},
title: self.title(),
url: self.url(),
browser_id: self.browser_id.value(),
browsing_context_id: self.browsing_context_id.value(),
outer_window_id: self.outer_window_id().value(),
is_top_level_target: true,
accessibility_actor: self.accessibility_name.clone(),
console_actor: self.console_name.clone(),
css_properties_actor: self.css_properties_name.clone(),
inspector_actor: self.inspector_name.clone(),
reflow_actor: self.reflow_name.clone(),
style_sheets_actor: self.style_sheets_name.clone(),
thread_actor: self.thread_name.clone(),
target_type: TargetType::Frame,
}
}
}