From e9c4aa534da8bcd4457576b8c10719dd956ed062 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Wed, 3 Sep 2014 01:41:06 -0400 Subject: [PATCH] Implement wire protocol support for DOM inspector. --- components/devtools/actor.rs | 24 +- components/devtools/actors/inspector.rs | 472 ++++++++++++++++++++++++ components/devtools/actors/root.rs | 7 +- components/devtools/actors/tab.rs | 30 +- components/devtools/lib.rs | 39 +- 5 files changed, 554 insertions(+), 18 deletions(-) create mode 100644 components/devtools/actors/inspector.rs diff --git a/components/devtools/actor.rs b/components/devtools/actor.rs index 9dd2cb2b401..f489e7facf4 100644 --- a/components/devtools/actor.rs +++ b/components/devtools/actor.rs @@ -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>, + new_actors: RefCell>>, + next: Cell, } 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) { self.actors.insert(actor.name().to_string(), actor); } + pub fn register_later(&self, actor: Box) { + 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(); } } diff --git a/components/devtools/actors/inspector.rs b/components/devtools/actors/inspector.rs new file mode 100644 index 00000000000..2bf0b0671d3 --- /dev/null +++ b/components/devtools/actors/inspector.rs @@ -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>, + pub pageStyle: RefCell>, + pub highlighter: RefCell>, +} + +#[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, + + pseudoClassLocks: Vec, + + 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, + 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, + rules: Vec, + sheets: Vec, + from: String, +} + +#[deriving(Encodable)] +struct GetComputedReply { + computed: Vec, //XXX all css props + from: String, +} + +#[deriving(Encodable)] +struct AppliedEntry { + rule: String, + pseudoElement: json::Json, + isSystem: bool, + matchedSelectors: Vec, +} + +#[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, + } + } +} diff --git a/components/devtools/actors/root.rs b/components/devtools/actors/root.rs index 2e74528ae1f..8ebd79e4016 100644 --- a/components/devtools/actors/root.rs +++ b/components/devtools/actors/root.rs @@ -15,7 +15,9 @@ use std::io::TcpStream; #[deriving(Encodable)] struct ActorTraits { - sources: bool + sources: bool, + highlightable: bool, + customHighlighters: Vec, } #[deriving(Encodable)] @@ -40,7 +42,6 @@ struct RootActorMsg { } pub struct RootActor { - pub next: u32, pub tabs: Vec, } @@ -90,6 +91,8 @@ impl RootActor { applicationType: "browser".to_string(), traits: ActorTraits { sources: true, + highlightable: true, + customHighlighters: vec!("BoxModelHighlighter".to_string()), }, } } diff --git a/components/devtools/actors/tab.rs b/components/devtools/actors/tab.rs index 407bd32fa37..c9cc3481264 100644 --- a/components/devtools/actors/tab.rs +++ b/components/devtools/actors/tab.rs @@ -36,6 +36,20 @@ struct ReconfigureReply { from: String } +#[deriving(Encodable)] +struct ListFramesReply { + from: String, + frames: Vec, +} + +#[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(), } } } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 1249060118d..5dbce103546 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -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) { let mut registry = ActorRegistry::new(); let root = box RootActor { - next: 0, tabs: vec!(), }; @@ -136,27 +138,36 @@ fn run_server(port: Receiver) { sender: Sender) { let mut actors = actors.lock(); - let (tab, console) = { - let root = actors.find_mut::("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::("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,