mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +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
|
||||
* 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.
|
||||
//! Supports dynamic attaching and detaching which control notifications of navigation, etc.
|
||||
|
||||
|
@ -18,6 +18,7 @@ use serde::Serialize;
|
|||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
||||
use crate::actors::configuration::{TargetConfigurationActor, ThreadConfigurationActor};
|
||||
use crate::actors::emulation::EmulationActor;
|
||||
use crate::actors::inspector::InspectorActor;
|
||||
use crate::actors::performance::PerformanceActor;
|
||||
|
@ -26,118 +27,126 @@ use crate::actors::stylesheets::StyleSheetsActor;
|
|||
use crate::actors::tab::TabDescriptorActor;
|
||||
use crate::actors::thread::ThreadActor;
|
||||
use crate::actors::timeline::TimelineActor;
|
||||
use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
|
||||
use crate::protocol::JsonPacketStream;
|
||||
use crate::StreamId;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BrowsingContextTraits {
|
||||
isBrowsingContext: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AttachedTraits {
|
||||
reconfigure: bool,
|
||||
frames: bool,
|
||||
logInPage: bool,
|
||||
canRewind: bool,
|
||||
watchpoints: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BrowsingContextAttachedReply {
|
||||
struct FrameUpdateReply {
|
||||
from: String,
|
||||
#[serde(rename = "type")]
|
||||
type_: String,
|
||||
threadActor: String,
|
||||
cacheDisabled: bool,
|
||||
javascriptEnabled: bool,
|
||||
traits: AttachedTraits,
|
||||
frames: Vec<FrameUpdateMsg>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BrowsingContextDetachedReply {
|
||||
from: String,
|
||||
#[serde(rename = "type")]
|
||||
type_: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ReconfigureReply {
|
||||
from: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ListFramesReply {
|
||||
from: String,
|
||||
frames: Vec<FrameMsg>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct FrameMsg {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FrameUpdateMsg {
|
||||
id: u32,
|
||||
is_top_level: bool,
|
||||
url: String,
|
||||
title: String,
|
||||
parentID: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ListWorkersReply {
|
||||
struct ResourceAvailableReply {
|
||||
from: String,
|
||||
workers: Vec<WorkerMsg>,
|
||||
#[serde(rename = "type")]
|
||||
type_: String,
|
||||
resources: Vec<ResourceAvailableMsg>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct WorkerMsg {
|
||||
id: u32,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
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)]
|
||||
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 {
|
||||
actor: String,
|
||||
title: String,
|
||||
url: String,
|
||||
outerWindowID: u32,
|
||||
browsingContextId: u32,
|
||||
consoleActor: String,
|
||||
/*emulationActor: String,
|
||||
inspectorActor: String,
|
||||
timelineActor: String,
|
||||
profilerActor: String,
|
||||
performanceActor: String,
|
||||
styleSheetsActor: String,*/
|
||||
#[serde(rename = "outerWindowID")]
|
||||
outer_window_id: u32,
|
||||
#[serde(rename = "browsingContextID")]
|
||||
browsing_context_id: u32,
|
||||
is_top_level_target: bool,
|
||||
console_actor: String,
|
||||
thread_actor: String,
|
||||
traits: BrowsingContextTraits,
|
||||
// Part of the official protocol, but not yet implemented.
|
||||
/*storageActor: String,
|
||||
memoryActor: String,
|
||||
framerateActor: String,
|
||||
reflowActor: String,
|
||||
cssPropertiesActor: String,
|
||||
animationsActor: String,
|
||||
webExtensionInspectedWindowActor: String,
|
||||
accessibilityActor: String,
|
||||
screenshotActor: String,
|
||||
changesActor: String,
|
||||
webSocketActor: String,
|
||||
manifestActor: String,*/
|
||||
// emulation_actor: String,
|
||||
// inspector_actor: String,
|
||||
// timeline_actor: String,
|
||||
// profiler_actor: String,
|
||||
// performance_actor: String,
|
||||
// style_sheets_actor: String,
|
||||
// storage_actor: String,
|
||||
// memory_actor: String,
|
||||
// framerate_actor: String,
|
||||
// reflow_actor: String,
|
||||
// css_properties_actor: 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 name: String,
|
||||
pub title: RefCell<String>,
|
||||
pub url: RefCell<String>,
|
||||
pub active_pipeline: Cell<PipelineId>,
|
||||
pub browsing_context_id: BrowsingContextId,
|
||||
pub console: String,
|
||||
pub _emulation: String,
|
||||
pub _inspector: String,
|
||||
pub _timeline: String,
|
||||
pub _profiler: 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 _timeline: 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 streams: RefCell<HashMap<StreamId, TcpStream>>,
|
||||
pub watcher: String,
|
||||
}
|
||||
|
||||
impl Actor for BrowsingContextActor {
|
||||
|
@ -149,89 +158,11 @@ impl Actor for BrowsingContextActor {
|
|||
&self,
|
||||
_registry: &ActorRegistry,
|
||||
msg_type: &str,
|
||||
msg: &Map<String, Value>,
|
||||
stream: &mut TcpStream,
|
||||
id: StreamId,
|
||||
_msg: &Map<String, Value>,
|
||||
_stream: &mut TcpStream,
|
||||
_id: StreamId,
|
||||
) -> Result<ActorMessageStatus, ()> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
@ -255,9 +186,10 @@ impl BrowsingContextActor {
|
|||
script_sender: IpcSender<DevtoolScriptControlMsg>,
|
||||
actors: &mut ActorRegistry,
|
||||
) -> BrowsingContextActor {
|
||||
let emulation = EmulationActor::new(actors.new_name("emulation"));
|
||||
|
||||
let name = actors.new_name("target");
|
||||
let DevtoolsPageInfo { title, url } = page_info;
|
||||
|
||||
let emulation = EmulationActor::new(actors.new_name("emulation"));
|
||||
|
||||
let inspector = InspectorActor {
|
||||
name: actors.new_name("inspector"),
|
||||
|
@ -268,48 +200,66 @@ impl BrowsingContextActor {
|
|||
browsing_context: name.clone(),
|
||||
};
|
||||
|
||||
let timeline =
|
||||
TimelineActor::new(actors.new_name("timeline"), pipeline, script_sender.clone());
|
||||
let performance = PerformanceActor::new(actors.new_name("performance"));
|
||||
|
||||
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
|
||||
// to an inconsistency in devtools. See Bug #1498893 in bugzilla
|
||||
let styleSheets = StyleSheetsActor::new(actors.new_name("stylesheets"));
|
||||
let thread = ThreadActor::new(actors.new_name("context"));
|
||||
|
||||
let DevtoolsPageInfo { title, url } = page_info;
|
||||
let style_sheets = StyleSheetsActor::new(actors.new_name("stylesheets"));
|
||||
|
||||
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 {
|
||||
name,
|
||||
script_chan: script_sender,
|
||||
title: RefCell::new(title),
|
||||
url: RefCell::new(url.into_string()),
|
||||
active_pipeline: Cell::new(pipeline),
|
||||
browsing_context_id: id,
|
||||
console,
|
||||
_emulation: emulation.name(),
|
||||
_inspector: inspector.name(),
|
||||
_timeline: timeline.name(),
|
||||
_profiler: profiler.name(),
|
||||
_performance: performance.name(),
|
||||
_styleSheets: styleSheets.name(),
|
||||
_tab: tabdesc.name(),
|
||||
thread: thread.name(),
|
||||
_profiler: profiler.name(),
|
||||
streams: RefCell::new(HashMap::new()),
|
||||
browsing_context_id: id,
|
||||
active_pipeline: Cell::new(pipeline),
|
||||
_style_sheets: style_sheets.name(),
|
||||
_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(inspector));
|
||||
actors.register(Box::new(timeline));
|
||||
actors.register(Box::new(profiler));
|
||||
actors.register(Box::new(performance));
|
||||
actors.register(Box::new(styleSheets));
|
||||
actors.register(Box::new(thread));
|
||||
actors.register(Box::new(profiler));
|
||||
actors.register(Box::new(style_sheets));
|
||||
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
|
||||
}
|
||||
|
@ -318,21 +268,23 @@ impl BrowsingContextActor {
|
|||
BrowsingContextActorMsg {
|
||||
actor: self.name(),
|
||||
traits: BrowsingContextTraits {
|
||||
isBrowsingContext: true,
|
||||
is_browsing_context: true,
|
||||
},
|
||||
title: self.title.borrow().clone(),
|
||||
url: self.url.borrow().clone(),
|
||||
//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
|
||||
outerWindowID: self.active_pipeline.get().index.0.get(),
|
||||
consoleActor: self.console.clone(),
|
||||
/*emulationActor: self.emulation.clone(),
|
||||
inspectorActor: self.inspector.clone(),
|
||||
timelineActor: self.timeline.clone(),
|
||||
profilerActor: self.profiler.clone(),
|
||||
performanceActor: self.performance.clone(),
|
||||
styleSheetsActor: self.styleSheets.clone(),*/
|
||||
outer_window_id: self.active_pipeline.get().index.0.get(),
|
||||
is_top_level_target: true,
|
||||
console_actor: self.console.clone(),
|
||||
thread_actor: self.thread.clone(),
|
||||
// emulation_actor: self.emulation.clone(),
|
||||
// inspector_actor: self.inspector.clone(),
|
||||
// performance_actor: self.performance.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(),
|
||||
url: url.as_str().to_owned(),
|
||||
title,
|
||||
nativeConsoleAPI: true,
|
||||
native_console_api: true,
|
||||
state: state.to_owned(),
|
||||
isFrameSwitching: false,
|
||||
is_frame_switching: false,
|
||||
};
|
||||
|
||||
for stream in self.streams.borrow_mut().values_mut() {
|
||||
let _ = stream.write_json_packet(&msg);
|
||||
}
|
||||
|
@ -371,16 +324,40 @@ impl BrowsingContextActor {
|
|||
}
|
||||
*self.title.borrow_mut() = title;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TabNavigated {
|
||||
from: String,
|
||||
#[serde(rename = "type")]
|
||||
type_: String,
|
||||
url: String,
|
||||
title: Option<String>,
|
||||
nativeConsoleAPI: bool,
|
||||
state: String,
|
||||
isFrameSwitching: bool,
|
||||
pub(crate) fn frame_update(&self, stream: &mut TcpStream) {
|
||||
let _ = stream.write_json_packet(&FrameUpdateReply {
|
||||
from: self.name(),
|
||||
type_: "frameUpdate".into(),
|
||||
frames: vec![FrameUpdateMsg {
|
||||
id: self.browsing_context_id.index.0.get(),
|
||||
is_top_level: true,
|
||||
title: self.title.borrow().clone(),
|
||||
url: self.url.borrow().clone(),
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
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()),
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue