mirror of
https://github.com/servo/servo
synced 2026-05-11 01:22:19 +02:00
Replace the previously hardcoded "name prefix" strings for actors with [`type_name`](https://doc.rust-lang.org/std/any/fn.type_name.html). Note that we aren't using the prefix directly as an unique identifier, and `type_name` wouldn't be suitable for that. The suffix is an incremental counter shared across all actors, so that alone is enough to differentiate them. The purpose of the prefix is to visually inspect the logs and see what actors are involved. With this change the prefix will be slightly different: "InspectorActor11" vs "inspector11", but that shouldn't affect behaviour. The eventual goal is to remove `new_name` and force the use of `register/register_later` to create an actor. This is however a bit more complicated, see #41768. While I'd love to add `base_name` directly to the `Actor` trait, that would unfortunately mean that it wouldn't be [dyn compatible](https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility). There are workarounds for this like separating the trait in two and implementing it automatically, but I feel that would be too convoluted. Testing: Manual testing, this patch shouldn't change behaviour. Part of: #41768 Signed-off-by: eri <eri@igalia.com>
268 lines
9.3 KiB
Rust
268 lines
9.3 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/. */
|
|
|
|
use std::any::{Any, type_name};
|
|
use std::cell::{Cell, RefCell};
|
|
use std::collections::HashMap;
|
|
use std::mem;
|
|
use std::net::TcpStream;
|
|
|
|
use base::id::PipelineId;
|
|
use log::{debug, warn};
|
|
use serde::Serialize;
|
|
use serde_json::{Map, Value, json};
|
|
|
|
use crate::StreamId;
|
|
use crate::protocol::{ClientRequest, JsonPacketStream};
|
|
|
|
/// Error replies.
|
|
///
|
|
/// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#error-packets>
|
|
#[derive(Debug)]
|
|
pub enum ActorError {
|
|
MissingParameter,
|
|
BadParameterType,
|
|
UnrecognizedPacketType,
|
|
/// Custom errors, not defined in the protocol docs.
|
|
/// This includes send errors, and errors that prevent Servo from sending a reply.
|
|
Internal,
|
|
}
|
|
|
|
impl ActorError {
|
|
pub fn name(&self) -> &'static str {
|
|
match self {
|
|
ActorError::MissingParameter => "missingParameter",
|
|
ActorError::BadParameterType => "badParameterType",
|
|
ActorError::UnrecognizedPacketType => "unrecognizedPacketType",
|
|
// The devtools frontend always checks for specific protocol errors by catching a JS exception `e` whose
|
|
// message contains the error name, and checking `e.message.includes("someErrorName")`. As a result, the
|
|
// only error name we can safely use for custom errors is the empty string, because any other error name we
|
|
// use may be a substring of some upstream error name.
|
|
ActorError::Internal => "",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A common trait for all devtools actors that encompasses an immutable name
|
|
/// and the ability to process messages that are directed to particular actors.
|
|
/// TODO: ensure the name is immutable
|
|
pub(crate) trait Actor: Any + ActorAsAny + Send {
|
|
fn handle_message(
|
|
&self,
|
|
request: ClientRequest,
|
|
registry: &ActorRegistry,
|
|
msg_type: &str,
|
|
msg: &Map<String, Value>,
|
|
stream_id: StreamId,
|
|
) -> Result<(), ActorError> {
|
|
let _ = (request, registry, msg_type, msg, stream_id);
|
|
Err(ActorError::UnrecognizedPacketType)
|
|
}
|
|
fn name(&self) -> String;
|
|
fn cleanup(&self, _id: StreamId) {}
|
|
}
|
|
|
|
pub(crate) trait ActorAsAny {
|
|
fn actor_as_any(&self) -> &dyn Any;
|
|
fn actor_as_any_mut(&mut self) -> &mut dyn Any;
|
|
}
|
|
|
|
impl<T: Actor> ActorAsAny for T {
|
|
fn actor_as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
fn actor_as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
pub(crate) trait ActorEncode<T: Serialize>: Actor {
|
|
fn encode(&self, registry: &ActorRegistry) -> T;
|
|
}
|
|
|
|
/// A list of known, owned actors.
|
|
#[derive(Default)]
|
|
pub struct ActorRegistry {
|
|
actors: HashMap<String, Box<dyn Actor>>,
|
|
new_actors: RefCell<Vec<Box<dyn Actor>>>,
|
|
old_actors: RefCell<Vec<String>>,
|
|
script_actors: RefCell<HashMap<String, String>>,
|
|
|
|
/// Lookup table for SourceActor names associated with a given PipelineId.
|
|
source_actor_names: RefCell<HashMap<PipelineId, Vec<String>>>,
|
|
/// Lookup table for inline source content associated with a given PipelineId.
|
|
inline_source_content: RefCell<HashMap<PipelineId, String>>,
|
|
|
|
next: Cell<u32>,
|
|
}
|
|
|
|
impl ActorRegistry {
|
|
pub(crate) fn cleanup(&self, stream_id: StreamId) {
|
|
for actor in self.actors.values() {
|
|
actor.cleanup(stream_id);
|
|
}
|
|
}
|
|
|
|
pub fn register_script_actor(&self, script_id: String, actor: String) {
|
|
debug!("registering {} ({})", actor, script_id);
|
|
let mut script_actors = self.script_actors.borrow_mut();
|
|
script_actors.insert(script_id, actor);
|
|
}
|
|
|
|
pub fn script_to_actor(&self, script_id: String) -> String {
|
|
if script_id.is_empty() {
|
|
return "".to_owned();
|
|
}
|
|
self.script_actors.borrow().get(&script_id).unwrap().clone()
|
|
}
|
|
|
|
pub fn script_actor_registered(&self, script_id: String) -> bool {
|
|
self.script_actors.borrow().contains_key(&script_id)
|
|
}
|
|
|
|
pub fn actor_to_script(&self, actor: String) -> String {
|
|
for (key, value) in &*self.script_actors.borrow() {
|
|
if *value == actor {
|
|
return key.to_owned();
|
|
}
|
|
}
|
|
panic!("couldn't find actor named {}", actor)
|
|
}
|
|
|
|
/// Create a name prefix for each actor type.
|
|
/// While not needed for unique ids as each actor already has a different
|
|
/// suffix, it can be used to visually identify actors in the logs.
|
|
pub fn base_name<T: Actor>() -> &'static str {
|
|
let prefix = type_name::<T>();
|
|
prefix.split("::").last().unwrap_or(prefix)
|
|
}
|
|
|
|
/// Create a unique name based on a monotonically increasing suffix
|
|
/// TODO: Merge this with `register/register_later` and don't allow to
|
|
/// create new names without registering an actor.
|
|
pub fn new_name<T: Actor>(&self) -> String {
|
|
let suffix = self.next.get();
|
|
self.next.set(suffix + 1);
|
|
format!("{}{}", Self::base_name::<T>(), suffix)
|
|
}
|
|
|
|
/// Add an actor to the registry of known actors that can receive messages.
|
|
pub(crate) fn register<T: Actor>(&mut self, actor: T) {
|
|
self.actors.insert(actor.name(), Box::new(actor));
|
|
}
|
|
|
|
/// Add an actor to the registry that can receive messages.
|
|
/// It won't be available until after the next message is processed.
|
|
pub(crate) fn register_later<T: Actor>(&self, actor: T) {
|
|
let mut actors = self.new_actors.borrow_mut();
|
|
actors.push(Box::new(actor));
|
|
}
|
|
|
|
/// Find an actor by registered name
|
|
pub fn find<'a, T: Any>(&'a self, name: &str) -> &'a T {
|
|
let actor = self.actors.get(name).unwrap();
|
|
actor.actor_as_any().downcast_ref::<T>().unwrap()
|
|
}
|
|
|
|
/// Find an actor by registered name
|
|
pub fn find_mut<'a, T: Any>(&'a mut self, name: &str) -> &'a mut T {
|
|
let actor = self.actors.get_mut(name).unwrap();
|
|
actor.actor_as_any_mut().downcast_mut::<T>().unwrap()
|
|
}
|
|
|
|
/// Find an actor by registered name and return its serialization
|
|
pub fn encode<T: ActorEncode<S>, S: Serialize>(&self, name: &str) -> S {
|
|
self.find::<T>(name).encode(self)
|
|
}
|
|
|
|
/// Attempt to process a message as directed by its `to` property. If the actor is not found, does not support the
|
|
/// message, or failed to handle the message, send an error reply instead.
|
|
pub(crate) fn handle_message(
|
|
&mut self,
|
|
msg: &Map<String, Value>,
|
|
stream: &mut TcpStream,
|
|
stream_id: StreamId,
|
|
) -> Result<(), ()> {
|
|
let to = match msg.get("to") {
|
|
Some(to) => to.as_str().unwrap(),
|
|
None => {
|
|
log::warn!("Received unexpected message: {:?}", msg);
|
|
return Err(());
|
|
},
|
|
};
|
|
|
|
match self.actors.get(to) {
|
|
None => {
|
|
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#packets>
|
|
let msg = json!({ "from": to, "error": "noSuchActor" });
|
|
let _ = stream.write_json_packet(&msg);
|
|
},
|
|
Some(actor) => {
|
|
let msg_type = msg.get("type").unwrap().as_str().unwrap();
|
|
if let Err(error) = ClientRequest::handle(stream, to, |req| {
|
|
actor.handle_message(req, self, msg_type, msg, stream_id)
|
|
}) {
|
|
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#error-packets>
|
|
let error = json!({
|
|
"from": actor.name(), "error": error.name()
|
|
});
|
|
warn!("Sending devtools protocol error: error={error:?} request={msg:?}");
|
|
let _ = stream.write_json_packet(&error);
|
|
}
|
|
},
|
|
}
|
|
let new_actors = mem::take(&mut *self.new_actors.borrow_mut());
|
|
for actor in new_actors.into_iter() {
|
|
self.actors.insert(actor.name().to_owned(), actor);
|
|
}
|
|
|
|
let old_actors = mem::take(&mut *self.old_actors.borrow_mut());
|
|
for name in old_actors {
|
|
self.drop_actor(name);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn drop_actor(&mut self, name: String) {
|
|
self.actors.remove(&name);
|
|
}
|
|
|
|
pub fn drop_actor_later(&self, name: String) {
|
|
let mut actors = self.old_actors.borrow_mut();
|
|
actors.push(name);
|
|
}
|
|
|
|
pub fn register_source_actor(&self, pipeline_id: PipelineId, actor_name: &str) {
|
|
self.source_actor_names
|
|
.borrow_mut()
|
|
.entry(pipeline_id)
|
|
.or_default()
|
|
.push(actor_name.to_owned());
|
|
}
|
|
|
|
pub fn source_actor_names_for_pipeline(&mut self, pipeline_id: PipelineId) -> Vec<String> {
|
|
if let Some(source_actor_names) = self.source_actor_names.borrow_mut().get(&pipeline_id) {
|
|
return source_actor_names.clone();
|
|
}
|
|
|
|
vec![]
|
|
}
|
|
|
|
pub fn set_inline_source_content(&mut self, pipeline_id: PipelineId, content: String) {
|
|
assert!(
|
|
self.inline_source_content
|
|
.borrow_mut()
|
|
.insert(pipeline_id, content)
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
pub fn inline_source_content(&mut self, pipeline_id: PipelineId) -> Option<String> {
|
|
self.inline_source_content
|
|
.borrow()
|
|
.get(&pipeline_id)
|
|
.cloned()
|
|
}
|
|
}
|