Devtools: send error replies instead of ignoring messages (#37686)

Client messages, which are always requests, are dispatched to Actor
instances one at a time via Actor::handle_message. Each request must be
paired with exactly one reply from the same actor the request was sent
to, where a reply is a message with no type (if a message from the
server has a type, it’s a notification, not a reply).

Failing to reply to a request will almost always permanently break that
actor, because either the client gets stuck waiting for a reply, or the
client receives the reply for a subsequent request as if it was the
reply for the current request. If an actor fails to reply to a request,
we want the dispatcher (ActorRegistry::handle_message) to send an error
of type `unrecognizedPacketType`, to keep the conversation for that
actor in sync. Since replies come in all shapes and sizes, we want to
allow Actor types to send replies without having to return them to the
dispatcher.

This patch adds a wrapper type around a client stream that guarantees
request/reply invariants. It allows the dispatcher to check if a valid
reply was sent, and guarantees that if the actor tries to send a reply,
it’s actually a valid reply (see ClientRequest::is_valid_reply). It does
not currently guarantee anything about messages sent via the TcpStream
released via ClientRequest::try_clone_stream or the return value of
ClientRequest::reply. We also send `unrecognizedPacketType`,
`missingParameter`, `badParameterType`, and `noSuchActor` messages per
the
[protocol](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#error-packets)
[docs](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#packets).

Testing: automated tests all pass, and manual testing looks ok
Fixes: #37683 and at least six bugs, plus one with a different root
cause, plus three with zero impact

---------

Signed-off-by: atbrakhi <atbrakhi@igalia.com>
Signed-off-by: Delan Azabani <dazabani@igalia.com>
Co-authored-by: delan azabani <dazabani@igalia.com>
Co-authored-by: Simon Wülker <simon.wuelker@arcor.de>
Co-authored-by: the6p4c <me@doggirl.gay>
This commit is contained in:
atbrakhi 2025-07-07 14:40:44 +02:00 committed by GitHub
parent fcb2a4cd95
commit 71d97bd935
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 661 additions and 637 deletions

View file

@ -12,14 +12,37 @@ use std::sync::{Arc, Mutex};
use base::cross_process_instant::CrossProcessInstant;
use base::id::PipelineId;
use log::debug;
use serde_json::{Map, Value};
use serde_json::{Map, Value, json};
use crate::StreamId;
use crate::protocol::{ClientRequest, JsonPacketStream};
#[derive(PartialEq)]
pub enum ActorMessageStatus {
Processed,
Ignored,
/// 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
@ -28,12 +51,12 @@ pub enum ActorMessageStatus {
pub(crate) trait Actor: Any + ActorAsAny {
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
stream_id: StreamId,
) -> Result<ActorMessageStatus, ()>;
) -> Result<(), ActorError>;
fn name(&self) -> String;
fn cleanup(&self, _id: StreamId) {}
}
@ -169,8 +192,8 @@ impl ActorRegistry {
actor.actor_as_any_mut().downcast_mut::<T>().unwrap()
}
/// Attempt to process a message as directed by its `to` property. If the actor is not
/// found or does not indicate that it knew how to process the message, ignore the failure.
/// 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>,
@ -186,17 +209,20 @@ impl ActorRegistry {
};
match self.actors.get(to) {
None => log::warn!("message received for unknown actor \"{}\"", 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 actor.handle_message(self, msg_type, msg, stream, stream_id)? !=
ActorMessageStatus::Processed
{
log::warn!(
"unexpected message type \"{}\" found for actor \"{}\"",
msg_type,
to
);
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 _ = stream.write_json_packet(&json!({
"from": actor.name(), "error": error.name()
}));
}
},
}

View file

@ -5,8 +5,8 @@
use serde::Serialize;
use crate::EmptyReplyMsg;
use crate::actor::{Actor, ActorMessageStatus};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError};
use crate::protocol::ClientRequest;
#[derive(Serialize)]
pub struct BreakpointListActorMsg {
@ -24,27 +24,24 @@ impl Actor for BreakpointListActor {
fn handle_message(
&self,
request: ClientRequest,
_registry: &crate::actor::ActorRegistry,
msg_type: &str,
_msg: &serde_json::Map<String, serde_json::Value>,
stream: &mut std::net::TcpStream,
_stream_id: crate::StreamId,
) -> Result<crate::actor::ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"setBreakpoint" => {
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"setActiveEventBreakpoints" => {
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -20,7 +20,7 @@ use ipc_channel::ipc::{self, IpcSender};
use serde::Serialize;
use serde_json::{Map, Value};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::inspector::InspectorActor;
use crate::actors::inspector::accessibility::AccessibilityActor;
use crate::actors::inspector::css_properties::CssPropertiesActor;
@ -30,7 +30,7 @@ use crate::actors::tab::TabDescriptorActor;
use crate::actors::thread::ThreadActor;
use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId, IdMap};
use crate::protocol::JsonPacketStream;
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::resource::ResourceAvailable;
use crate::{EmptyReplyMsg, StreamId};
@ -166,29 +166,28 @@ impl Actor for BrowsingContextActor {
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"listFrames" => {
// TODO: Find out what needs to be listed here
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"listWorkers" => {
let _ = stream.write_json_packet(&ListWorkersReply {
request.reply_final(&ListWorkersReply {
from: self.name(),
// TODO: Find out what needs to be listed here
workers: vec![],
});
ActorMessageStatus::Processed
})?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
fn cleanup(&self, id: StreamId) {
@ -353,8 +352,8 @@ impl BrowsingContextActor {
*self.title.borrow_mut() = title;
}
pub(crate) fn frame_update(&self, stream: &mut TcpStream) {
let _ = stream.write_json_packet(&FrameUpdateReply {
pub(crate) fn frame_update(&self, request: &mut ClientRequest) {
let _ = request.write_json_packet(&FrameUpdateReply {
from: self.name(),
type_: "frameUpdate".into(),
frames: vec![FrameUpdateMsg {

View file

@ -25,11 +25,11 @@ use serde::Serialize;
use serde_json::{self, Map, Number, Value};
use uuid::Uuid;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::browsing_context::BrowsingContextActor;
use crate::actors::object::ObjectActor;
use crate::actors::worker::WorkerActor;
use crate::protocol::JsonPacketStream;
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::resource::{ResourceArrayType, ResourceAvailable};
use crate::{StreamId, UniqueId};
@ -304,18 +304,19 @@ impl Actor for ConsoleActor {
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"clearMessagesCache" => {
self.cached_events
.borrow_mut()
.remove(&self.current_unique_id(registry));
ActorMessageStatus::Processed
// FIXME: need to send a reply here!
return Err(ActorError::UnrecognizedPacketType);
},
"getCachedMessages" => {
@ -368,8 +369,7 @@ impl Actor for ConsoleActor {
from: self.name(),
messages,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"startListeners" => {
@ -384,8 +384,7 @@ impl Actor for ConsoleActor {
.collect(),
traits: StartedListenersTraits,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"stopListeners" => {
@ -401,8 +400,7 @@ impl Actor for ConsoleActor {
.map(|listener| listener.as_str().unwrap().to_owned())
.collect(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
//TODO: implement autocompletion like onAutocomplete in
@ -413,14 +411,12 @@ impl Actor for ConsoleActor {
matches: vec![],
match_prop: "".to_owned(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"evaluateJS" => {
let msg = self.evaluate_js(registry, msg);
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"evaluateJSAsync" => {
@ -431,14 +427,12 @@ impl Actor for ConsoleActor {
};
// Emit an eager reply so that the client starts listening
// for an async event with the resultID
if stream.write_json_packet(&early_reply).is_err() {
return Ok(ActorMessageStatus::Processed);
}
let stream = request.reply(&early_reply)?;
if msg.get("eager").and_then(|v| v.as_bool()).unwrap_or(false) {
// We don't support the side-effect free evaluation that eager evalaution
// really needs.
return Ok(ActorMessageStatus::Processed);
return Ok(());
}
let reply = self.evaluate_js(registry, msg).unwrap();
@ -454,8 +448,7 @@ impl Actor for ConsoleActor {
helper_result: reply.helper_result,
};
// Send the data from evaluateJS along with a resultID
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
stream.write_json_packet(&msg)?
},
"setPreferences" => {
@ -463,11 +456,11 @@ impl Actor for ConsoleActor {
from: self.name(),
updated: vec![],
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -2,14 +2,12 @@
* 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::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::{ActorDescription, JsonPacketStream, Method};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::{ActorDescription, ClientRequest, Method};
#[derive(Serialize)]
struct GetDescriptionReply {
@ -46,13 +44,13 @@ impl Actor for DeviceActor {
}
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"getDescription" => {
let msg = GetDescriptionReply {
from: self.name(),
@ -64,12 +62,12 @@ impl Actor for DeviceActor {
brand_name: "Servo".to_string(),
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -3,7 +3,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::mem;
use std::net::TcpStream;
use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg;
@ -11,8 +10,9 @@ use ipc_channel::ipc::IpcSender;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::timeline::HighResolutionStamp;
use crate::protocol::ClientRequest;
pub struct FramerateActor {
name: String,
@ -29,13 +29,13 @@ impl Actor for FramerateActor {
fn handle_message(
&self,
_request: ClientRequest,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(ActorMessageStatus::Ignored)
) -> Result<(), ActorError> {
Err(ActorError::UnrecognizedPacketType)
}
}

View file

@ -6,7 +6,6 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::net::TcpStream;
use devtools_traits::DevtoolScriptControlMsg;
use devtools_traits::DevtoolScriptControlMsg::GetRootNode;
@ -15,13 +14,13 @@ use serde::Serialize;
use serde_json::{self, Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::browsing_context::BrowsingContextActor;
use crate::actors::inspector::highlighter::{HighlighterActor, HighlighterMsg};
use crate::actors::inspector::node::NodeInfoToProtocol;
use crate::actors::inspector::page_style::{PageStyleActor, PageStyleMsg};
use crate::actors::inspector::walker::{WalkerActor, WalkerMsg};
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
pub mod accessibility;
pub mod css_properties;
@ -73,19 +72,19 @@ impl Actor for InspectorActor {
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
) -> Result<(), ActorError> {
let browsing_context = registry.find::<BrowsingContextActor>(&self.browsing_context);
let pipeline = browsing_context.active_pipeline_id.get();
Ok(match msg_type {
match msg_type {
"getWalker" => {
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetRootNode(pipeline, tx)).unwrap();
let root_info = rx.recv().unwrap().ok_or(())?;
let root_info = rx.recv().unwrap().ok_or(ActorError::Internal)?;
let name = self
.walker
@ -121,8 +120,7 @@ impl Actor for InspectorActor {
root,
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getPageStyle" => {
@ -149,8 +147,7 @@ impl Actor for InspectorActor {
]),
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"supportsHighlighters" => {
@ -158,8 +155,7 @@ impl Actor for InspectorActor {
from: self.name(),
value: true,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getHighlighterByType" => {
@ -180,11 +176,10 @@ impl Actor for InspectorActor {
actor: self.highlighter.borrow().clone().unwrap(),
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -5,14 +5,12 @@
//! The Accessibility actor is responsible for the Accessibility tab in the DevTools page. Right
//! now it is a placeholder for future functionality.
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
#[derive(Serialize)]
struct BootstrapState {
@ -75,20 +73,19 @@ impl Actor for AccessibilityActor {
/// inspector Walker actor)
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"bootstrap" => {
let msg = BootstrapReply {
from: self.name(),
state: BootstrapState { enabled: false },
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getSimulator" => {
// TODO: Create actual simulator
@ -97,8 +94,7 @@ impl Actor for AccessibilityActor {
from: self.name(),
simulator: ActorMsg { actor: simulator },
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getTraits" => {
let msg = GetTraitsReply {
@ -107,8 +103,7 @@ impl Actor for AccessibilityActor {
tabbing_order: true,
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getWalker" => {
// TODO: Create actual accessible walker
@ -117,11 +112,11 @@ impl Actor for AccessibilityActor {
from: self.name(),
walker: ActorMsg { actor: walker },
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -6,15 +6,14 @@
//! alternative names
use std::collections::HashMap;
use std::net::TcpStream;
use devtools_traits::CssDatabaseProperty;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
pub struct CssPropertiesActor {
name: String,
@ -38,23 +37,20 @@ impl Actor for CssPropertiesActor {
/// inspector can show the available options
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"getCSSDatabase" => {
let _ = stream.write_json_packet(&GetCssDatabaseReply {
from: self.name(),
properties: &self.properties,
});
ActorMessageStatus::Processed
},
_ => ActorMessageStatus::Ignored,
})
) -> Result<(), ActorError> {
match msg_type {
"getCSSDatabase" => request.reply_final(&GetCssDatabaseReply {
from: self.name(),
properties: &self.properties,
})?,
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -5,16 +5,14 @@
//! Handles highlighting selected DOM nodes in the inspector. At the moment it only replies and
//! changes nothing on Servo's side.
use std::net::TcpStream;
use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg;
use ipc_channel::ipc::IpcSender;
use serde::Serialize;
use serde_json::{self, Map, Value};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
@ -46,22 +44,20 @@ impl Actor for HighlighterActor {
/// - `hide`: Disables highlighting for the selected node
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"show" => {
let Some(node_actor) = msg.get("node") else {
// TODO: send missing parameter error
return Ok(ActorMessageStatus::Ignored);
return Err(ActorError::MissingParameter);
};
let Some(node_actor_name) = node_actor.as_str() else {
// TODO: send invalid parameter error
return Ok(ActorMessageStatus::Ignored);
return Err(ActorError::BadParameterType);
};
if node_actor_name.starts_with("inspector") {
@ -71,8 +67,7 @@ impl Actor for HighlighterActor {
from: self.name(),
value: false,
};
let _ = stream.write_json_packet(&msg);
return Ok(ActorMessageStatus::Processed);
return request.reply_final(&msg);
}
self.instruct_script_thread_to_highlight_node(
@ -83,20 +78,19 @@ impl Actor for HighlighterActor {
from: self.name(),
value: true,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"hide" => {
self.instruct_script_thread_to_highlight_node(None, registry);
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -5,14 +5,12 @@
//! The layout actor informs the DevTools client of the layout properties of the document, such as
//! grids or flexboxes. It acts as a placeholder for now.
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
#[derive(Serialize)]
pub struct LayoutInspectorActorMsg {
@ -47,21 +45,20 @@ impl Actor for LayoutInspectorActor {
/// - `getCurrentFlexbox`: Returns the active flexbox, non functional at the moment
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"getGrids" => {
let msg = GetGridsReply {
from: self.name(),
// TODO: Actually create a list of grids
grids: vec![],
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getCurrentFlexbox" => {
let msg = GetCurrentFlexboxReply {
@ -69,12 +66,14 @@ impl Actor for LayoutInspectorActor {
// TODO: Create and return the current flexbox object
flexbox: None,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
fn cleanup(&self, _id: StreamId) {}
}
impl LayoutInspectorActor {

View file

@ -7,7 +7,6 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::net::TcpStream;
use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement, ModifyAttribute};
@ -16,9 +15,9 @@ use ipc_channel::ipc::{self, IpcSender};
use serde::Serialize;
use serde_json::{self, Map, Value};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::inspector::walker::WalkerActor;
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
use crate::{EmptyReplyMsg, StreamId};
/// Text node type constant. This is defined again to avoid depending on `script`, where it is defined originally.
@ -113,15 +112,19 @@ impl Actor for NodeActor {
/// - `getUniqueSelector`: Returns the display name of this node
fn handle_message(
&self,
mut request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"modifyAttributes" => {
let mods = msg.get("modifications").ok_or(())?.as_array().ok_or(())?;
let mods = msg
.get("modifications")
.ok_or(ActorError::MissingParameter)?
.as_array()
.ok_or(ActorError::BadParameterType)?;
let modifications: Vec<_> = mods
.iter()
.filter_map(|json_mod| {
@ -130,7 +133,7 @@ impl Actor for NodeActor {
.collect();
let walker = registry.find::<WalkerActor>(&self.walker);
walker.new_mutations(stream, &self.name, &modifications);
walker.new_mutations(&mut request, &self.name, &modifications);
self.script_chan
.send(ModifyAttribute(
@ -138,11 +141,10 @@ impl Actor for NodeActor {
registry.actor_to_script(self.name()),
modifications,
))
.map_err(|_| ())?;
.map_err(|_| ActorError::Internal)?;
let reply = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
"getUniqueSelector" => {
@ -150,7 +152,10 @@ impl Actor for NodeActor {
self.script_chan
.send(GetDocumentElement(self.pipeline, tx))
.unwrap();
let doc_elem_info = rx.recv().map_err(|_| ())?.ok_or(())?;
let doc_elem_info = rx
.recv()
.map_err(|_| ActorError::Internal)?
.ok_or(ActorError::Internal)?;
let node = doc_elem_info.encode(
registry,
true,
@ -163,12 +168,12 @@ impl Actor for NodeActor {
from: self.name(),
value: node.display_name,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -8,7 +8,6 @@
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::iter::once;
use std::net::TcpStream;
use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg::{GetLayout, GetSelectors};
@ -18,11 +17,11 @@ use serde::Serialize;
use serde_json::{self, Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::inspector::node::NodeActor;
use crate::actors::inspector::style_rule::{AppliedRule, ComputedDeclaration, StyleRuleActor};
use crate::actors::inspector::walker::{WalkerActor, find_child};
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
#[derive(Serialize)]
struct GetAppliedReply {
@ -114,30 +113,34 @@ impl Actor for PageStyleActor {
/// - `isPositionEditable`: Informs whether you can change a style property in the inspector.
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"getApplied" => self.get_applied(msg, registry, stream)?,
"getComputed" => self.get_computed(msg, registry, stream)?,
"getLayout" => self.get_layout(msg, registry, stream)?,
"isPositionEditable" => self.is_position_editable(stream),
_ => ActorMessageStatus::Ignored,
})
) -> Result<(), ActorError> {
match msg_type {
"getApplied" => self.get_applied(request, msg, registry),
"getComputed" => self.get_computed(request, msg, registry),
"getLayout" => self.get_layout(request, msg, registry),
"isPositionEditable" => self.is_position_editable(request),
_ => Err(ActorError::UnrecognizedPacketType),
}
}
}
impl PageStyleActor {
fn get_applied(
&self,
request: ClientRequest,
msg: &Map<String, Value>,
registry: &ActorRegistry,
stream: &mut TcpStream,
) -> Result<ActorMessageStatus, ()> {
let target = msg.get("node").ok_or(())?.as_str().ok_or(())?;
) -> Result<(), ActorError> {
let target = msg
.get("node")
.ok_or(ActorError::MissingParameter)?
.as_str()
.ok_or(ActorError::BadParameterType)?;
let node = registry.find::<NodeActor>(target);
let walker = registry.find::<WalkerActor>(&node.walker);
let entries: Vec<_> = find_child(
@ -214,17 +217,20 @@ impl PageStyleActor {
entries,
from: self.name(),
};
let _ = stream.write_json_packet(&msg);
Ok(ActorMessageStatus::Processed)
request.reply_final(&msg)
}
fn get_computed(
&self,
request: ClientRequest,
msg: &Map<String, Value>,
registry: &ActorRegistry,
stream: &mut TcpStream,
) -> Result<ActorMessageStatus, ()> {
let target = msg.get("node").ok_or(())?.as_str().ok_or(())?;
) -> Result<(), ActorError> {
let target = msg
.get("node")
.ok_or(ActorError::MissingParameter)?
.as_str()
.ok_or(ActorError::BadParameterType)?;
let node_actor = registry.find::<NodeActor>(target);
let computed = (|| match node_actor
.style_rules
@ -249,18 +255,22 @@ impl PageStyleActor {
computed,
from: self.name(),
};
let _ = stream.write_json_packet(&msg);
Ok(ActorMessageStatus::Processed)
request.reply_final(&msg)
}
fn get_layout(
&self,
request: ClientRequest,
msg: &Map<String, Value>,
registry: &ActorRegistry,
stream: &mut TcpStream,
) -> Result<ActorMessageStatus, ()> {
let target = msg.get("node").ok_or(())?.as_str().ok_or(())?;
let (computed_node_sender, computed_node_receiver) = ipc::channel().map_err(|_| ())?;
) -> Result<(), ActorError> {
let target = msg
.get("node")
.ok_or(ActorError::MissingParameter)?
.as_str()
.ok_or(ActorError::BadParameterType)?;
let (computed_node_sender, computed_node_receiver) =
ipc::channel().map_err(|_| ActorError::Internal)?;
self.script_chan
.send(GetLayout(
self.pipeline,
@ -288,7 +298,10 @@ impl PageStyleActor {
padding_left,
width,
height,
} = computed_node_receiver.recv().map_err(|_| ())?.ok_or(())?;
} = computed_node_receiver
.recv()
.map_err(|_| ActorError::Internal)?
.ok_or(ActorError::Internal)?;
let msg_auto_margins = msg
.get("autoMargins")
.and_then(Value::as_bool)
@ -333,18 +346,16 @@ impl PageStyleActor {
width,
height,
};
let msg = serde_json::to_string(&msg).map_err(|_| ())?;
let msg = serde_json::from_str::<Value>(&msg).map_err(|_| ())?;
let _ = stream.write_json_packet(&msg);
Ok(ActorMessageStatus::Processed)
let msg = serde_json::to_string(&msg).map_err(|_| ActorError::Internal)?;
let msg = serde_json::from_str::<Value>(&msg).map_err(|_| ActorError::Internal)?;
request.reply_final(&msg)
}
fn is_position_editable(&self, stream: &mut TcpStream) -> ActorMessageStatus {
fn is_position_editable(&self, request: ClientRequest) -> Result<(), ActorError> {
let msg = IsPositionEditableReply {
from: self.name(),
value: false,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)
}
}

View file

@ -7,7 +7,6 @@
//! A group is either the html style attribute or one selector from one stylesheet.
use std::collections::HashMap;
use std::net::TcpStream;
use devtools_traits::DevtoolScriptControlMsg::{
GetAttributeStyle, GetComputedStyle, GetDocumentElement, GetStylesheetStyle, ModifyRule,
@ -17,10 +16,10 @@ use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::inspector::node::NodeActor;
use crate::actors::inspector::walker::WalkerActor;
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
const ELEMENT_STYLE_TYPE: u32 = 100;
@ -98,16 +97,20 @@ impl Actor for StyleRuleActor {
/// when returning the list of rules.
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"setRuleText" => {
// Parse the modifications sent from the client
let mods = msg.get("modifications").ok_or(())?.as_array().ok_or(())?;
let mods = msg
.get("modifications")
.ok_or(ActorError::MissingParameter)?
.as_array()
.ok_or(ActorError::BadParameterType)?;
let modifications: Vec<_> = mods
.iter()
.filter_map(|json_mod| {
@ -125,13 +128,13 @@ impl Actor for StyleRuleActor {
registry.actor_to_script(self.node.clone()),
modifications,
))
.map_err(|_| ())?;
.map_err(|_| ActorError::Internal)?;
let _ = stream.write_json_packet(&self.encodable(registry));
ActorMessageStatus::Processed
request.reply_final(&self.encodable(registry))?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -5,7 +5,6 @@
//! The walker actor is responsible for traversing the DOM tree in various ways to create new nodes
use std::cell::RefCell;
use std::net::TcpStream;
use base::id::PipelineId;
use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement};
@ -14,10 +13,10 @@ use ipc_channel::ipc::{self, IpcSender};
use serde::Serialize;
use serde_json::{self, Map, Value};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::inspector::layout::{LayoutInspectorActor, LayoutInspectorActorMsg};
use crate::actors::inspector::node::{NodeActorMsg, NodeInfoToProtocol};
use crate::protocol::JsonPacketStream;
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
@ -64,7 +63,7 @@ struct GetLayoutInspectorReply {
}
#[derive(Serialize)]
struct WatchRootNodeReply {
struct WatchRootNodeNotification {
#[serde(rename = "type")]
type_: String,
from: String,
@ -94,7 +93,7 @@ struct GetOffsetParentReply {
}
#[derive(Serialize)]
struct NewMutationsReply {
struct NewMutationsNotification {
from: String,
#[serde(rename = "type")]
type_: String,
@ -123,24 +122,31 @@ impl Actor for WalkerActor {
/// node and its ascendents
fn handle_message(
&self,
mut request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"children" => {
let target = msg.get("node").ok_or(())?.as_str().ok_or(())?;
let (tx, rx) = ipc::channel().map_err(|_| ())?;
let target = msg
.get("node")
.ok_or(ActorError::MissingParameter)?
.as_str()
.ok_or(ActorError::BadParameterType)?;
let (tx, rx) = ipc::channel().map_err(|_| ActorError::Internal)?;
self.script_chan
.send(GetChildren(
self.pipeline,
registry.actor_to_script(target.into()),
tx,
))
.map_err(|_| ())?;
let children = rx.recv().map_err(|_| ())?.ok_or(())?;
.map_err(|_| ActorError::Internal)?;
let children = rx
.recv()
.map_err(|_| ActorError::Internal)?
.ok_or(ActorError::Internal)?;
let msg = ChildrenReply {
has_first: true,
@ -159,20 +165,21 @@ impl Actor for WalkerActor {
.collect(),
from: self.name(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"clearPseudoClassLocks" => {
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"documentElement" => {
let (tx, rx) = ipc::channel().map_err(|_| ())?;
let (tx, rx) = ipc::channel().map_err(|_| ActorError::Internal)?;
self.script_chan
.send(GetDocumentElement(self.pipeline, tx))
.map_err(|_| ())?;
let doc_elem_info = rx.recv().map_err(|_| ())?.ok_or(())?;
.map_err(|_| ActorError::Internal)?;
let doc_elem_info = rx
.recv()
.map_err(|_| ActorError::Internal)?
.ok_or(ActorError::Internal)?;
let node = doc_elem_info.encode(
registry,
true,
@ -185,8 +192,7 @@ impl Actor for WalkerActor {
from: self.name(),
node,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getLayoutInspector" => {
// TODO: Create actual layout inspector actor
@ -198,8 +204,7 @@ impl Actor for WalkerActor {
from: self.name(),
actor,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getMutations" => {
let msg = GetMutationsReply {
@ -216,20 +221,26 @@ impl Actor for WalkerActor {
})
.collect(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getOffsetParent" => {
let msg = GetOffsetParentReply {
from: self.name(),
node: None,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"querySelector" => {
let selector = msg.get("selector").ok_or(())?.as_str().ok_or(())?;
let node = msg.get("node").ok_or(())?.as_str().ok_or(())?;
let selector = msg
.get("selector")
.ok_or(ActorError::MissingParameter)?
.as_str()
.ok_or(ActorError::BadParameterType)?;
let node = msg
.get("node")
.ok_or(ActorError::MissingParameter)?
.as_str()
.ok_or(ActorError::BadParameterType)?;
let mut hierarchy = find_child(
&self.script_chan,
self.pipeline,
@ -239,39 +250,38 @@ impl Actor for WalkerActor {
vec![],
|msg| msg.display_name == selector,
)
.map_err(|_| ())?;
.map_err(|_| ActorError::Internal)?;
hierarchy.reverse();
let node = hierarchy.pop().ok_or(())?;
let node = hierarchy.pop().ok_or(ActorError::Internal)?;
let msg = QuerySelectorReply {
from: self.name(),
node,
new_parents: hierarchy,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"watchRootNode" => {
let msg = WatchRootNodeReply {
let msg = WatchRootNodeNotification {
type_: "root-available".into(),
from: self.name(),
node: self.root_node.clone(),
};
let _ = stream.write_json_packet(&msg);
let _ = request.write_json_packet(&msg);
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}
impl WalkerActor {
pub(crate) fn new_mutations(
&self,
stream: &mut TcpStream,
request: &mut ClientRequest,
target: &str,
modifications: &[AttrModification],
) {
@ -279,7 +289,7 @@ impl WalkerActor {
let mut mutations = self.mutations.borrow_mut();
mutations.extend(modifications.iter().cloned().map(|m| (m, target.into())));
}
let _ = stream.write_json_packet(&NewMutationsReply {
let _ = request.write_json_packet(&NewMutationsNotification {
from: self.name(),
type_: "newMutations".into(),
});

View file

@ -2,13 +2,12 @@
* 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::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@ -36,13 +35,13 @@ impl Actor for MemoryActor {
fn handle_message(
&self,
_request: ClientRequest,
_registry: &ActorRegistry,
_msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(ActorMessageStatus::Ignored)
) -> Result<(), ActorError> {
Err(ActorError::UnrecognizedPacketType)
}
}

View file

@ -5,7 +5,6 @@
//! Liberally derived from the [Firefox JS implementation](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js).
//! Handles interaction with the remote web console on network events (HTTP requests, responses) in Servo.
use std::net::TcpStream;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use chrono::{Local, LocalResult, TimeZone};
@ -16,9 +15,9 @@ use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::network_handler::Cause;
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
pub struct NetworkEventActor {
pub name: String,
@ -202,13 +201,13 @@ impl Actor for NetworkEventActor {
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"getRequestHeaders" => {
let mut headers = Vec::new();
let mut raw_headers_string = "".to_owned();
@ -232,8 +231,7 @@ impl Actor for NetworkEventActor {
header_size: headers_size,
raw_headers: raw_headers_string,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getRequestCookies" => {
let mut cookies = Vec::new();
@ -248,8 +246,7 @@ impl Actor for NetworkEventActor {
from: self.name(),
cookies,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getRequestPostData" => {
let msg = GetRequestPostDataReply {
@ -257,8 +254,7 @@ impl Actor for NetworkEventActor {
post_data: self.request_body.clone(),
post_data_discarded: self.request_body.is_none(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getResponseHeaders" => {
if let Some(ref response_headers) = self.response_headers_raw {
@ -282,9 +278,11 @@ impl Actor for NetworkEventActor {
header_size: headers_size,
raw_headers: raw_headers_string,
};
let _ = stream.write_json_packet(&msg);
request.reply_final(&msg)?;
} else {
// FIXME: what happens when there are no response headers?
return Err(ActorError::Internal);
}
ActorMessageStatus::Processed
},
"getResponseCookies" => {
let mut cookies = Vec::new();
@ -300,8 +298,7 @@ impl Actor for NetworkEventActor {
from: self.name(),
cookies,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getResponseContent" => {
let msg = GetResponseContentReply {
@ -309,8 +306,7 @@ impl Actor for NetworkEventActor {
content: self.response_body.clone(),
content_discarded: self.response_body.is_none(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getEventTimings" => {
// TODO: This is a fake timings msg
@ -323,8 +319,7 @@ impl Actor for NetworkEventActor {
timings: timings_obj,
total_time: total,
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getSecurityInfo" => {
// TODO: Send the correct values for securityInfo.
@ -334,11 +329,11 @@ impl Actor for NetworkEventActor {
state: "insecure".to_owned(),
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -2,12 +2,11 @@
* 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::net::TcpStream;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
pub struct ObjectActor {
pub name: String,
@ -20,14 +19,14 @@ impl Actor for ObjectActor {
}
fn handle_message(
&self,
_request: ClientRequest,
_: &ActorRegistry,
_: &str,
_: &Map<String, Value>,
_: &mut TcpStream,
_: StreamId,
) -> Result<ActorMessageStatus, ()> {
) -> Result<(), ActorError> {
// TODO: Handle enumSymbols for console object inspection
Ok(ActorMessageStatus::Ignored)
Err(ActorError::UnrecognizedPacketType)
}
}

View file

@ -5,14 +5,12 @@
// TODO: Is this actor still relevant?
#![allow(dead_code)]
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::{ActorDescription, JsonPacketStream, Method};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::{ActorDescription, ClientRequest, Method};
pub struct PerformanceActor {
name: String,
@ -62,13 +60,13 @@ impl Actor for PerformanceActor {
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"connect" => {
let msg = ConnectReply {
from: self.name(),
@ -82,8 +80,7 @@ impl Actor for PerformanceActor {
},
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"canCurrentlyRecord" => {
let msg = CanCurrentlyRecordReply {
@ -93,11 +90,11 @@ impl Actor for PerformanceActor {
errors: vec![],
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -2,16 +2,13 @@
* 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::net::TcpStream;
use log::warn;
use serde::Serialize;
use serde_json::{Map, Value};
use servo_config::pref;
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
pub struct PreferenceActor {
name: String,
@ -30,43 +27,44 @@ impl Actor for PreferenceActor {
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
let Some(key) = msg.get("value").and_then(|v| v.as_str()) else {
warn!("PreferenceActor: handle_message: value is not a string");
return Ok(ActorMessageStatus::Ignored);
};
) -> Result<(), ActorError> {
let key = msg
.get("value")
.ok_or(ActorError::MissingParameter)?
.as_str()
.ok_or(ActorError::BadParameterType)?;
// TODO: Map more preferences onto their Servo values.
Ok(match key {
match key {
"dom.serviceWorkers.enabled" => {
self.write_bool(pref!(dom_serviceworker_enabled), stream)
self.write_bool(request, pref!(dom_serviceworker_enabled))
},
_ => self.handle_missing_preference(msg_type, stream),
})
_ => self.handle_missing_preference(request, msg_type),
}
}
}
impl PreferenceActor {
fn handle_missing_preference(
&self,
request: ClientRequest,
msg_type: &str,
stream: &mut TcpStream,
) -> ActorMessageStatus {
) -> Result<(), ActorError> {
match msg_type {
"getBoolPref" => self.write_bool(false, stream),
"getCharPref" => self.write_char("".into(), stream),
"getIntPref" => self.write_int(0, stream),
"getFloatPref" => self.write_float(0., stream),
_ => ActorMessageStatus::Ignored,
"getBoolPref" => self.write_bool(request, false),
"getCharPref" => self.write_char(request, "".into()),
"getIntPref" => self.write_int(request, 0),
"getFloatPref" => self.write_float(request, 0.),
_ => Err(ActorError::UnrecognizedPacketType),
}
}
fn write_bool(&self, pref_value: bool, stream: &mut TcpStream) -> ActorMessageStatus {
fn write_bool(&self, request: ClientRequest, pref_value: bool) -> Result<(), ActorError> {
#[derive(Serialize)]
struct BoolReply {
from: String,
@ -77,11 +75,10 @@ impl PreferenceActor {
from: self.name.clone(),
value: pref_value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)
}
fn write_char(&self, pref_value: String, stream: &mut TcpStream) -> ActorMessageStatus {
fn write_char(&self, request: ClientRequest, pref_value: String) -> Result<(), ActorError> {
#[derive(Serialize)]
struct CharReply {
from: String,
@ -92,11 +89,10 @@ impl PreferenceActor {
from: self.name.clone(),
value: pref_value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)
}
fn write_int(&self, pref_value: i64, stream: &mut TcpStream) -> ActorMessageStatus {
fn write_int(&self, request: ClientRequest, pref_value: i64) -> Result<(), ActorError> {
#[derive(Serialize)]
struct IntReply {
from: String,
@ -107,11 +103,10 @@ impl PreferenceActor {
from: self.name.clone(),
value: pref_value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)
}
fn write_float(&self, pref_value: f64, stream: &mut TcpStream) -> ActorMessageStatus {
fn write_float(&self, request: ClientRequest, pref_value: f64) -> Result<(), ActorError> {
#[derive(Serialize)]
struct FloatReply {
from: String,
@ -122,7 +117,6 @@ impl PreferenceActor {
from: self.name.clone(),
value: pref_value,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)
}
}

View file

@ -6,15 +6,13 @@
//!
//! [Firefox JS implementation]: https://searchfox.org/mozilla-central/source/devtools/server/actors/descriptors/process.js
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::root::DescriptorTraits;
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
#[derive(Serialize)]
struct ListWorkersReply {
@ -46,24 +44,24 @@ impl Actor for ProcessActor {
/// - `listWorkers`: Returns a list of web workers, not supported yet.
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"listWorkers" => {
let reply = ListWorkersReply {
from: self.name(),
workers: vec![],
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -4,12 +4,11 @@
//! This actor is used for protocol purposes, it forwards the reflow events to clients.
use std::net::TcpStream;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
use crate::{EmptyReplyMsg, StreamId};
pub struct ReflowActor {
name: String,
@ -25,20 +24,24 @@ impl Actor for ReflowActor {
/// - `start`: Does nothing yet. This doesn't need a reply like other messages.
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
_stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"start" => {
// TODO: Create an observer on "reflows" events
ActorMessageStatus::Processed
let msg = EmptyReplyMsg { from: self.name() };
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
fn cleanup(&self, _id: StreamId) {}
}
impl ReflowActor {

View file

@ -10,19 +10,18 @@
//! [Firefox JS implementation]: https://searchfox.org/mozilla-central/source/devtools/server/actors/root.js
use std::cell::RefCell;
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value, json};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::device::DeviceActor;
use crate::actors::performance::PerformanceActor;
use crate::actors::process::{ProcessActor, ProcessActorMsg};
use crate::actors::tab::{TabDescriptorActor, TabDescriptorActorMsg};
use crate::actors::worker::{WorkerActor, WorkerMsg};
use crate::protocol::{ActorDescription, JsonPacketStream};
use crate::protocol::{ActorDescription, ClientRequest};
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@ -116,13 +115,6 @@ struct GetProcessResponse {
process_descriptor: ProcessActorMsg,
}
#[derive(Serialize)]
struct ErrorResponse {
from: String,
error: String,
message: String,
}
pub struct RootActor {
pub tabs: Vec<String>,
pub workers: Vec<String>,
@ -140,27 +132,25 @@ impl Actor for RootActor {
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"connect" => {
let message = json!({
"from": "root",
});
let _ = stream.write_json_packet(&message);
ActorMessageStatus::Processed
request.reply_final(&message)?
},
"listAddons" => {
let actor = ListAddonsReply {
from: "root".to_owned(),
addons: vec![],
};
let _ = stream.write_json_packet(&actor);
ActorMessageStatus::Processed
request.reply_final(&actor)?
},
"listProcesses" => {
@ -169,8 +159,7 @@ impl Actor for RootActor {
from: self.name(),
processes: vec![process],
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
// TODO: Unexpected message getTarget for process (when inspecting)
@ -180,8 +169,7 @@ impl Actor for RootActor {
from: self.name(),
process_descriptor: process,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
"getRoot" => {
@ -192,8 +180,7 @@ impl Actor for RootActor {
device_actor: self.device.clone(),
preference_actor: self.preference.clone(),
};
let _ = stream.write_json_packet(&actor);
ActorMessageStatus::Processed
request.reply_final(&actor)?
},
"listTabs" => {
@ -213,8 +200,7 @@ impl Actor for RootActor {
})
.collect(),
};
let _ = stream.write_json_packet(&actor);
ActorMessageStatus::Processed
request.reply_final(&actor)?
},
"listServiceWorkerRegistrations" => {
@ -222,8 +208,7 @@ impl Actor for RootActor {
from: self.name(),
registrations: vec![],
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
"listWorkers" => {
@ -235,26 +220,24 @@ impl Actor for RootActor {
.map(|name| registry.find::<WorkerActor>(name).encodable())
.collect(),
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
"getTab" => {
let Some(serde_json::Value::Number(browser_id)) = msg.get("browserId") else {
return Ok(ActorMessageStatus::Ignored);
};
let browser_id = browser_id.as_u64().unwrap();
let browser_id = msg
.get("browserId")
.ok_or(ActorError::MissingParameter)?
.as_u64()
.ok_or(ActorError::BadParameterType)?;
let Some(tab) = self.get_tab_msg_by_browser_id(registry, browser_id as u32) else {
return Ok(ActorMessageStatus::Ignored);
return Err(ActorError::Internal);
};
let reply = GetTabReply {
from: self.name(),
tab,
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
"protocolDescription" => {
@ -265,24 +248,12 @@ impl Actor for RootActor {
device: DeviceActor::description(),
},
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => {
let reply = ErrorResponse {
from: self.name(),
error: "unrecognizedPacketType".to_owned(),
message: format!(
"Actor {} does not recognize the packet type '{}'",
self.name(),
msg_type,
),
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Ignored
},
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -4,7 +4,6 @@
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::net::TcpStream;
use base::id::PipelineId;
use serde::Serialize;
@ -12,8 +11,8 @@ use serde_json::{Map, Value};
use servo_url::ServoUrl;
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
/// A `sourceForm` as used in responses to thread `sources` requests.
///
@ -134,13 +133,13 @@ impl Actor for SourceActor {
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
// Client has requested contents of the source.
"source" => {
let reply = SourceContentReply {
@ -154,10 +153,10 @@ impl Actor for SourceActor {
.unwrap_or("<!-- not available; please reload! -->")
.to_owned(),
};
let _ = stream.write_json_packet(&reply);
ActorMessageStatus::Processed
request.reply_final(&reply)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -2,14 +2,12 @@
* 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::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@ -28,24 +26,24 @@ impl Actor for StyleSheetsActor {
}
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"getStyleSheets" => {
let msg = GetStyleSheetsReply {
from: self.name(),
style_sheets: vec![],
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -9,17 +9,15 @@
//!
//! [Firefox JS implementation]: https://searchfox.org/mozilla-central/source/devtools/server/actors/descriptors/tab.js
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
use crate::actors::root::{DescriptorTraits, RootActor};
use crate::actors::watcher::{WatcherActor, WatcherActorMsg};
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@ -89,42 +87,40 @@ impl Actor for TabDescriptorActor {
/// to describe the debugging capabilities of this tab.
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"getTarget" => {
let frame = registry
.find::<BrowsingContextActor>(&self.browsing_context_actor)
.encodable();
let _ = stream.write_json_packet(&GetTargetReply {
request.reply_final(&GetTargetReply {
from: self.name(),
frame,
});
ActorMessageStatus::Processed
})?
},
"getFavicon" => {
// TODO: Return a favicon when available
let _ = stream.write_json_packet(&GetFaviconReply {
request.reply_final(&GetFaviconReply {
from: self.name(),
favicon: String::new(),
});
ActorMessageStatus::Processed
})?
},
"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 {
request.reply_final(&GetWatcherReply {
from: self.name(),
watcher: watcher.encodable(),
});
ActorMessageStatus::Processed
})?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -2,14 +2,12 @@
* 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::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use super::source::{SourceManager, SourcesReply};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
@ -71,13 +69,13 @@ impl Actor for ThreadActor {
fn handle_message(
&self,
mut request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"attach" => {
let msg = ThreadAttached {
from: self.name(),
@ -92,9 +90,8 @@ impl Actor for ThreadActor {
type_: "attached".to_owned(),
},
};
let _ = stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&EmptyReplyMsg { from: self.name() });
ActorMessageStatus::Processed
request.write_json_packet(&msg)?;
request.reply_final(&EmptyReplyMsg { from: self.name() })?
},
"resume" => {
@ -102,9 +99,8 @@ impl Actor for ThreadActor {
from: self.name(),
type_: "resumed".to_owned(),
};
let _ = stream.write_json_packet(&msg);
let _ = stream.write_json_packet(&EmptyReplyMsg { from: self.name() });
ActorMessageStatus::Processed
request.write_json_packet(&msg)?;
request.reply_final(&EmptyReplyMsg { from: self.name() })?
},
"interrupt" => {
@ -112,14 +108,11 @@ impl Actor for ThreadActor {
from: self.name(),
type_: "interrupted".to_owned(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.write_json_packet(&msg)?;
request.reply_final(&EmptyReplyMsg { from: self.name() })?
},
"reconfigure" => {
let _ = stream.write_json_packet(&EmptyReplyMsg { from: self.name() });
ActorMessageStatus::Processed
},
"reconfigure" => request.reply_final(&EmptyReplyMsg { from: self.name() })?,
// Client has attached to the thread and wants to load script sources.
// <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#loading-script-sources>
@ -128,10 +121,10 @@ impl Actor for ThreadActor {
from: self.name(),
sources: self.source_manager.source_forms(registry),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -6,7 +6,6 @@
#![allow(dead_code)]
use std::cell::RefCell;
use std::error::Error;
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use std::thread;
@ -21,10 +20,10 @@ use serde::{Serialize, Serializer};
use serde_json::{Map, Value};
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::framerate::FramerateActor;
use crate::actors::memory::{MemoryActor, TimelineMemoryReply};
use crate::protocol::JsonPacketStream;
use crate::protocol::{ClientRequest, JsonPacketStream};
pub struct TimelineActor {
name: String,
@ -191,13 +190,13 @@ impl Actor for TimelineActor {
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"start" => {
**self.is_recording.lock().as_mut().unwrap() = true;
@ -211,7 +210,7 @@ impl Actor for TimelineActor {
.unwrap();
//TODO: support multiple connections by using root actor's streams instead.
*self.stream.borrow_mut() = stream.try_clone().ok();
*self.stream.borrow_mut() = request.try_clone_stream().ok();
// init memory actor
if let Some(with_memory) = msg.get("withMemory") {
@ -236,7 +235,7 @@ impl Actor for TimelineActor {
self.name(),
registry.shareable(),
registry.start_stamp(),
stream.try_clone().unwrap(),
request.try_clone_stream().unwrap(),
self.memory_actor.borrow().clone(),
self.framerate_actor.borrow().clone(),
);
@ -250,8 +249,7 @@ impl Actor for TimelineActor {
CrossProcessInstant::now(),
),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"stop" => {
@ -263,7 +261,6 @@ impl Actor for TimelineActor {
),
};
let _ = stream.write_json_packet(&msg);
self.script_sender
.send(DropTimelineMarkers(
self.pipeline_id,
@ -282,7 +279,7 @@ impl Actor for TimelineActor {
**self.is_recording.lock().as_mut().unwrap() = false;
self.stream.borrow_mut().take();
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"isRecording" => {
@ -291,12 +288,12 @@ impl Actor for TimelineActor {
value: *self.is_recording.lock().unwrap(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}
@ -330,7 +327,7 @@ impl Emitter {
}
}
fn send(&mut self, markers: Vec<TimelineMarkerReply>) -> Result<(), Box<dyn Error>> {
fn send(&mut self, markers: Vec<TimelineMarkerReply>) -> Result<(), ActorError> {
let end_time = CrossProcessInstant::now();
let reply = MarkersEmitterReply {
type_: "markers".to_owned(),

View file

@ -24,7 +24,7 @@ use self::network_parent::{NetworkParentActor, NetworkParentActorMsg};
use super::breakpoint::BreakpointListActor;
use super::thread::ThreadActor;
use super::worker::WorkerMsg;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
use crate::actors::root::RootActor;
use crate::actors::watcher::target_configuration::{
@ -33,7 +33,7 @@ use crate::actors::watcher::target_configuration::{
use crate::actors::watcher::thread_configuration::{
ThreadConfigurationActor, ThreadConfigurationActorMsg,
};
use crate::protocol::JsonPacketStream;
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::resource::{ResourceArrayType, ResourceAvailable};
use crate::{EmptyReplyMsg, IdMap, StreamId, WorkerActor};
@ -230,15 +230,15 @@ impl Actor for WatcherActor {
/// - `getThreadConfigurationActor`: The same but with the configuration actor for the thread
fn handle_message(
&self,
mut request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
) -> Result<(), ActorError> {
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
let root = registry.find::<RootActor>("root");
Ok(match msg_type {
match msg_type {
"watchTargets" => {
// As per logs we either get targetType as "frame" or "worker"
let target_type = msg
@ -252,9 +252,9 @@ impl Actor for WatcherActor {
type_: "target-available-form".into(),
target: TargetActorMsg::BrowsingContext(target.encodable()),
};
let _ = stream.write_json_packet(&msg);
let _ = request.write_json_packet(&msg);
target.frame_update(stream);
target.frame_update(&mut request);
} else if target_type == "worker" {
for worker_name in &root.workers {
let worker = registry.find::<WorkerActor>(worker_name);
@ -263,11 +263,10 @@ impl Actor for WatcherActor {
type_: "target-available-form".into(),
target: TargetActorMsg::Worker(worker.encodable()),
};
let _ = stream.write_json_packet(&worker_msg);
let _ = request.write_json_packet(&worker_msg);
}
} else {
warn!("Unexpected target_type: {}", target_type);
return Ok(ActorMessageStatus::Ignored);
}
// Messages that contain a `type` field are used to send event callbacks, but they
@ -275,15 +274,14 @@ impl Actor for WatcherActor {
// 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
request.reply_final(&msg)?
},
"watchResources" => {
let Some(resource_types) = msg.get("resourceTypes") else {
return Ok(ActorMessageStatus::Ignored);
return Err(ActorError::MissingParameter);
};
let Some(resource_types) = resource_types.as_array() else {
return Ok(ActorMessageStatus::Ignored);
return Err(ActorError::BadParameterType);
};
for resource in resource_types {
@ -311,7 +309,7 @@ impl Actor for WatcherActor {
event,
"document-event".into(),
ResourceArrayType::Available,
stream,
&mut request,
);
}
},
@ -321,7 +319,7 @@ impl Actor for WatcherActor {
thread_actor.source_manager.source_forms(registry),
"source".into(),
ResourceArrayType::Available,
stream,
&mut request,
);
for worker_name in &root.workers {
@ -332,7 +330,7 @@ impl Actor for WatcherActor {
thread.source_manager.source_forms(registry),
"source".into(),
ResourceArrayType::Available,
stream,
&mut request,
);
}
},
@ -340,19 +338,16 @@ impl Actor for WatcherActor {
"network-event" => {},
_ => warn!("resource {} not handled yet", resource),
}
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
}
ActorMessageStatus::Processed
let msg = EmptyReplyMsg { from: self.name() };
request.reply_final(&msg)?
},
"getParentBrowsingContextID" => {
let msg = GetParentBrowsingContextIDReply {
from: self.name(),
browsing_context_id: target.browsing_context_id.value(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getNetworkParentActor" => {
let network_parent = registry.find::<NetworkParentActor>(&self.network_parent);
@ -360,8 +355,7 @@ impl Actor for WatcherActor {
from: self.name(),
network: network_parent.encodable(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getTargetConfigurationActor" => {
let target_configuration =
@ -370,8 +364,7 @@ impl Actor for WatcherActor {
from: self.name(),
configuration: target_configuration.encodable(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getThreadConfigurationActor" => {
let thread_configuration =
@ -380,24 +373,23 @@ impl Actor for WatcherActor {
from: self.name(),
configuration: thread_configuration.encodable(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
"getBreakpointListActor" => {
let breakpoint_list_name = registry.new_name("breakpoint-list");
let breakpoint_list = BreakpointListActor::new(breakpoint_list_name.clone());
registry.register_later(Box::new(breakpoint_list));
let _ = stream.write_json_packet(&GetBreakpointListActorReply {
request.reply_final(&GetBreakpointListActorReply {
from: self.name(),
breakpoint_list: GetBreakpointListActorReplyInner {
actor: breakpoint_list_name,
},
});
ActorMessageStatus::Processed
})?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -2,13 +2,11 @@
* 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::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
@ -30,20 +28,20 @@ impl Actor for NetworkParentActor {
/// - `setSaveRequestAndResponseBodies`: Doesn't do anything yet
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"setSaveRequestAndResponseBodies" => {
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -6,17 +6,16 @@
//! This actor manages the configuration flags that the devtools host can apply to the targets.
use std::collections::HashMap;
use std::net::TcpStream;
use embedder_traits::Theme;
use log::warn;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::actors::browsing_context::BrowsingContextActor;
use crate::actors::tab::TabDescriptorActor;
use crate::protocol::JsonPacketStream;
use crate::protocol::ClientRequest;
use crate::{EmptyReplyMsg, RootActor, StreamId};
#[derive(Serialize)]
@ -48,22 +47,19 @@ impl Actor for TargetConfigurationActor {
/// - `updateConfiguration`: Receives new configuration flags from the devtools host.
fn handle_message(
&self,
request: ClientRequest,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"updateConfiguration" => {
let config = match msg.get("configuration").and_then(|v| v.as_object()) {
Some(config) => config,
None => {
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
return Ok(ActorMessageStatus::Processed);
},
};
let config = msg
.get("configuration")
.ok_or(ActorError::MissingParameter)?
.as_object()
.ok_or(ActorError::BadParameterType)?;
if let Some(scheme) = config.get("colorSchemeSimulation").and_then(|v| v.as_str()) {
let theme = match scheme {
"dark" => Theme::Dark,
@ -76,17 +72,19 @@ impl Actor for TargetConfigurationActor {
let browsing_context_name = tab_actor.browsing_context();
let browsing_context_actor =
registry.find::<BrowsingContextActor>(&browsing_context_name);
browsing_context_actor.simulate_color_scheme(theme)?;
browsing_context_actor
.simulate_color_scheme(theme)
.map_err(|_| ActorError::Internal)?;
} else {
warn!("No active tab for updateConfiguration");
}
}
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -6,13 +6,12 @@
//! This actor manages the configuration flags that the devtools host can apply to 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::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::ClientRequest;
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
@ -35,21 +34,21 @@ impl Actor for ThreadConfigurationActor {
/// - `updateConfiguration`: Receives new configuration flags from the devtools host.
fn handle_message(
&self,
request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"updateConfiguration" => {
// TODO: Actually update configuration
let msg = EmptyReplyMsg { from: self.name() };
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
request.reply_final(&msg)?
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
}

View file

@ -15,8 +15,8 @@ use serde_json::{Map, Value};
use servo_url::ServoUrl;
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::actor::{Actor, ActorError, ActorRegistry};
use crate::protocol::{ClientRequest, JsonPacketStream};
use crate::resource::ResourceAvailable;
#[derive(Clone, Copy)]
@ -68,30 +68,28 @@ impl Actor for WorkerActor {
}
fn handle_message(
&self,
mut request: ClientRequest,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &Map<String, Value>,
stream: &mut TcpStream,
stream_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
) -> Result<(), ActorError> {
match msg_type {
"attach" => {
let msg = AttachedReply {
from: self.name(),
type_: "attached".to_owned(),
url: self.url.as_str().to_owned(),
};
if stream.write_json_packet(&msg).is_err() {
return Ok(ActorMessageStatus::Processed);
}
// FIXME: we dont send an actual reply (message without type), which seems to be a bug?
request.write_json_packet(&msg)?;
self.streams
.borrow_mut()
.insert(stream_id, stream.try_clone().unwrap());
.insert(stream_id, request.try_clone_stream().unwrap());
// FIXME: fix messages to not require forging a pipeline for worker messages
self.script_chan
.send(WantsLiveNotifications(TEST_PIPELINE_ID, true))
.unwrap();
ActorMessageStatus::Processed
},
"connect" => {
@ -101,8 +99,8 @@ impl Actor for WorkerActor {
thread_actor: self.thread.clone(),
console_actor: self.console.clone(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
// FIXME: we dont send an actual reply (message without type), which seems to be a bug?
request.write_json_packet(&msg)?;
},
"detach" => {
@ -110,13 +108,14 @@ impl Actor for WorkerActor {
from: self.name(),
type_: "detached".to_string(),
};
let _ = stream.write_json_packet(&msg);
self.cleanup(stream_id);
ActorMessageStatus::Processed
// FIXME: we dont send an actual reply (message without type), which seems to be a bug?
request.write_json_packet(&msg)?;
},
_ => ActorMessageStatus::Ignored,
})
_ => return Err(ActorError::UnrecognizedPacketType),
};
Ok(())
}
fn cleanup(&self, stream_id: StreamId) {

View file

@ -28,7 +28,7 @@ use devtools_traits::{
};
use embedder_traits::{AllowOrDeny, EmbedderMsg, EmbedderProxy};
use ipc_channel::ipc::{self, IpcSender};
use log::trace;
use log::{trace, warn};
use resource::{ResourceArrayType, ResourceAvailable};
use serde::Serialize;
use servo_rand::RngCore;
@ -646,8 +646,8 @@ fn allow_devtools_client(stream: &mut TcpStream, embedder: &EmbedderProxy, token
fn handle_client(actors: Arc<Mutex<ActorRegistry>>, mut stream: TcpStream, stream_id: StreamId) {
log::info!("Connection established to {}", stream.peer_addr().unwrap());
let msg = actors.lock().unwrap().find::<RootActor>("root").encodable();
if let Err(e) = stream.write_json_packet(&msg) {
log::warn!("Error writing response: {:?}", e);
if let Err(error) = stream.write_json_packet(&msg) {
warn!("Failed to send initial packet from root actor: {error:?}");
return;
}

View file

@ -5,13 +5,14 @@
//! Low-level wire protocol implementation. Currently only supports
//! [JSON packets](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#json-packets).
use std::error::Error;
use std::io::{Read, Write};
use std::net::TcpStream;
use log::debug;
use serde::Serialize;
use serde_json::{self, Value};
use serde_json::{self, Value, json};
use crate::actor::ActorError;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@ -29,42 +30,18 @@ pub struct Method {
}
pub trait JsonPacketStream {
fn write_json_packet<T: Serialize>(&mut self, obj: &T) -> Result<(), Box<dyn Error>>;
#[allow(dead_code)]
fn write_merged_json_packet<T: Serialize, U: Serialize>(
&mut self,
base: &T,
extra: &U,
) -> Result<(), Box<dyn Error>>;
fn write_json_packet<T: Serialize>(&mut self, message: &T) -> Result<(), ActorError>;
fn read_json_packet(&mut self) -> Result<Option<Value>, String>;
}
impl JsonPacketStream for TcpStream {
fn write_json_packet<T: Serialize>(&mut self, obj: &T) -> Result<(), Box<dyn Error>> {
let s = serde_json::to_string(obj)?;
fn write_json_packet<T: Serialize>(&mut self, message: &T) -> Result<(), ActorError> {
let s = serde_json::to_string(message).map_err(|_| ActorError::Internal)?;
debug!("<- {}", s);
write!(self, "{}:{}", s.len(), s)?;
write!(self, "{}:{}", s.len(), s).map_err(|_| ActorError::Internal)?;
Ok(())
}
fn write_merged_json_packet<T: Serialize, U: Serialize>(
&mut self,
base: &T,
extra: &U,
) -> Result<(), Box<dyn Error>> {
let mut obj = serde_json::to_value(base)?;
let obj = obj.as_object_mut().unwrap();
let extra = serde_json::to_value(extra)?;
let extra = extra.as_object().unwrap();
for (key, value) in extra {
obj.insert(key.to_owned(), value.to_owned());
}
self.write_json_packet(obj)
}
fn read_json_packet(&mut self) -> Result<Option<Value>, String> {
// 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]
@ -102,3 +79,106 @@ impl JsonPacketStream for TcpStream {
}
}
}
/// Wrapper around a client stream that guarantees request/reply invariants.
///
/// Client messages, which are always requests, are dispatched to Actor instances one at a time via
/// [`crate::Actor::handle_message`]. Each request must be paired with exactly one reply from the
/// same actor the request was sent to, where a reply is a message with no type (if a message from
/// the server has a type, its a notification, not a reply).
///
/// Failing to reply to a request will almost always permanently break that actor, because either
/// the client gets stuck waiting for a reply, or the client receives the reply for a subsequent
/// request as if it was the reply for the current request. If an actor fails to reply to a request,
/// we want the dispatcher ([`crate::ActorRegistry::handle_message`]) to send an error of type
/// `unrecognizedPacketType`, to keep the conversation for that actor in sync.
///
/// Since replies come in all shapes and sizes, we want to allow Actor types to send replies without
/// having to return them to the dispatcher. This wrapper type allows the dispatcher to check if a
/// valid reply was sent, and guarantees that if the actor tries to send a reply, its actually a
/// valid reply (see [`Self::is_valid_reply`]).
///
/// It does not currently guarantee anything about messages sent via the [`TcpStream`] released via
/// [`Self::try_clone_stream`] or the return value of [`Self::reply`].
pub struct ClientRequest<'req, 'sent> {
/// Client stream.
stream: &'req mut TcpStream,
/// Expected actor name.
actor_name: &'req str,
/// Sent flag, allowing ActorRegistry to check for unhandled requests.
sent: &'sent mut bool,
}
impl ClientRequest<'_, '_> {
/// Run the given handler, with a new request that wraps the given client stream and expected actor name.
///
/// Returns [`ActorError::UnrecognizedPacketType`] if the actor did not send a reply.
pub fn handle<'req>(
client: &'req mut TcpStream,
actor_name: &'req str,
handler: impl FnOnce(ClientRequest<'req, '_>) -> Result<(), ActorError>,
) -> Result<(), ActorError> {
let mut sent = false;
let request = ClientRequest {
stream: client,
actor_name,
sent: &mut sent,
};
handler(request)?;
if sent {
Ok(())
} else {
Err(ActorError::UnrecognizedPacketType)
}
}
}
impl<'req> ClientRequest<'req, '_> {
/// Send the given reply to the request being handled.
///
/// If successful, sets the sent flag and returns the underlying stream,
/// allowing other messages to be sent after replying to a request.
pub fn reply<T: Serialize>(self, reply: &T) -> Result<&'req mut TcpStream, ActorError> {
debug_assert!(self.is_valid_reply(reply), "Message is not a valid reply");
self.stream.write_json_packet(reply)?;
*self.sent = true;
Ok(self.stream)
}
/// Like `reply`, but for cases where the actor no longer needs the stream.
pub fn reply_final<T: Serialize>(self, reply: &T) -> Result<(), ActorError> {
debug_assert!(self.is_valid_reply(reply), "Message is not a valid reply");
let _stream = self.reply(reply)?;
Ok(())
}
pub fn try_clone_stream(&self) -> std::io::Result<TcpStream> {
self.stream.try_clone()
}
/// Return true iff the given message is a reply (has no `type` or `to`), and is from the expected actor.
///
/// This incurs a runtime conversion to a BTreeMap, so it should only be used in debug assertions.
fn is_valid_reply<T: Serialize>(&self, message: &T) -> bool {
let reply = json!(message);
reply.get("from").and_then(|from| from.as_str()) == Some(self.actor_name) &&
reply.get("to").is_none() &&
reply.get("type").is_none()
}
}
/// Actors can also send other messages before replying to a request.
impl JsonPacketStream for ClientRequest<'_, '_> {
fn write_json_packet<T: Serialize>(&mut self, message: &T) -> Result<(), ActorError> {
debug_assert!(
!self.is_valid_reply(message),
"Replies must use reply() or reply_final()"
);
self.stream.write_json_packet(message)
}
fn read_json_packet(&mut self) -> Result<Option<Value>, String> {
self.stream.read_json_packet()
}
}

View file

@ -2,8 +2,6 @@
* 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::net::TcpStream;
use serde::Serialize;
use crate::protocol::JsonPacketStream;
@ -24,22 +22,22 @@ pub(crate) struct ResourceAvailableReply<T: Serialize> {
pub(crate) trait ResourceAvailable {
fn actor_name(&self) -> String;
fn resource_array<T: Serialize>(
fn resource_array<T: Serialize, S: JsonPacketStream>(
&self,
resource: T,
resource_type: String,
array_type: ResourceArrayType,
stream: &mut TcpStream,
stream: &mut S,
) {
self.resources_array(vec![resource], resource_type, array_type, stream);
}
fn resources_array<T: Serialize>(
fn resources_array<T: Serialize, S: JsonPacketStream>(
&self,
resources: Vec<T>,
resource_type: String,
array_type: ResourceArrayType,
stream: &mut TcpStream,
stream: &mut S,
) {
let msg = ResourceAvailableReply::<T> {
from: self.actor_name(),