Implement wire protocol support for DOM inspector.

This commit is contained in:
Josh Matthews 2014-09-03 01:41:06 -04:00
parent c31e2f928d
commit e9c4aa534d
5 changed files with 554 additions and 18 deletions

View file

@ -6,6 +6,7 @@
use std::any::{Any, AnyRefExt, AnyMutRefExt};
use std::collections::hashmap::HashMap;
use std::cell::{Cell, RefCell};
use std::io::TcpStream;
use std::mem::{transmute, transmute_copy};
use std::raw::TraitObject;
@ -69,6 +70,8 @@ impl<'a> AnyRefExt<'a> for &'a Actor {
/// A list of known, owned actors.
pub struct ActorRegistry {
actors: HashMap<String, Box<Actor+Send+Sized>>,
new_actors: RefCell<Vec<Box<Actor+Send+Sized>>>,
next: Cell<u32>,
}
impl ActorRegistry {
@ -76,14 +79,28 @@ impl ActorRegistry {
pub fn new() -> ActorRegistry {
ActorRegistry {
actors: HashMap::new(),
new_actors: RefCell::new(vec!()),
next: Cell::new(0),
}
}
/// Create a unique name based on a monotonically increasing suffix
pub fn new_name(&self, prefix: &str) -> String {
let suffix = self.next.get();
self.next.set(suffix + 1);
format!("{:s}{:u}", prefix, suffix)
}
/// Add an actor to the registry of known actors that can receive messages.
pub fn register(&mut self, actor: Box<Actor+Send+Sized>) {
self.actors.insert(actor.name().to_string(), actor);
}
pub fn register_later(&self, actor: Box<Actor+Send+Sized>) {
let mut actors = self.new_actors.borrow_mut();
actors.push(actor);
}
/// Find an actor by registered name
pub fn find<'a, T: 'static>(&'a self, name: &str) -> &'a T {
//FIXME: Rust bug forces us to implement bogus Any for Actor since downcast_ref currently
@ -104,7 +121,7 @@ impl ActorRegistry {
/// 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.
pub fn handle_message(&self, msg: &json::Object, stream: &mut TcpStream) {
pub fn handle_message(&mut self, msg: &json::Object, stream: &mut TcpStream) {
let to = msg.find(&"to".to_string()).unwrap().as_string().unwrap();
match self.actors.find(&to.to_string()) {
None => println!("message received for unknown actor \"{:s}\"", to),
@ -116,5 +133,10 @@ impl ActorRegistry {
}
}
}
let mut new_actors = self.new_actors.borrow_mut();
for &actor in new_actors.iter() {
self.actors.insert(actor.name().to_string(), actor);
}
new_actors.clear();
}
}

View file

@ -0,0 +1,472 @@
/* 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 http://mozilla.org/MPL/2.0/. */
/// Liberally derived from the [Firefox JS implementation](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/inspector.js).
use actor::{Actor, ActorRegistry};
use protocol::JsonPacketSender;
use serialize::json;
use std::cell::RefCell;
use std::io::TcpStream;
pub struct InspectorActor {
pub name: String,
pub walker: RefCell<Option<String>>,
pub pageStyle: RefCell<Option<String>>,
pub highlighter: RefCell<Option<String>>,
}
#[deriving(Encodable)]
struct GetHighlighterReply {
highligter: HighlighterMsg, // sic.
from: String,
}
#[deriving(Encodable)]
struct HighlighterMsg {
actor: String,
}
struct HighlighterActor {
name: String,
}
#[deriving(Encodable)]
struct ShowBoxModelReply {
from: String,
}
#[deriving(Encodable)]
struct HideBoxModelReply {
from: String,
}
impl Actor for HighlighterActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &String,
_msg: &json::Object,
stream: &mut TcpStream) -> bool {
match msg_type.as_slice() {
"showBoxModel" => {
let msg = ShowBoxModelReply {
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
"hideBoxModel" => {
let msg = HideBoxModelReply {
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
_ => false,
}
}
}
#[deriving(Encodable)]
struct GetWalkerReply {
from: String,
walker: WalkerMsg,
}
#[deriving(Encodable)]
struct WalkerMsg {
actor: String,
root: NodeActorMsg,
}
#[deriving(Encodable)]
struct AttrMsg {
namespace: String,
name: String,
value: String,
}
#[deriving(Encodable)]
struct NodeActorMsg {
actor: String,
baseURI: String,
parent: String,
nodeType: uint,
namespaceURI: String,
nodeName: String,
numChildren: uint,
name: String,
publicId: String,
systemId: String,
attrs: Vec<AttrMsg>,
pseudoClassLocks: Vec<String>,
isDisplayed: bool,
hasEventListeners: bool,
isDocumentElement: bool,
shortValue: String,
incompleteValue: bool,
}
struct WalkerActor {
name: String,
}
#[deriving(Encodable)]
struct QuerySelectorReply {
from: String,
}
#[deriving(Encodable)]
struct DocumentElementReply {
from: String,
node: NodeActorMsg,
}
#[deriving(Encodable)]
struct ClearPseudoclassesReply {
from: String,
}
#[deriving(Encodable)]
struct ChildrenReply {
hasFirst: bool,
hasLast: bool,
nodes: Vec<NodeActorMsg>,
from: String,
}
impl Actor for WalkerActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &String,
_msg: &json::Object,
stream: &mut TcpStream) -> bool {
match msg_type.as_slice() {
"querySelector" => {
let msg = QuerySelectorReply {
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
"documentElement" => {
let msg = DocumentElementReply {
from: self.name(),
node: NodeActorMsg {
actor: "node0".to_string(),
baseURI: "".to_string(),
parent: "".to_string(),
nodeType: 1, //ELEMENT_NODE
namespaceURI: "".to_string(),
nodeName: "html".to_string(),
numChildren: 0,
name: "".to_string(),
publicId: "".to_string(),
systemId: "".to_string(),
attrs: vec!(AttrMsg {
namespace: "".to_string(),
name: "manifest".to_string(),
value: "foo.manifest".to_string(),
}),
pseudoClassLocks: vec!(),
isDisplayed: true,
hasEventListeners: false,
isDocumentElement: true,
shortValue: "".to_string(),
incompleteValue: false,
}
};
stream.write_json_packet(&msg);
true
}
"clearPseudoClassLocks" => {
let msg = ClearPseudoclassesReply {
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
"children" => {
let msg = ChildrenReply {
hasFirst: true,
hasLast: true,
nodes: vec!(),
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
_ => false,
}
}
}
struct NodeActor {
name: String,
}
impl Actor for NodeActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &String,
_msg: &json::Object,
_stream: &mut TcpStream) -> bool {
match msg_type.as_slice() {
_ => false,
}
}
}
#[deriving(Encodable)]
struct GetPageStyleReply {
from: String,
pageStyle: PageStyleMsg,
}
#[deriving(Encodable)]
struct PageStyleMsg {
actor: String,
}
struct PageStyleActor {
name: String,
}
#[deriving(Encodable)]
struct GetAppliedReply {
entries: Vec<AppliedEntry>,
rules: Vec<AppliedRule>,
sheets: Vec<AppliedSheet>,
from: String,
}
#[deriving(Encodable)]
struct GetComputedReply {
computed: Vec<uint>, //XXX all css props
from: String,
}
#[deriving(Encodable)]
struct AppliedEntry {
rule: String,
pseudoElement: json::Json,
isSystem: bool,
matchedSelectors: Vec<String>,
}
#[deriving(Encodable)]
struct AppliedRule {
actor: String,
__type__: uint,
href: String,
cssText: String,
line: uint,
column: uint,
parentStyleSheet: String,
}
#[deriving(Encodable)]
struct AppliedSheet {
actor: String,
href: String,
nodeHref: String,
disabled: bool,
title: String,
system: bool,
styleSheetIndex: int,
ruleCount: uint,
}
impl Actor for PageStyleActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &String,
_msg: &json::Object,
stream: &mut TcpStream) -> bool {
match msg_type.as_slice() {
"getApplied" => {
//TODO: query script for relevant applied styles to node (msg.node)
let msg = GetAppliedReply {
entries: vec!(),
rules: vec!(),
sheets: vec!(),
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
"getComputed" => {
//TODO: query script for relevant computed styles on node (msg.node)
let msg = GetComputedReply {
computed: vec!(),
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
//TODO: query script for box layout properties of node (msg.node)
//"getLayout" => {}
_ => false,
}
}
}
impl Actor for InspectorActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &String,
_msg: &json::Object,
stream: &mut TcpStream) -> bool {
match msg_type.as_slice() {
"getWalker" => {
if self.walker.borrow().is_none() {
let walker = WalkerActor {
name: registry.new_name("walker"),
};
let mut walker_name = self.walker.borrow_mut();
*walker_name = Some(walker.name());
registry.register_later(box walker);
}
let node = NodeActor {
name: registry.new_name("node"),
};
let node_actor_name = node.name();
registry.register_later(box node);
//TODO: query script for actual root node
//TODO: extra node actor creation
let node = NodeActorMsg {
actor: node_actor_name,
baseURI: "".to_string(),
parent: "".to_string(),
nodeType: 1, //ELEMENT_NODE
namespaceURI: "".to_string(),
nodeName: "html".to_string(),
numChildren: 1,
name: "".to_string(),
publicId: "".to_string(),
systemId: "".to_string(),
attrs: vec!(AttrMsg {
namespace: "".to_string(),
name: "manifest".to_string(),
value: "foo.manifest".to_string(),
}),
pseudoClassLocks: vec!(),
isDisplayed: true,
hasEventListeners: false,
isDocumentElement: true,
shortValue: "".to_string(),
incompleteValue: false,
};
let msg = GetWalkerReply {
from: self.name(),
walker: WalkerMsg {
actor: self.walker.borrow().clone().unwrap(),
root: node,
}
};
stream.write_json_packet(&msg);
true
}
"getPageStyle" => {
if self.pageStyle.borrow().is_none() {
let style = PageStyleActor {
name: registry.new_name("pageStyle"),
};
let mut pageStyle = self.pageStyle.borrow_mut();
*pageStyle = Some(style.name());
registry.register_later(box style);
}
let msg = GetPageStyleReply {
from: self.name(),
pageStyle: PageStyleMsg {
actor: self.pageStyle.borrow().clone().unwrap(),
},
};
stream.write_json_packet(&msg);
true
}
//TODO: this is an old message; try adding highlightable to the root traits instead
// and support getHighlighter instead
//"highlight" => {}
"getHighlighter" => {
if self.highlighter.borrow().is_none() {
let highlighter_actor = HighlighterActor {
name: registry.new_name("highlighter"),
};
let mut highlighter = self.highlighter.borrow_mut();
*highlighter = Some(highlighter_actor.name());
registry.register_later(box highlighter_actor);
}
let msg = GetHighlighterReply {
from: self.name(),
highligter: HighlighterMsg {
actor: self.highlighter.borrow().clone().unwrap(),
},
};
stream.write_json_packet(&msg);
true
}
_ => false,
}
}
}

View file

@ -15,7 +15,9 @@ use std::io::TcpStream;
#[deriving(Encodable)]
struct ActorTraits {
sources: bool
sources: bool,
highlightable: bool,
customHighlighters: Vec<String>,
}
#[deriving(Encodable)]
@ -40,7 +42,6 @@ struct RootActorMsg {
}
pub struct RootActor {
pub next: u32,
pub tabs: Vec<String>,
}
@ -90,6 +91,8 @@ impl RootActor {
applicationType: "browser".to_string(),
traits: ActorTraits {
sources: true,
highlightable: true,
customHighlighters: vec!("BoxModelHighlighter".to_string()),
},
}
}

View file

@ -36,6 +36,20 @@ struct ReconfigureReply {
from: String
}
#[deriving(Encodable)]
struct ListFramesReply {
from: String,
frames: Vec<FrameMsg>,
}
#[deriving(Encodable)]
struct FrameMsg {
id: uint,
url: String,
title: String,
parentID: uint,
}
#[deriving(Encodable)]
pub struct TabActorMsg {
actor: String,
@ -43,12 +57,15 @@ pub struct TabActorMsg {
url: String,
outerWindowID: uint,
consoleActor: String,
inspectorActor: String,
}
pub struct TabActor {
pub name: String,
pub title: String,
pub url: String,
pub console: String,
pub inspector: String,
}
impl Actor for TabActor {
@ -90,6 +107,16 @@ impl Actor for TabActor {
stream.write_json_packet(&msg);
true
}
"listFrames" => {
let msg = ListFramesReply {
from: self.name(),
frames: vec!(),
};
stream.write_json_packet(&msg);
true
}
_ => false
}
}
@ -102,7 +129,8 @@ impl TabActor {
title: self.title.clone(),
url: self.url.clone(),
outerWindowID: 0, //FIXME: this should probably be the pipeline id
consoleActor: "console0".to_string(), //FIXME: this should be the actual actor name
consoleActor: self.console.clone(),
inspectorActor: self.inspector.clone(),
}
}
}

View file

@ -27,8 +27,9 @@ extern crate serialize;
extern crate sync;
extern crate servo_msg = "msg";
use actor::ActorRegistry;
use actor::{Actor, ActorRegistry};
use actors::console::ConsoleActor;
use actors::inspector::InspectorActor;
use actors::root::RootActor;
use actors::tab::TabActor;
use protocol::JsonPacketSender;
@ -36,6 +37,7 @@ use protocol::JsonPacketSender;
use devtools_traits::{ServerExitMsg, DevtoolsControlMsg, NewGlobal, DevtoolScriptControlMsg};
use servo_msg::constellation_msg::PipelineId;
use std::cell::RefCell;
use std::comm;
use std::comm::{Disconnected, Empty};
use std::io::{TcpListener, TcpStream};
@ -49,8 +51,9 @@ mod actor;
/// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/
mod actors {
pub mod console;
pub mod tab;
pub mod inspector;
pub mod root;
pub mod tab;
}
mod protocol;
@ -76,7 +79,6 @@ fn run_server(port: Receiver<DevtoolsControlMsg>) {
let mut registry = ActorRegistry::new();
let root = box RootActor {
next: 0,
tabs: vec!(),
};
@ -136,27 +138,36 @@ fn run_server(port: Receiver<DevtoolsControlMsg>) {
sender: Sender<DevtoolScriptControlMsg>) {
let mut actors = actors.lock();
let (tab, console) = {
let root = actors.find_mut::<RootActor>("root");
let tab = TabActor {
name: format!("tab{}", root.next),
title: "".to_string(),
url: "about:blank".to_string(),
};
//TODO: move all this actor creation into a constructor method on TabActor
let (tab, console, inspector) = {
let console = ConsoleActor {
name: format!("console{}", root.next),
name: actors.new_name("console"),
script_chan: sender,
pipeline: pipeline,
};
let inspector = InspectorActor {
name: actors.new_name("inspector"),
walker: RefCell::new(None),
pageStyle: RefCell::new(None),
highlighter: RefCell::new(None),
};
//TODO: send along the current page title and URL
let tab = TabActor {
name: actors.new_name("tab"),
title: "".to_string(),
url: "about:blank".to_string(),
console: console.name(),
inspector: inspector.name(),
};
root.next += 1;
let root = actors.find_mut::<RootActor>("root");
root.tabs.push(tab.name.clone());
(tab, console)
(tab, console, inspector)
};
actors.register(box tab);
actors.register(box console);
actors.register(box inspector);
}
//TODO: figure out some system that allows us to watch for new connections,