mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
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:
parent
699f6960f5
commit
370fbf0331
2 changed files with 108 additions and 46 deletions
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue