DevTools: Display tabs and processes (#32475)

* feat: show tabs and processes on devtools

Co-authored-by: fabricedesre <fabrice@desre.org>

* chore: clean for pr

* fix: use serde renaming to avoid camel case

Co-authored-by: Martin Robinson <mrobinson@igalia.com>

* fix: serde rename all to camel case

* refactor: reduce getTab nesting level

---------

Co-authored-by: fabricedesre <fabrice@desre.org>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
eri 2024-06-12 11:23:09 +02:00 committed by GitHub
parent 699f6960f5
commit 370fbf0331
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 108 additions and 46 deletions

View file

@ -20,11 +20,12 @@ use crate::protocol::{ActorDescription, JsonPacketStream};
use crate::StreamId; use crate::StreamId;
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ActorTraits { struct ActorTraits {
sources: bool, sources: bool,
highlightable: bool, highlightable: bool,
customHighlighters: bool, custom_highlighters: bool,
networkMonitor: bool, network_monitor: bool,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -37,12 +38,13 @@ struct ListAddonsReply {
enum AddonMsg {} enum AddonMsg {}
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct GetRootReply { struct GetRootReply {
from: String, from: String,
selected: u32, selected: u32,
performanceActor: String, performance_actor: String,
deviceActor: String, device_actor: String,
preferenceActor: String, preference_actor: String,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -59,9 +61,10 @@ struct GetTabReply {
} }
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RootActorMsg { pub struct RootActorMsg {
from: String, from: String,
applicationType: String, application_type: String,
traits: ActorTraits, traits: ActorTraits,
} }
@ -95,17 +98,28 @@ struct ListProcessesResponse {
processes: Vec<ProcessForm>, processes: Vec<ProcessForm>,
} }
#[derive(Serialize)] #[derive(Default, Serialize)]
struct ProcessForm { #[serde(rename_all = "camelCase")]
actor: String, pub struct DescriptorTraits {
id: u32, pub(crate) watcher: bool,
isParent: bool, pub(crate) supports_reload_descriptor: bool,
} }
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct ProcessForm {
actor: String,
id: u32,
is_parent: bool,
is_windowless_parent: bool,
traits: DescriptorTraits,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct GetProcessResponse { struct GetProcessResponse {
from: String, from: String,
form: ProcessForm, process_descriptor: ProcessForm,
} }
pub struct RootActor { pub struct RootActor {
@ -126,7 +140,7 @@ impl Actor for RootActor {
&self, &self,
registry: &ActorRegistry, registry: &ActorRegistry,
msg_type: &str, msg_type: &str,
_msg: &Map<String, Value>, msg: &Map<String, Value>,
stream: &mut TcpStream, stream: &mut TcpStream,
_id: StreamId, _id: StreamId,
) -> Result<ActorMessageStatus, ()> { ) -> Result<ActorMessageStatus, ()> {
@ -146,7 +160,9 @@ impl Actor for RootActor {
processes: vec![ProcessForm { processes: vec![ProcessForm {
actor: self.process.clone(), actor: self.process.clone(),
id: 0, id: 0,
isParent: true, is_parent: true,
is_windowless_parent: false,
traits: Default::default(),
}], }],
}; };
let _ = stream.write_json_packet(&reply); let _ = stream.write_json_packet(&reply);
@ -156,10 +172,12 @@ impl Actor for RootActor {
"getProcess" => { "getProcess" => {
let reply = GetProcessResponse { let reply = GetProcessResponse {
from: self.name(), from: self.name(),
form: ProcessForm { process_descriptor: ProcessForm {
actor: self.process.clone(), actor: self.process.clone(),
id: 0, id: 0,
isParent: true, is_parent: true,
is_windowless_parent: false,
traits: Default::default(),
}, },
}; };
let _ = stream.write_json_packet(&reply); let _ = stream.write_json_packet(&reply);
@ -170,15 +188,15 @@ impl Actor for RootActor {
let actor = GetRootReply { let actor = GetRootReply {
from: "root".to_owned(), from: "root".to_owned(),
selected: 0, selected: 0,
performanceActor: self.performance.clone(), performance_actor: self.performance.clone(),
deviceActor: self.device.clone(), device_actor: self.device.clone(),
preferenceActor: self.preference.clone(), preference_actor: self.preference.clone(),
}; };
let _ = stream.write_json_packet(&actor); let _ = stream.write_json_packet(&actor);
ActorMessageStatus::Processed ActorMessageStatus::Processed
}, },
// https://docs.firefox-dev.tools/backend/protocol.html#listing-browser-tabs // https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#listing-browser-tabs
"listTabs" => { "listTabs" => {
let actor = ListTabsReply { let actor = ListTabsReply {
from: "root".to_owned(), from: "root".to_owned(),
@ -189,7 +207,7 @@ impl Actor for RootActor {
.map(|target| { .map(|target| {
registry registry
.find::<TabDescriptorActor>(target) .find::<TabDescriptorActor>(target)
.encodable(registry) .encodable(registry, false)
}) })
.collect(), .collect(),
}; };
@ -220,10 +238,18 @@ impl Actor for RootActor {
}, },
"getTab" => { "getTab" => {
let tab = registry.find::<TabDescriptorActor>(&self.tabs[0]); let Some(serde_json::Value::Number(browser_id)) = msg.get("browserId") else {
return Ok(ActorMessageStatus::Ignored);
};
let browser_id = browser_id.as_u64().unwrap();
let Some(tab) = self.get_tab_msg_by_browser_id(registry, browser_id as u32) else {
return Ok(ActorMessageStatus::Ignored);
};
let reply = GetTabReply { let reply = GetTabReply {
from: self.name(), from: self.name(),
tab: tab.encodable(registry), tab,
}; };
let _ = stream.write_json_packet(&reply); let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed ActorMessageStatus::Processed
@ -250,13 +276,28 @@ impl RootActor {
pub fn encodable(&self) -> RootActorMsg { pub fn encodable(&self) -> RootActorMsg {
RootActorMsg { RootActorMsg {
from: "root".to_owned(), from: "root".to_owned(),
applicationType: "browser".to_owned(), application_type: "browser".to_owned(),
traits: ActorTraits { traits: ActorTraits {
sources: false, sources: false,
highlightable: true, highlightable: true,
customHighlighters: true, custom_highlighters: true,
networkMonitor: false, network_monitor: false,
}, },
} }
} }
fn get_tab_msg_by_browser_id(
&self,
registry: &ActorRegistry,
browser_id: u32,
) -> Option<TabDescriptorActorMsg> {
self.tabs
.iter()
.map(|target| {
registry
.find::<TabDescriptorActor>(target)
.encodable(registry, true)
})
.find(|tab| tab.id() == browser_id)
}
} }

View file

@ -9,25 +9,29 @@ use serde_json::{Map, Value};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg}; use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
use crate::actors::root::RootActor; use crate::actors::root::{DescriptorTraits, RootActor};
use crate::protocol::JsonPacketStream; use crate::protocol::JsonPacketStream;
use crate::StreamId; use crate::StreamId;
// https://searchfox.org/mozilla-central/source/devtools/server/actors/descriptors/tab.js
#[derive(Serialize)] #[derive(Serialize)]
pub struct TabDescriptorTraits { #[serde(rename_all = "camelCase")]
getFavicon: bool,
hasTabInfo: bool,
watcher: bool,
}
#[derive(Serialize)]
pub struct TabDescriptorActorMsg { pub struct TabDescriptorActorMsg {
actor: String, actor: String,
browser_id: u32,
browsing_context_id: u32,
is_zombie_tab: bool,
outer_window_id: u32,
selected: bool,
title: String, title: String,
traits: DescriptorTraits,
url: String, url: String,
outerWindowID: u32, }
browsingContextId: u32,
traits: TabDescriptorTraits, impl TabDescriptorActorMsg {
pub fn id(&self) -> u32 {
self.browser_id
}
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -36,6 +40,12 @@ struct GetTargetReply {
frame: BrowsingContextActorMsg, frame: BrowsingContextActorMsg,
} }
#[derive(Serialize)]
struct GetFaviconReply {
from: String,
favicon: String,
}
pub struct TabDescriptorActor { pub struct TabDescriptorActor {
name: String, name: String,
browsing_context_actor: String, browsing_context_actor: String,
@ -65,6 +75,15 @@ impl Actor for TabDescriptorActor {
}); });
ActorMessageStatus::Processed ActorMessageStatus::Processed
}, },
"getFavicon" => {
// TODO: Return a favicon when available
let _ = stream.write_json_packet(&GetFaviconReply {
from: self.name(),
favicon: String::new(),
});
ActorMessageStatus::Processed
},
// TODO: Unexpected message getWatcher when inspecting tab (create watcher actor)
_ => ActorMessageStatus::Ignored, _ => ActorMessageStatus::Ignored,
}) })
} }
@ -84,23 +103,25 @@ impl TabDescriptorActor {
} }
} }
pub fn encodable(&self, registry: &ActorRegistry) -> TabDescriptorActorMsg { pub fn encodable(&self, registry: &ActorRegistry, selected: bool) -> TabDescriptorActorMsg {
let ctx_actor = registry.find::<BrowsingContextActor>(&self.browsing_context_actor); let ctx_actor = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
let title = ctx_actor.title.borrow().clone(); let title = ctx_actor.title.borrow().clone();
let url = ctx_actor.url.borrow().clone(); let url = ctx_actor.url.borrow().clone();
TabDescriptorActorMsg { TabDescriptorActorMsg {
title,
url,
actor: self.name(), actor: self.name(),
browsingContextId: ctx_actor.browsing_context_id.index.0.get(), browsing_context_id: ctx_actor.browsing_context_id.index.0.get(),
outerWindowID: ctx_actor.active_pipeline.get().index.0.get(), browser_id: ctx_actor.active_pipeline.get().index.0.get(),
traits: TabDescriptorTraits { is_zombie_tab: false,
getFavicon: false, outer_window_id: ctx_actor.active_pipeline.get().index.0.get(),
hasTabInfo: true, selected,
watcher: false, title,
traits: DescriptorTraits {
watcher: true,
supports_reload_descriptor: false,
}, },
url,
} }
} }
} }