mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
These PR adds `resource_available` as a common shared util - [x] ./mach build -d does not report any errors - [x] ./mach test-tidy does not report any errors - [x] These changes partially implement https://github.com/servo/servo/issues/36027 --------- Signed-off-by: atbrakhi <atbrakhi@igalia.com>
368 lines
14 KiB
Rust
368 lines
14 KiB
Rust
/* 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/. */
|
|
|
|
//! 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.
|
|
//!
|
|
//! Liberally derived from the [Firefox JS implementation].
|
|
//!
|
|
//! [Firefox JS implementation]: https://searchfox.org/mozilla-central/source/devtools/server/actors/descriptors/watcher.js
|
|
|
|
use std::collections::HashMap;
|
|
use std::net::TcpStream;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use log::warn;
|
|
use serde::Serialize;
|
|
use serde_json::{Map, Value};
|
|
|
|
use self::network_parent::{NetworkParentActor, NetworkParentActorMsg};
|
|
use super::thread::ThreadActor;
|
|
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
|
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
|
|
use crate::actors::watcher::target_configuration::{
|
|
TargetConfigurationActor, TargetConfigurationActorMsg,
|
|
};
|
|
use crate::actors::watcher::thread_configuration::{
|
|
ThreadConfigurationActor, ThreadConfigurationActorMsg,
|
|
};
|
|
use crate::protocol::JsonPacketStream;
|
|
use crate::resource::ResourceAvailable;
|
|
use crate::{EmptyReplyMsg, StreamId};
|
|
|
|
pub mod network_parent;
|
|
pub mod target_configuration;
|
|
pub mod thread_configuration;
|
|
|
|
/// 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", true),
|
|
("css-message", false),
|
|
("css-registered-properties", false),
|
|
("document-event", false),
|
|
("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", true),
|
|
("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 GetParentBrowsingContextIDReply {
|
|
from: String,
|
|
#[serde(rename = "browsingContextID")]
|
|
browsing_context_id: u32,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct GetNetworkParentActorReply {
|
|
from: String,
|
|
network: NetworkParentActorMsg,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct GetTargetConfigurationActorReply {
|
|
from: String,
|
|
configuration: TargetConfigurationActorMsg,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct GetThreadConfigurationActorReply {
|
|
from: String,
|
|
configuration: ThreadConfigurationActorMsg,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct GetBreakpointListActorReply {
|
|
from: String,
|
|
breakpoint_list: GetBreakpointListActorReplyInner,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct GetBreakpointListActorReplyInner {
|
|
actor: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct DocumentEvent {
|
|
#[serde(rename = "hasNativeConsoleAPI")]
|
|
has_native_console_api: Option<bool>,
|
|
name: String,
|
|
#[serde(rename = "newURI")]
|
|
new_uri: Option<String>,
|
|
time: u64,
|
|
title: Option<String>,
|
|
url: Option<String>,
|
|
}
|
|
|
|
#[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,
|
|
network_parent: String,
|
|
target_configuration: String,
|
|
thread_configuration: 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
|
|
/// `resources-available-array` events.
|
|
///
|
|
/// - `getNetworkParentActor`: Returns the network parent actor. It doesn't seem to do much at
|
|
/// the moment.
|
|
///
|
|
/// - `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, ()> {
|
|
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
|
|
Ok(match msg_type {
|
|
"watchTargets" => {
|
|
let msg = WatchTargetsReply {
|
|
from: self.name(),
|
|
type_: "target-available-form".into(),
|
|
target: target.encodable(),
|
|
};
|
|
let _ = stream.write_json_packet(&msg);
|
|
|
|
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 msg = EmptyReplyMsg { from: self.name() };
|
|
let _ = stream.write_json_packet(&msg);
|
|
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;
|
|
};
|
|
match resource {
|
|
"document-event" => {
|
|
// 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 &name in ["dom-loading", "dom-interactive", "dom-complete"].iter() {
|
|
let event = DocumentEvent {
|
|
has_native_console_api: Some(true),
|
|
name: name.into(),
|
|
new_uri: None,
|
|
time: SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_millis()
|
|
as u64,
|
|
title: Some(target.title.borrow().clone()),
|
|
url: Some(target.url.borrow().clone()),
|
|
};
|
|
target.resource_available(event, "document-event".into());
|
|
}
|
|
},
|
|
"source" => {
|
|
let thread_actor = registry.find::<ThreadActor>(&target.thread);
|
|
let sources = thread_actor.sources();
|
|
target.resources_available(sources.iter().collect(), "source".into());
|
|
},
|
|
"console-message" | "error-message" => {},
|
|
_ => warn!("resource {} not handled yet", resource),
|
|
}
|
|
|
|
let msg = EmptyReplyMsg { from: self.name() };
|
|
let _ = stream.write_json_packet(&msg);
|
|
}
|
|
ActorMessageStatus::Processed
|
|
},
|
|
"getParentBrowsingContextID" => {
|
|
let msg = GetParentBrowsingContextIDReply {
|
|
from: self.name(),
|
|
browsing_context_id: target.browsing_context_id.value(),
|
|
};
|
|
let _ = stream.write_json_packet(&msg);
|
|
ActorMessageStatus::Processed
|
|
},
|
|
"getNetworkParentActor" => {
|
|
let network_parent = registry.find::<NetworkParentActor>(&self.network_parent);
|
|
let msg = GetNetworkParentActorReply {
|
|
from: self.name(),
|
|
network: network_parent.encodable(),
|
|
};
|
|
let _ = stream.write_json_packet(&msg);
|
|
ActorMessageStatus::Processed
|
|
},
|
|
"getTargetConfigurationActor" => {
|
|
let target_configuration =
|
|
registry.find::<TargetConfigurationActor>(&self.target_configuration);
|
|
let msg = GetTargetConfigurationActorReply {
|
|
from: self.name(),
|
|
configuration: target_configuration.encodable(),
|
|
};
|
|
let _ = stream.write_json_packet(&msg);
|
|
ActorMessageStatus::Processed
|
|
},
|
|
"getThreadConfigurationActor" => {
|
|
let thread_configuration =
|
|
registry.find::<ThreadConfigurationActor>(&self.thread_configuration);
|
|
let msg = GetThreadConfigurationActorReply {
|
|
from: self.name(),
|
|
configuration: thread_configuration.encodable(),
|
|
};
|
|
let _ = stream.write_json_packet(&msg);
|
|
ActorMessageStatus::Processed
|
|
},
|
|
"getBreakpointListActor" => {
|
|
let _ = stream.write_json_packet(&GetBreakpointListActorReply {
|
|
from: self.name(),
|
|
breakpoint_list: GetBreakpointListActorReplyInner {
|
|
actor: registry.new_name("breakpoint-list"),
|
|
},
|
|
});
|
|
ActorMessageStatus::Processed
|
|
},
|
|
_ => ActorMessageStatus::Ignored,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl WatcherActor {
|
|
pub fn new(
|
|
actors: &mut ActorRegistry,
|
|
browsing_context_actor: String,
|
|
session_context: SessionContext,
|
|
) -> Self {
|
|
let network_parent = NetworkParentActor::new(actors.new_name("network-parent"));
|
|
let target_configuration =
|
|
TargetConfigurationActor::new(actors.new_name("target-configuration"));
|
|
let thread_configuration =
|
|
ThreadConfigurationActor::new(actors.new_name("thread-configuration"));
|
|
|
|
let watcher = Self {
|
|
name: actors.new_name("watcher"),
|
|
browsing_context_actor,
|
|
network_parent: network_parent.name(),
|
|
target_configuration: target_configuration.name(),
|
|
thread_configuration: thread_configuration.name(),
|
|
session_context,
|
|
};
|
|
|
|
actors.register(Box::new(network_parent));
|
|
actors.register(Box::new(target_configuration));
|
|
actors.register(Box::new(thread_configuration));
|
|
|
|
watcher
|
|
}
|
|
|
|
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(),
|
|
},
|
|
}
|
|
}
|
|
}
|