mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
DevTools: Implement watcher actor (#32509)
* feat: base for watcher * feat: some more watcher tests * feat: implement getWatcher * refactor: clean up getWatcher * feat: implement watchTargets * feat: implement watchResources * feat: very messy watchTargets fix * refactor: clean browsing context * feat: target configuration * refactor: start cleanup * refactor: more doc coments * refactor: clean browsing context
This commit is contained in:
parent
26c585a0c5
commit
5eb8813448
9 changed files with 680 additions and 244 deletions
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
//! Liberally derived from the [Firefox JS implementation](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webbrowser.js).
|
//! Liberally derived from the [Firefox JS implementation](https://searchfox.org/mozilla-central/source/devtools/server/actors/webbrowser.js).
|
||||||
//! Connection point for remote devtools that wish to investigate a particular Browsing Context's contents.
|
//! Connection point for remote devtools that wish to investigate a particular Browsing Context's contents.
|
||||||
//! Supports dynamic attaching and detaching which control notifications of navigation, etc.
|
//! Supports dynamic attaching and detaching which control notifications of navigation, etc.
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ use serde::Serialize;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
||||||
|
use crate::actors::configuration::{TargetConfigurationActor, ThreadConfigurationActor};
|
||||||
use crate::actors::emulation::EmulationActor;
|
use crate::actors::emulation::EmulationActor;
|
||||||
use crate::actors::inspector::InspectorActor;
|
use crate::actors::inspector::InspectorActor;
|
||||||
use crate::actors::performance::PerformanceActor;
|
use crate::actors::performance::PerformanceActor;
|
||||||
|
@ -26,118 +27,126 @@ use crate::actors::stylesheets::StyleSheetsActor;
|
||||||
use crate::actors::tab::TabDescriptorActor;
|
use crate::actors::tab::TabDescriptorActor;
|
||||||
use crate::actors::thread::ThreadActor;
|
use crate::actors::thread::ThreadActor;
|
||||||
use crate::actors::timeline::TimelineActor;
|
use crate::actors::timeline::TimelineActor;
|
||||||
|
use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
|
||||||
use crate::protocol::JsonPacketStream;
|
use crate::protocol::JsonPacketStream;
|
||||||
use crate::StreamId;
|
use crate::StreamId;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct BrowsingContextTraits {
|
struct FrameUpdateReply {
|
||||||
isBrowsingContext: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct AttachedTraits {
|
|
||||||
reconfigure: bool,
|
|
||||||
frames: bool,
|
|
||||||
logInPage: bool,
|
|
||||||
canRewind: bool,
|
|
||||||
watchpoints: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct BrowsingContextAttachedReply {
|
|
||||||
from: String,
|
from: String,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
type_: String,
|
type_: String,
|
||||||
threadActor: String,
|
frames: Vec<FrameUpdateMsg>,
|
||||||
cacheDisabled: bool,
|
|
||||||
javascriptEnabled: bool,
|
|
||||||
traits: AttachedTraits,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct BrowsingContextDetachedReply {
|
#[serde(rename_all = "camelCase")]
|
||||||
from: String,
|
struct FrameUpdateMsg {
|
||||||
#[serde(rename = "type")]
|
|
||||||
type_: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct ReconfigureReply {
|
|
||||||
from: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct ListFramesReply {
|
|
||||||
from: String,
|
|
||||||
frames: Vec<FrameMsg>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct FrameMsg {
|
|
||||||
id: u32,
|
id: u32,
|
||||||
|
is_top_level: bool,
|
||||||
url: String,
|
url: String,
|
||||||
title: String,
|
title: String,
|
||||||
parentID: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ListWorkersReply {
|
struct ResourceAvailableReply {
|
||||||
from: String,
|
from: String,
|
||||||
workers: Vec<WorkerMsg>,
|
#[serde(rename = "type")]
|
||||||
|
type_: String,
|
||||||
|
resources: Vec<ResourceAvailableMsg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct WorkerMsg {
|
#[serde(rename_all = "camelCase")]
|
||||||
id: u32,
|
struct ResourceAvailableMsg {
|
||||||
|
#[serde(rename = "hasNativeConsoleAPI")]
|
||||||
|
has_native_console_api: Option<bool>,
|
||||||
|
name: String,
|
||||||
|
#[serde(rename = "newURI")]
|
||||||
|
new_uri: Option<String>,
|
||||||
|
resource_type: String,
|
||||||
|
time: u64,
|
||||||
|
title: Option<String>,
|
||||||
|
url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
struct TabNavigated {
|
||||||
|
from: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
type_: String,
|
||||||
|
url: String,
|
||||||
|
title: Option<String>,
|
||||||
|
#[serde(rename = "nativeConsoleAPI")]
|
||||||
|
native_console_api: bool,
|
||||||
|
state: String,
|
||||||
|
is_frame_switching: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct BrowsingContextTraits {
|
||||||
|
is_browsing_context: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BrowsingContextActorMsg {
|
pub struct BrowsingContextActorMsg {
|
||||||
actor: String,
|
actor: String,
|
||||||
title: String,
|
title: String,
|
||||||
url: String,
|
url: String,
|
||||||
outerWindowID: u32,
|
#[serde(rename = "outerWindowID")]
|
||||||
browsingContextId: u32,
|
outer_window_id: u32,
|
||||||
consoleActor: String,
|
#[serde(rename = "browsingContextID")]
|
||||||
/*emulationActor: String,
|
browsing_context_id: u32,
|
||||||
inspectorActor: String,
|
is_top_level_target: bool,
|
||||||
timelineActor: String,
|
console_actor: String,
|
||||||
profilerActor: String,
|
thread_actor: String,
|
||||||
performanceActor: String,
|
|
||||||
styleSheetsActor: String,*/
|
|
||||||
traits: BrowsingContextTraits,
|
traits: BrowsingContextTraits,
|
||||||
// Part of the official protocol, but not yet implemented.
|
// Part of the official protocol, but not yet implemented.
|
||||||
/*storageActor: String,
|
// emulation_actor: String,
|
||||||
memoryActor: String,
|
// inspector_actor: String,
|
||||||
framerateActor: String,
|
// timeline_actor: String,
|
||||||
reflowActor: String,
|
// profiler_actor: String,
|
||||||
cssPropertiesActor: String,
|
// performance_actor: String,
|
||||||
animationsActor: String,
|
// style_sheets_actor: String,
|
||||||
webExtensionInspectedWindowActor: String,
|
// storage_actor: String,
|
||||||
accessibilityActor: String,
|
// memory_actor: String,
|
||||||
screenshotActor: String,
|
// framerate_actor: String,
|
||||||
changesActor: String,
|
// reflow_actor: String,
|
||||||
webSocketActor: String,
|
// css_properties_actor: String,
|
||||||
manifestActor: String,*/
|
// animations_actor: String,
|
||||||
|
// web_extension_inspected_window_actor: String,
|
||||||
|
// accessibility_actor: String,
|
||||||
|
// screenshot_actor: String,
|
||||||
|
// changes_actor: String,
|
||||||
|
// web_socket_actor: String,
|
||||||
|
// manifest_actor: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The browsing context actor encompasses all of the other supporting actors when debugging a web
|
||||||
|
/// view. To this extent, it contains a watcher actor that helps when communicating with the host,
|
||||||
|
/// as well as resource actors that each perform one debugging function.
|
||||||
pub(crate) struct BrowsingContextActor {
|
pub(crate) struct BrowsingContextActor {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub title: RefCell<String>,
|
pub title: RefCell<String>,
|
||||||
pub url: RefCell<String>,
|
pub url: RefCell<String>,
|
||||||
|
pub active_pipeline: Cell<PipelineId>,
|
||||||
|
pub browsing_context_id: BrowsingContextId,
|
||||||
pub console: String,
|
pub console: String,
|
||||||
pub _emulation: String,
|
pub _emulation: String,
|
||||||
pub _inspector: String,
|
pub _inspector: String,
|
||||||
pub _timeline: String,
|
|
||||||
pub _profiler: String,
|
|
||||||
pub _performance: String,
|
pub _performance: String,
|
||||||
pub _styleSheets: String,
|
pub _profiler: String,
|
||||||
|
pub _style_sheets: String,
|
||||||
|
pub target_configuration: String,
|
||||||
|
pub thread_configuration: String,
|
||||||
pub thread: String,
|
pub thread: String,
|
||||||
|
pub _timeline: String,
|
||||||
pub _tab: String,
|
pub _tab: String,
|
||||||
pub streams: RefCell<HashMap<StreamId, TcpStream>>,
|
|
||||||
pub browsing_context_id: BrowsingContextId,
|
|
||||||
pub active_pipeline: Cell<PipelineId>,
|
|
||||||
pub script_chan: IpcSender<DevtoolScriptControlMsg>,
|
pub script_chan: IpcSender<DevtoolScriptControlMsg>,
|
||||||
|
pub streams: RefCell<HashMap<StreamId, TcpStream>>,
|
||||||
|
pub watcher: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for BrowsingContextActor {
|
impl Actor for BrowsingContextActor {
|
||||||
|
@ -149,89 +158,11 @@ impl Actor for BrowsingContextActor {
|
||||||
&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, ()> {
|
||||||
Ok(match msg_type {
|
Ok(match msg_type {
|
||||||
"reconfigure" => {
|
|
||||||
if let Some(options) = msg.get("options").and_then(|o| o.as_object()) {
|
|
||||||
if let Some(val) = options.get("performReload") {
|
|
||||||
if val.as_bool().unwrap_or(false) {
|
|
||||||
let _ = self
|
|
||||||
.script_chan
|
|
||||||
.send(DevtoolScriptControlMsg::Reload(self.active_pipeline.get()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = stream.write_json_packet(&ReconfigureReply { from: self.name() });
|
|
||||||
ActorMessageStatus::Processed
|
|
||||||
},
|
|
||||||
|
|
||||||
// https://docs.firefox-dev.tools/backend/protocol.html#listing-browser-tabs
|
|
||||||
// (see "To attach to a _targetActor_")
|
|
||||||
"attach" => {
|
|
||||||
let msg = BrowsingContextAttachedReply {
|
|
||||||
from: self.name(),
|
|
||||||
type_: "tabAttached".to_owned(),
|
|
||||||
threadActor: self.thread.clone(),
|
|
||||||
cacheDisabled: false,
|
|
||||||
javascriptEnabled: true,
|
|
||||||
traits: AttachedTraits {
|
|
||||||
reconfigure: false,
|
|
||||||
frames: true,
|
|
||||||
logInPage: false,
|
|
||||||
canRewind: false,
|
|
||||||
watchpoints: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if stream.write_json_packet(&msg).is_err() {
|
|
||||||
return Ok(ActorMessageStatus::Processed);
|
|
||||||
}
|
|
||||||
self.streams
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(id, stream.try_clone().unwrap());
|
|
||||||
self.script_chan
|
|
||||||
.send(WantsLiveNotifications(self.active_pipeline.get(), true))
|
|
||||||
.unwrap();
|
|
||||||
ActorMessageStatus::Processed
|
|
||||||
},
|
|
||||||
|
|
||||||
"detach" => {
|
|
||||||
let msg = BrowsingContextDetachedReply {
|
|
||||||
from: self.name(),
|
|
||||||
type_: "detached".to_owned(),
|
|
||||||
};
|
|
||||||
let _ = stream.write_json_packet(&msg);
|
|
||||||
self.cleanup(id);
|
|
||||||
ActorMessageStatus::Processed
|
|
||||||
},
|
|
||||||
|
|
||||||
"listFrames" => {
|
|
||||||
let msg = ListFramesReply {
|
|
||||||
from: self.name(),
|
|
||||||
frames: vec![FrameMsg {
|
|
||||||
//FIXME: shouldn't ignore pipeline namespace field
|
|
||||||
id: self.active_pipeline.get().index.0.get(),
|
|
||||||
parentID: 0,
|
|
||||||
url: self.url.borrow().clone(),
|
|
||||||
title: self.title.borrow().clone(),
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
let _ = stream.write_json_packet(&msg);
|
|
||||||
ActorMessageStatus::Processed
|
|
||||||
},
|
|
||||||
|
|
||||||
"listWorkers" => {
|
|
||||||
let msg = ListWorkersReply {
|
|
||||||
from: self.name(),
|
|
||||||
workers: vec![],
|
|
||||||
};
|
|
||||||
let _ = stream.write_json_packet(&msg);
|
|
||||||
ActorMessageStatus::Processed
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => ActorMessageStatus::Ignored,
|
_ => ActorMessageStatus::Ignored,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -255,9 +186,10 @@ impl BrowsingContextActor {
|
||||||
script_sender: IpcSender<DevtoolScriptControlMsg>,
|
script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||||
actors: &mut ActorRegistry,
|
actors: &mut ActorRegistry,
|
||||||
) -> BrowsingContextActor {
|
) -> BrowsingContextActor {
|
||||||
let emulation = EmulationActor::new(actors.new_name("emulation"));
|
|
||||||
|
|
||||||
let name = actors.new_name("target");
|
let name = actors.new_name("target");
|
||||||
|
let DevtoolsPageInfo { title, url } = page_info;
|
||||||
|
|
||||||
|
let emulation = EmulationActor::new(actors.new_name("emulation"));
|
||||||
|
|
||||||
let inspector = InspectorActor {
|
let inspector = InspectorActor {
|
||||||
name: actors.new_name("inspector"),
|
name: actors.new_name("inspector"),
|
||||||
|
@ -268,48 +200,66 @@ impl BrowsingContextActor {
|
||||||
browsing_context: name.clone(),
|
browsing_context: name.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let timeline =
|
let performance = PerformanceActor::new(actors.new_name("performance"));
|
||||||
TimelineActor::new(actors.new_name("timeline"), pipeline, script_sender.clone());
|
|
||||||
|
|
||||||
let profiler = ProfilerActor::new(actors.new_name("profiler"));
|
let profiler = ProfilerActor::new(actors.new_name("profiler"));
|
||||||
let performance = PerformanceActor::new(actors.new_name("performance"));
|
|
||||||
|
|
||||||
// the strange switch between styleSheets and stylesheets is due
|
// the strange switch between styleSheets and stylesheets is due
|
||||||
// to an inconsistency in devtools. See Bug #1498893 in bugzilla
|
// to an inconsistency in devtools. See Bug #1498893 in bugzilla
|
||||||
let styleSheets = StyleSheetsActor::new(actors.new_name("stylesheets"));
|
let style_sheets = StyleSheetsActor::new(actors.new_name("stylesheets"));
|
||||||
let thread = ThreadActor::new(actors.new_name("context"));
|
|
||||||
|
|
||||||
let DevtoolsPageInfo { title, url } = page_info;
|
|
||||||
|
|
||||||
let tabdesc = TabDescriptorActor::new(actors, name.clone());
|
let tabdesc = TabDescriptorActor::new(actors, name.clone());
|
||||||
|
|
||||||
|
let target_configuration =
|
||||||
|
TargetConfigurationActor::new(actors.new_name("target-configuration"));
|
||||||
|
|
||||||
|
let thread_configuration =
|
||||||
|
ThreadConfigurationActor::new(actors.new_name("thread-configuration"));
|
||||||
|
|
||||||
|
let thread = ThreadActor::new(actors.new_name("context"));
|
||||||
|
|
||||||
|
let timeline =
|
||||||
|
TimelineActor::new(actors.new_name("timeline"), pipeline, script_sender.clone());
|
||||||
|
|
||||||
|
let watcher = WatcherActor::new(
|
||||||
|
actors.new_name("watcher"),
|
||||||
|
name.clone(),
|
||||||
|
SessionContext::new(SessionContextType::BrowserElement),
|
||||||
|
);
|
||||||
|
|
||||||
let target = BrowsingContextActor {
|
let target = BrowsingContextActor {
|
||||||
name,
|
name,
|
||||||
script_chan: script_sender,
|
script_chan: script_sender,
|
||||||
title: RefCell::new(title),
|
title: RefCell::new(title),
|
||||||
url: RefCell::new(url.into_string()),
|
url: RefCell::new(url.into_string()),
|
||||||
|
active_pipeline: Cell::new(pipeline),
|
||||||
|
browsing_context_id: id,
|
||||||
console,
|
console,
|
||||||
_emulation: emulation.name(),
|
_emulation: emulation.name(),
|
||||||
_inspector: inspector.name(),
|
_inspector: inspector.name(),
|
||||||
_timeline: timeline.name(),
|
|
||||||
_profiler: profiler.name(),
|
|
||||||
_performance: performance.name(),
|
_performance: performance.name(),
|
||||||
_styleSheets: styleSheets.name(),
|
_profiler: profiler.name(),
|
||||||
_tab: tabdesc.name(),
|
|
||||||
thread: thread.name(),
|
|
||||||
streams: RefCell::new(HashMap::new()),
|
streams: RefCell::new(HashMap::new()),
|
||||||
browsing_context_id: id,
|
_style_sheets: style_sheets.name(),
|
||||||
active_pipeline: Cell::new(pipeline),
|
_tab: tabdesc.name(),
|
||||||
|
target_configuration: target_configuration.name(),
|
||||||
|
thread_configuration: thread_configuration.name(),
|
||||||
|
thread: thread.name(),
|
||||||
|
_timeline: timeline.name(),
|
||||||
|
watcher: watcher.name(),
|
||||||
};
|
};
|
||||||
|
|
||||||
actors.register(Box::new(emulation));
|
actors.register(Box::new(emulation));
|
||||||
actors.register(Box::new(inspector));
|
actors.register(Box::new(inspector));
|
||||||
actors.register(Box::new(timeline));
|
|
||||||
actors.register(Box::new(profiler));
|
|
||||||
actors.register(Box::new(performance));
|
actors.register(Box::new(performance));
|
||||||
actors.register(Box::new(styleSheets));
|
actors.register(Box::new(profiler));
|
||||||
actors.register(Box::new(thread));
|
actors.register(Box::new(style_sheets));
|
||||||
actors.register(Box::new(tabdesc));
|
actors.register(Box::new(tabdesc));
|
||||||
|
actors.register(Box::new(target_configuration));
|
||||||
|
actors.register(Box::new(thread_configuration));
|
||||||
|
actors.register(Box::new(thread));
|
||||||
|
actors.register(Box::new(timeline));
|
||||||
|
actors.register(Box::new(watcher));
|
||||||
|
|
||||||
target
|
target
|
||||||
}
|
}
|
||||||
|
@ -318,21 +268,23 @@ impl BrowsingContextActor {
|
||||||
BrowsingContextActorMsg {
|
BrowsingContextActorMsg {
|
||||||
actor: self.name(),
|
actor: self.name(),
|
||||||
traits: BrowsingContextTraits {
|
traits: BrowsingContextTraits {
|
||||||
isBrowsingContext: true,
|
is_browsing_context: true,
|
||||||
},
|
},
|
||||||
title: self.title.borrow().clone(),
|
title: self.title.borrow().clone(),
|
||||||
url: self.url.borrow().clone(),
|
url: self.url.borrow().clone(),
|
||||||
//FIXME: shouldn't ignore pipeline namespace field
|
//FIXME: shouldn't ignore pipeline namespace field
|
||||||
browsingContextId: self.browsing_context_id.index.0.get(),
|
browsing_context_id: self.browsing_context_id.index.0.get(),
|
||||||
//FIXME: shouldn't ignore pipeline namespace field
|
//FIXME: shouldn't ignore pipeline namespace field
|
||||||
outerWindowID: self.active_pipeline.get().index.0.get(),
|
outer_window_id: self.active_pipeline.get().index.0.get(),
|
||||||
consoleActor: self.console.clone(),
|
is_top_level_target: true,
|
||||||
/*emulationActor: self.emulation.clone(),
|
console_actor: self.console.clone(),
|
||||||
inspectorActor: self.inspector.clone(),
|
thread_actor: self.thread.clone(),
|
||||||
timelineActor: self.timeline.clone(),
|
// emulation_actor: self.emulation.clone(),
|
||||||
profilerActor: self.profiler.clone(),
|
// inspector_actor: self.inspector.clone(),
|
||||||
performanceActor: self.performance.clone(),
|
// performance_actor: self.performance.clone(),
|
||||||
styleSheetsActor: self.styleSheets.clone(),*/
|
// profiler_actor: self.profiler.clone(),
|
||||||
|
// style_sheets_actor: self.style_sheets.clone(),
|
||||||
|
// timeline_actor: self.timeline.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,10 +308,11 @@ impl BrowsingContextActor {
|
||||||
type_: "tabNavigated".to_owned(),
|
type_: "tabNavigated".to_owned(),
|
||||||
url: url.as_str().to_owned(),
|
url: url.as_str().to_owned(),
|
||||||
title,
|
title,
|
||||||
nativeConsoleAPI: true,
|
native_console_api: true,
|
||||||
state: state.to_owned(),
|
state: state.to_owned(),
|
||||||
isFrameSwitching: false,
|
is_frame_switching: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
for stream in self.streams.borrow_mut().values_mut() {
|
for stream in self.streams.borrow_mut().values_mut() {
|
||||||
let _ = stream.write_json_packet(&msg);
|
let _ = stream.write_json_packet(&msg);
|
||||||
}
|
}
|
||||||
|
@ -371,16 +324,40 @@ impl BrowsingContextActor {
|
||||||
}
|
}
|
||||||
*self.title.borrow_mut() = title;
|
*self.title.borrow_mut() = title;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
pub(crate) fn frame_update(&self, stream: &mut TcpStream) {
|
||||||
struct TabNavigated {
|
let _ = stream.write_json_packet(&FrameUpdateReply {
|
||||||
from: String,
|
from: self.name(),
|
||||||
#[serde(rename = "type")]
|
type_: "frameUpdate".into(),
|
||||||
type_: String,
|
frames: vec![FrameUpdateMsg {
|
||||||
url: String,
|
id: self.browsing_context_id.index.0.get(),
|
||||||
title: Option<String>,
|
is_top_level: true,
|
||||||
nativeConsoleAPI: bool,
|
title: self.title.borrow().clone(),
|
||||||
state: String,
|
url: self.url.borrow().clone(),
|
||||||
isFrameSwitching: bool,
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn document_event(&self, stream: &mut TcpStream) {
|
||||||
|
// TODO: This is a hacky way of sending the 3 messages
|
||||||
|
// Figure out if there needs work to be done here, ensure the page is loaded
|
||||||
|
for (i, &name) in ["dom-loading", "dom-interactive", "dom-complete"]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let _ = stream.write_json_packet(&ResourceAvailableReply {
|
||||||
|
from: self.name(),
|
||||||
|
type_: "resource-available-form".into(),
|
||||||
|
resources: vec![ResourceAvailableMsg {
|
||||||
|
has_native_console_api: None,
|
||||||
|
name: name.into(),
|
||||||
|
new_uri: None,
|
||||||
|
resource_type: "document-event".into(),
|
||||||
|
time: i as u64,
|
||||||
|
title: Some(self.title.borrow().clone()),
|
||||||
|
url: Some(self.url.borrow().clone()),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
153
components/devtools/actors/configuration.rs
Normal file
153
components/devtools/actors/configuration.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Liberally derived from <https://searchfox.org/mozilla-central/source/devtools/server/actors/target-configuration.js>
|
||||||
|
//! and <https://searchfox.org/mozilla-central/source/devtools/server/actors/thread-configuration.js>
|
||||||
|
//! These actors manage the configuration flags that the devtools host can apply to the targets and threads.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
||||||
|
use crate::protocol::JsonPacketStream;
|
||||||
|
use crate::{EmptyReplyMsg, StreamId};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct TargetConfigurationTraits {
|
||||||
|
supported_options: HashMap<&'static str, bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct TargetConfigurationActorMsg {
|
||||||
|
actor: String,
|
||||||
|
configuration: HashMap<&'static str, bool>,
|
||||||
|
traits: TargetConfigurationTraits,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TargetConfigurationActor {
|
||||||
|
name: String,
|
||||||
|
configuration: HashMap<&'static str, bool>,
|
||||||
|
supported_options: HashMap<&'static str, bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ThreadConfigurationActorMsg {
|
||||||
|
actor: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ThreadConfigurationActor {
|
||||||
|
name: String,
|
||||||
|
_configuration: HashMap<&'static str, bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for TargetConfigurationActor {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The target configuration actor can handle the following messages:
|
||||||
|
///
|
||||||
|
/// - `updateConfiguration`: Receives new configuration flags from the devtools host.
|
||||||
|
fn handle_message(
|
||||||
|
&self,
|
||||||
|
_registry: &ActorRegistry,
|
||||||
|
msg_type: &str,
|
||||||
|
_msg: &Map<String, Value>,
|
||||||
|
stream: &mut TcpStream,
|
||||||
|
_id: StreamId,
|
||||||
|
) -> Result<ActorMessageStatus, ()> {
|
||||||
|
Ok(match msg_type {
|
||||||
|
"updateConfiguration" => {
|
||||||
|
// TODO: Actually update configuration
|
||||||
|
let _ = stream.write_json_packet(&EmptyReplyMsg { from: self.name() });
|
||||||
|
|
||||||
|
ActorMessageStatus::Processed
|
||||||
|
},
|
||||||
|
_ => ActorMessageStatus::Ignored,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TargetConfigurationActor {
|
||||||
|
pub fn new(name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
configuration: HashMap::new(),
|
||||||
|
supported_options: HashMap::from([
|
||||||
|
("cacheDisabled", false),
|
||||||
|
("colorSchemeSimulation", false),
|
||||||
|
("customFormatters", false),
|
||||||
|
("customUserAgent", false),
|
||||||
|
("javascriptEnabled", false),
|
||||||
|
("overrideDPPX", false),
|
||||||
|
("printSimulationEnabled", false),
|
||||||
|
("rdmPaneMaxTouchPoints", false),
|
||||||
|
("rdmPaneOrientation", false),
|
||||||
|
("recordAllocations", false),
|
||||||
|
("reloadOnTouchSimulationToggle", false),
|
||||||
|
("restoreFocus", false),
|
||||||
|
("serviceWorkersTestingEnabled", false),
|
||||||
|
("setTabOffline", false),
|
||||||
|
("touchEventsOverride", false),
|
||||||
|
("tracerOptions", false),
|
||||||
|
("useSimpleHighlightersForReducedMotion", false),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encodable(&self) -> TargetConfigurationActorMsg {
|
||||||
|
TargetConfigurationActorMsg {
|
||||||
|
actor: self.name(),
|
||||||
|
configuration: self.configuration.clone(),
|
||||||
|
traits: TargetConfigurationTraits {
|
||||||
|
supported_options: self.supported_options.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for ThreadConfigurationActor {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The thread configuration actor can handle the following messages:
|
||||||
|
///
|
||||||
|
/// - `updateConfiguration`: Receives new configuration flags from the devtools host.
|
||||||
|
fn handle_message(
|
||||||
|
&self,
|
||||||
|
_registry: &ActorRegistry,
|
||||||
|
msg_type: &str,
|
||||||
|
_msg: &Map<String, Value>,
|
||||||
|
stream: &mut TcpStream,
|
||||||
|
_id: StreamId,
|
||||||
|
) -> Result<ActorMessageStatus, ()> {
|
||||||
|
Ok(match msg_type {
|
||||||
|
"updateConfiguration" => {
|
||||||
|
// TODO: Actually update configuration
|
||||||
|
let _ = stream.write_json_packet(&EmptyReplyMsg { from: self.name() });
|
||||||
|
|
||||||
|
ActorMessageStatus::Processed
|
||||||
|
},
|
||||||
|
_ => ActorMessageStatus::Ignored,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadConfigurationActor {
|
||||||
|
pub fn new(name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
_configuration: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encodable(&self) -> ThreadConfigurationActorMsg {
|
||||||
|
ThreadConfigurationActorMsg { actor: self.name() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,7 +59,7 @@ impl Actor for DeviceActor {
|
||||||
apptype: "servo".to_string(),
|
apptype: "servo".to_string(),
|
||||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
appbuildid: BUILD_ID.to_string(),
|
appbuildid: BUILD_ID.to_string(),
|
||||||
platformversion: "124.0".to_string(),
|
platformversion: "125.0".to_string(),
|
||||||
brandName: "Servo".to_string(),
|
brandName: "Servo".to_string(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,12 +2,16 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Liberally derived from the [Firefox JS implementation]
|
||||||
|
//! (https://searchfox.org/mozilla-central/source/devtools/server/actors/descriptors/process.js)
|
||||||
|
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
||||||
|
use crate::actors::root::DescriptorTraits;
|
||||||
use crate::protocol::JsonPacketStream;
|
use crate::protocol::JsonPacketStream;
|
||||||
use crate::StreamId;
|
use crate::StreamId;
|
||||||
|
|
||||||
|
@ -17,14 +21,18 @@ struct ListWorkersReply {
|
||||||
workers: Vec<u32>, // TODO: use proper JSON structure.
|
workers: Vec<u32>, // TODO: use proper JSON structure.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcessActor {
|
#[derive(Serialize)]
|
||||||
name: String,
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ProcessActorMsg {
|
||||||
|
actor: String,
|
||||||
|
id: u32,
|
||||||
|
is_parent: bool,
|
||||||
|
is_windowless_parent: bool,
|
||||||
|
traits: DescriptorTraits,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessActor {
|
pub struct ProcessActor {
|
||||||
pub fn new(name: String) -> Self {
|
name: String,
|
||||||
Self { name }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for ProcessActor {
|
impl Actor for ProcessActor {
|
||||||
|
@ -32,6 +40,9 @@ impl Actor for ProcessActor {
|
||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The process actor can handle the following messages:
|
||||||
|
///
|
||||||
|
/// - `listWorkers`: Returns a list of web workers, not supported yet.
|
||||||
fn handle_message(
|
fn handle_message(
|
||||||
&self,
|
&self,
|
||||||
_registry: &ActorRegistry,
|
_registry: &ActorRegistry,
|
||||||
|
@ -54,3 +65,19 @@ impl Actor for ProcessActor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ProcessActor {
|
||||||
|
pub fn new(name: String) -> Self {
|
||||||
|
Self { name }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encodable(&self) -> ProcessActorMsg {
|
||||||
|
ProcessActorMsg {
|
||||||
|
actor: self.name(),
|
||||||
|
id: 0,
|
||||||
|
is_parent: true,
|
||||||
|
is_windowless_parent: false,
|
||||||
|
traits: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,18 +2,20 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Liberally derived from the [Firefox JS implementation]
|
||||||
|
//! (https://searchfox.org/mozilla-central/source/devtools/server/actors/root.js).
|
||||||
|
//! Connection point for all new remote devtools interactions, providing lists of know actors
|
||||||
|
//! that perform more specific actions (targets, addons, browser chrome, etc.)
|
||||||
|
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
/// Liberally derived from the [Firefox JS implementation]
|
|
||||||
/// (http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/root.js).
|
|
||||||
/// Connection point for all new remote devtools interactions, providing lists of know actors
|
|
||||||
/// that perform more specific actions (targets, addons, browser chrome, etc.)
|
|
||||||
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
||||||
use crate::actors::device::DeviceActor;
|
use crate::actors::device::DeviceActor;
|
||||||
use crate::actors::performance::PerformanceActor;
|
use crate::actors::performance::PerformanceActor;
|
||||||
|
use crate::actors::process::{ProcessActor, ProcessActorMsg};
|
||||||
use crate::actors::tab::{TabDescriptorActor, TabDescriptorActorMsg};
|
use crate::actors::tab::{TabDescriptorActor, TabDescriptorActorMsg};
|
||||||
use crate::actors::worker::{WorkerActor, WorkerMsg};
|
use crate::actors::worker::{WorkerActor, WorkerMsg};
|
||||||
use crate::protocol::{ActorDescription, JsonPacketStream};
|
use crate::protocol::{ActorDescription, JsonPacketStream};
|
||||||
|
@ -95,7 +97,7 @@ pub struct Types {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ListProcessesResponse {
|
struct ListProcessesResponse {
|
||||||
from: String,
|
from: String,
|
||||||
processes: Vec<ProcessForm>,
|
processes: Vec<ProcessActorMsg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize)]
|
#[derive(Default, Serialize)]
|
||||||
|
@ -105,21 +107,11 @@ pub struct DescriptorTraits {
|
||||||
pub(crate) supports_reload_descriptor: bool,
|
pub(crate) supports_reload_descriptor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ProcessForm {
|
|
||||||
actor: String,
|
|
||||||
id: u32,
|
|
||||||
is_parent: bool,
|
|
||||||
is_windowless_parent: bool,
|
|
||||||
traits: DescriptorTraits,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct GetProcessResponse {
|
struct GetProcessResponse {
|
||||||
from: String,
|
from: String,
|
||||||
process_descriptor: ProcessForm,
|
process_descriptor: ProcessActorMsg,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RootActor {
|
pub struct RootActor {
|
||||||
|
@ -155,30 +147,21 @@ impl Actor for RootActor {
|
||||||
},
|
},
|
||||||
|
|
||||||
"listProcesses" => {
|
"listProcesses" => {
|
||||||
|
let process = registry.find::<ProcessActor>(&self.process).encodable();
|
||||||
let reply = ListProcessesResponse {
|
let reply = ListProcessesResponse {
|
||||||
from: self.name(),
|
from: self.name(),
|
||||||
processes: vec![ProcessForm {
|
processes: vec![process],
|
||||||
actor: self.process.clone(),
|
|
||||||
id: 0,
|
|
||||||
is_parent: true,
|
|
||||||
is_windowless_parent: false,
|
|
||||||
traits: Default::default(),
|
|
||||||
}],
|
|
||||||
};
|
};
|
||||||
let _ = stream.write_json_packet(&reply);
|
let _ = stream.write_json_packet(&reply);
|
||||||
ActorMessageStatus::Processed
|
ActorMessageStatus::Processed
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Unexpected message getTarget for process (when inspecting)
|
||||||
"getProcess" => {
|
"getProcess" => {
|
||||||
|
let process = registry.find::<ProcessActor>(&self.process).encodable();
|
||||||
let reply = GetProcessResponse {
|
let reply = GetProcessResponse {
|
||||||
from: self.name(),
|
from: self.name(),
|
||||||
process_descriptor: ProcessForm {
|
process_descriptor: process,
|
||||||
actor: self.process.clone(),
|
|
||||||
id: 0,
|
|
||||||
is_parent: true,
|
|
||||||
is_windowless_parent: false,
|
|
||||||
traits: Default::default(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let _ = stream.write_json_packet(&reply);
|
let _ = stream.write_json_packet(&reply);
|
||||||
ActorMessageStatus::Processed
|
ActorMessageStatus::Processed
|
||||||
|
@ -196,7 +179,6 @@ impl Actor for RootActor {
|
||||||
ActorMessageStatus::Processed
|
ActorMessageStatus::Processed
|
||||||
},
|
},
|
||||||
|
|
||||||
// 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(),
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Liberally derived from the [Firefox JS implementation]
|
||||||
|
//! (https://searchfox.org/mozilla-central/source/devtools/server/actors/descriptors/tab.js)
|
||||||
|
//! Descriptor actor that represents a web view. It can link a tab to the corresponding watcher
|
||||||
|
//! actor to enable inspection.
|
||||||
|
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -10,17 +15,19 @@ 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::{DescriptorTraits, RootActor};
|
use crate::actors::root::{DescriptorTraits, RootActor};
|
||||||
|
use crate::actors::watcher::{WatcherActor, WatcherActorMsg};
|
||||||
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)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TabDescriptorActorMsg {
|
pub struct TabDescriptorActorMsg {
|
||||||
actor: String,
|
actor: String,
|
||||||
browser_id: u32,
|
browser_id: u32,
|
||||||
|
#[serde(rename = "browsingContextID")]
|
||||||
browsing_context_id: u32,
|
browsing_context_id: u32,
|
||||||
is_zombie_tab: bool,
|
is_zombie_tab: bool,
|
||||||
|
#[serde(rename = "outerWindowID")]
|
||||||
outer_window_id: u32,
|
outer_window_id: u32,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
title: String,
|
title: String,
|
||||||
|
@ -46,6 +53,13 @@ struct GetFaviconReply {
|
||||||
favicon: String,
|
favicon: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct GetWatcherReply {
|
||||||
|
from: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
watcher: WatcherActorMsg,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TabDescriptorActor {
|
pub struct TabDescriptorActor {
|
||||||
name: String,
|
name: String,
|
||||||
browsing_context_actor: String,
|
browsing_context_actor: String,
|
||||||
|
@ -56,6 +70,14 @@ impl Actor for TabDescriptorActor {
|
||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The tab actor can handle the following messages:
|
||||||
|
///
|
||||||
|
/// - `getTarget`: Returns the surrounding `BrowsingContextActor`.
|
||||||
|
///
|
||||||
|
/// - `getFavicon`: Should return the tab favicon, but it is not yet supported.
|
||||||
|
///
|
||||||
|
/// - `getWatcher`: Returns a `WatcherActor` linked to the tab's `BrowsingContext`. It is used
|
||||||
|
/// to describe the debugging capabilities of this tab.
|
||||||
fn handle_message(
|
fn handle_message(
|
||||||
&self,
|
&self,
|
||||||
registry: &ActorRegistry,
|
registry: &ActorRegistry,
|
||||||
|
@ -83,7 +105,15 @@ impl Actor for TabDescriptorActor {
|
||||||
});
|
});
|
||||||
ActorMessageStatus::Processed
|
ActorMessageStatus::Processed
|
||||||
},
|
},
|
||||||
// TODO: Unexpected message getWatcher when inspecting tab (create watcher actor)
|
"getWatcher" => {
|
||||||
|
let ctx_actor = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
|
||||||
|
let watcher = registry.find::<WatcherActor>(&ctx_actor.watcher);
|
||||||
|
let _ = stream.write_json_packet(&GetWatcherReply {
|
||||||
|
from: self.name(),
|
||||||
|
watcher: watcher.encodable(),
|
||||||
|
});
|
||||||
|
ActorMessageStatus::Processed
|
||||||
|
},
|
||||||
_ => ActorMessageStatus::Ignored,
|
_ => ActorMessageStatus::Ignored,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -105,21 +135,22 @@ impl TabDescriptorActor {
|
||||||
|
|
||||||
pub fn encodable(&self, registry: &ActorRegistry, selected: bool) -> 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 browser_id = ctx_actor.active_pipeline.get().index.0.get();
|
||||||
|
let browsing_context_id = ctx_actor.browsing_context_id.index.0.get();
|
||||||
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 {
|
||||||
actor: self.name(),
|
actor: self.name(),
|
||||||
browsing_context_id: ctx_actor.browsing_context_id.index.0.get(),
|
browsing_context_id,
|
||||||
browser_id: ctx_actor.active_pipeline.get().index.0.get(),
|
browser_id,
|
||||||
is_zombie_tab: false,
|
is_zombie_tab: false,
|
||||||
outer_window_id: ctx_actor.active_pipeline.get().index.0.get(),
|
outer_window_id: browser_id,
|
||||||
selected,
|
selected,
|
||||||
title,
|
title,
|
||||||
traits: DescriptorTraits {
|
traits: DescriptorTraits {
|
||||||
watcher: true,
|
watcher: true,
|
||||||
supports_reload_descriptor: false,
|
supports_reload_descriptor: true,
|
||||||
},
|
},
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
|
|
259
components/devtools/actors/watcher.rs
Normal file
259
components/devtools/actors/watcher.rs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! Liberally derived from the [Firefox JS implementation]
|
||||||
|
//! (https://searchfox.org/mozilla-central/source/devtools/server/actors/watcher.js).
|
||||||
|
//! The watcher is the main entry point when debugging an element. Right now only web views are supported.
|
||||||
|
//! It talks to the devtools remote and lists the capabilities of the inspected target, and it serves
|
||||||
|
//! as a bridge for messages between actors.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
||||||
|
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
|
||||||
|
use crate::actors::configuration::{
|
||||||
|
TargetConfigurationActor, TargetConfigurationActorMsg, ThreadConfigurationActor,
|
||||||
|
ThreadConfigurationActorMsg,
|
||||||
|
};
|
||||||
|
use crate::protocol::JsonPacketStream;
|
||||||
|
use crate::{EmptyReplyMsg, StreamId};
|
||||||
|
|
||||||
|
/// Describes the debugged context. It informs the server of which objects can be debugged.
|
||||||
|
/// <https://searchfox.org/mozilla-central/source/devtools/server/actors/watcher/session-context.js>
|
||||||
|
#[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,
|
||||||
|
// Right now we only support debugging web views (frames)
|
||||||
|
supported_targets: HashMap::from([
|
||||||
|
("frame", true),
|
||||||
|
("process", false),
|
||||||
|
("worker", false),
|
||||||
|
("service_worker", false),
|
||||||
|
("shared_worker", false),
|
||||||
|
]),
|
||||||
|
// At the moment we are blocking most resources to avoid errors
|
||||||
|
// Support for them will be enabled gradually once the corresponding actors start
|
||||||
|
// working propperly
|
||||||
|
supported_resources: HashMap::from([
|
||||||
|
("console-message", true),
|
||||||
|
("css-change", false),
|
||||||
|
("css-message", false),
|
||||||
|
("css-registered-properties", false),
|
||||||
|
("document-event", true),
|
||||||
|
("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 GetTargetConfigurationActorReply {
|
||||||
|
from: String,
|
||||||
|
configuration: TargetConfigurationActorMsg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct GetThreadConfigurationActorReply {
|
||||||
|
from: String,
|
||||||
|
configuration: ThreadConfigurationActorMsg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
session_context: SessionContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for WatcherActor {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The watcher actor can handle the following messages:
|
||||||
|
///
|
||||||
|
/// - `watchTargets`: Returns a list of objects to debug. Since we only support web views, it
|
||||||
|
/// returns the associated `BrowsingContextActor`. Every target sent creates a
|
||||||
|
/// `target-available-form` event.
|
||||||
|
///
|
||||||
|
/// - `watchResources`: Start watching certain resource types. This sends
|
||||||
|
/// `resource-available-form` events.
|
||||||
|
///
|
||||||
|
/// - `getTargetConfigurationActor`: Returns the configuration actor for a specific target, so
|
||||||
|
/// that the server can update its settings.
|
||||||
|
///
|
||||||
|
/// - `getThreadConfigurationActor`: The same but with the configuration actor for the thread
|
||||||
|
fn handle_message(
|
||||||
|
&self,
|
||||||
|
registry: &ActorRegistry,
|
||||||
|
msg_type: &str,
|
||||||
|
msg: &Map<String, Value>,
|
||||||
|
stream: &mut TcpStream,
|
||||||
|
_id: StreamId,
|
||||||
|
) -> Result<ActorMessageStatus, ()> {
|
||||||
|
Ok(match msg_type {
|
||||||
|
"watchTargets" => {
|
||||||
|
let target = registry
|
||||||
|
.find::<BrowsingContextActor>(&self.browsing_context_actor)
|
||||||
|
.encodable();
|
||||||
|
let _ = stream.write_json_packet(&WatchTargetsReply {
|
||||||
|
from: self.name(),
|
||||||
|
type_: "target-available-form".into(),
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
|
||||||
|
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
|
||||||
|
target.frame_update(stream);
|
||||||
|
|
||||||
|
// Messages that contain a `type` field are used to send event callbacks, but they
|
||||||
|
// don't count as a reply. Since every message needs to be responded, we send an
|
||||||
|
// extra empty packet to the devtools host to inform that we successfully received
|
||||||
|
// and processed the message so that it can continue
|
||||||
|
let _ = stream.write_json_packet(&EmptyReplyMsg { from: self.name() });
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
let target =
|
||||||
|
registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
|
||||||
|
|
||||||
|
match resource {
|
||||||
|
"document-event" => {
|
||||||
|
target.document_event(stream);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = stream.write_json_packet(&EmptyReplyMsg { from: self.name() });
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorMessageStatus::Processed
|
||||||
|
},
|
||||||
|
"getTargetConfigurationActor" => {
|
||||||
|
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
|
||||||
|
let target_configuration =
|
||||||
|
registry.find::<TargetConfigurationActor>(&target.target_configuration);
|
||||||
|
|
||||||
|
let _ = stream.write_json_packet(&GetTargetConfigurationActorReply {
|
||||||
|
from: self.name(),
|
||||||
|
configuration: target_configuration.encodable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
ActorMessageStatus::Processed
|
||||||
|
},
|
||||||
|
"getThreadConfigurationActor" => {
|
||||||
|
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
|
||||||
|
let thread_configuration =
|
||||||
|
registry.find::<ThreadConfigurationActor>(&target.thread_configuration);
|
||||||
|
|
||||||
|
let _ = stream.write_json_packet(&GetThreadConfigurationActorReply {
|
||||||
|
from: self.name(),
|
||||||
|
configuration: thread_configuration.encodable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
ActorMessageStatus::Processed
|
||||||
|
},
|
||||||
|
_ => ActorMessageStatus::Ignored,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WatcherActor {
|
||||||
|
pub fn new(
|
||||||
|
name: String,
|
||||||
|
browsing_context_actor: String,
|
||||||
|
session_context: SessionContext,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
browsing_context_actor,
|
||||||
|
session_context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
//! An actor-based remote devtools server implementation. Only tested with
|
//! An actor-based remote devtools server implementation. Only tested with
|
||||||
//! nightly Firefox versions at time of writing. Largely based on
|
//! nightly Firefox versions at time of writing. Largely based on
|
||||||
//! reverse-engineering of Firefox chrome devtool logs and reading of
|
//! reverse-engineering of Firefox chrome devtool logs and reading of
|
||||||
//! [code](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/).
|
//! [code](https://searchfox.org/mozilla-central/source/devtools/server).
|
||||||
|
|
||||||
#![crate_name = "devtools"]
|
#![crate_name = "devtools"]
|
||||||
#![crate_type = "rlib"]
|
#![crate_type = "rlib"]
|
||||||
|
@ -48,9 +48,10 @@ use crate::actors::worker::{WorkerActor, WorkerType};
|
||||||
use crate::protocol::JsonPacketStream;
|
use crate::protocol::JsonPacketStream;
|
||||||
|
|
||||||
mod actor;
|
mod actor;
|
||||||
/// Corresponds to <http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/>
|
/// <https://searchfox.org/mozilla-central/source/devtools/server/actors>
|
||||||
mod actors {
|
mod actors {
|
||||||
pub mod browsing_context;
|
pub mod browsing_context;
|
||||||
|
pub mod configuration;
|
||||||
pub mod console;
|
pub mod console;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod emulation;
|
pub mod emulation;
|
||||||
|
@ -68,6 +69,7 @@ mod actors {
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
|
pub mod watcher;
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
}
|
}
|
||||||
mod protocol;
|
mod protocol;
|
||||||
|
@ -113,6 +115,11 @@ struct ResponseStartUpdateMsg {
|
||||||
response: ResponseStartMsg,
|
response: ResponseStartMsg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct EmptyReplyMsg {
|
||||||
|
pub from: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Spin up a devtools server that listens for connections on the specified port.
|
/// Spin up a devtools server that listens for connections on the specified port.
|
||||||
pub fn start_server(port: u16, embedder: EmbedderProxy) -> Sender<DevtoolsControlMsg> {
|
pub fn start_server(port: u16, embedder: EmbedderProxy) -> Sender<DevtoolsControlMsg> {
|
||||||
let (sender, receiver) = unbounded();
|
let (sender, receiver) = unbounded();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
//! Low-level wire protocol implementation. Currently only supports
|
//! Low-level wire protocol implementation. Currently only supports
|
||||||
//! [JSON packets](https://wiki.mozilla.org/Remote_Debugging_Protocol_Stream_Transport#JSON_Packets).
|
//! [JSON packets](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#json-packets).
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
@ -63,7 +63,7 @@ impl JsonPacketStream for TcpStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_json_packet(&mut self) -> Result<Option<Value>, String> {
|
fn read_json_packet(&mut self) -> Result<Option<Value>, String> {
|
||||||
// https://wiki.mozilla.org/Remote_Debugging_Protocol_Stream_Transport
|
// https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#stream-transport
|
||||||
// In short, each JSON packet is [ascii length]:[JSON data of given length]
|
// In short, each JSON packet is [ascii length]:[JSON data of given length]
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
loop {
|
loop {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue