servo/components/devtools/actors/inspector.rs
2016-01-17 13:18:15 +01:00

580 lines
17 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at 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, ActorMessageStatus, ActorRegistry};
use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement, GetRootNode};
use devtools_traits::DevtoolScriptControlMsg::{GetLayout, ModifyAttribute};
use devtools_traits::{ComputedNodeLayout, DevtoolScriptControlMsg, NodeInfo};
use ipc_channel::ipc::{self, IpcSender};
use msg::constellation_msg::PipelineId;
use protocol::JsonPacketStream;
use rustc_serialize::json::{self, Json, ToJson};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::net::TcpStream;
pub struct InspectorActor {
pub name: String,
pub walker: RefCell<Option<String>>,
pub pageStyle: RefCell<Option<String>>,
pub highlighter: RefCell<Option<String>>,
pub script_chan: IpcSender<DevtoolScriptControlMsg>,
pub pipeline: PipelineId,
}
#[derive(RustcEncodable)]
struct GetHighlighterReply {
highligter: HighlighterMsg, // sic.
from: String,
}
#[derive(RustcEncodable)]
struct HighlighterMsg {
actor: String,
}
struct HighlighterActor {
name: String,
}
pub struct NodeActor {
pub name: String,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId,
}
#[derive(RustcEncodable)]
struct ShowBoxModelReply {
from: String,
}
#[derive(RustcEncodable)]
struct HideBoxModelReply {
from: String,
}
impl Actor for HighlighterActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
_registry: &ActorRegistry,
msg_type: &str,
_msg: &json::Object,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"showBoxModel" => {
let msg = ShowBoxModelReply {
from: self.name(),
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
"hideBoxModel" => {
let msg = HideBoxModelReply {
from: self.name(),
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
_ => ActorMessageStatus::Ignored,
})
}
}
#[derive(RustcEncodable)]
struct ModifyAttributeReply {
from: String,
}
impl Actor for NodeActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &json::Object,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"modifyAttributes" => {
let target = msg.get("to").unwrap().as_string().unwrap();
let mods = msg.get("modifications").unwrap().as_array().unwrap();
let modifications = mods.iter().map(|json_mod| {
json::decode(&json_mod.to_string()).unwrap()
}).collect();
self.script_chan.send(ModifyAttribute(self.pipeline,
registry.actor_to_script(target.to_owned()),
modifications))
.unwrap();
let reply = ModifyAttributeReply {
from: self.name(),
};
stream.write_json_packet(&reply);
ActorMessageStatus::Processed
}
_ => ActorMessageStatus::Ignored,
})
}
}
#[derive(RustcEncodable)]
struct GetWalkerReply {
from: String,
walker: WalkerMsg,
}
#[derive(RustcEncodable)]
struct WalkerMsg {
actor: String,
root: NodeActorMsg,
}
#[derive(RustcEncodable)]
struct AttrMsg {
namespace: String,
name: String,
value: String,
}
#[derive(RustcEncodable)]
struct NodeActorMsg {
actor: String,
baseURI: String,
parent: String,
nodeType: u16,
namespaceURI: String,
nodeName: String,
numChildren: usize,
name: String,
publicId: String,
systemId: String,
attrs: Vec<AttrMsg>,
pseudoClassLocks: Vec<String>,
isDisplayed: bool,
hasEventListeners: bool,
isDocumentElement: bool,
shortValue: String,
incompleteValue: bool,
}
trait NodeInfoToProtocol {
fn encode(self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId) -> NodeActorMsg;
}
impl NodeInfoToProtocol for NodeInfo {
fn encode(self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId) -> NodeActorMsg {
let actor_name = if !actors.script_actor_registered(self.uniqueId.clone()) {
let name = actors.new_name("node");
let node_actor = NodeActor {
name: name.clone(),
script_chan: script_chan,
pipeline: pipeline.clone(),
};
actors.register_script_actor(self.uniqueId, name.clone());
actors.register_later(box node_actor);
name
} else {
actors.script_to_actor(self.uniqueId)
};
NodeActorMsg {
actor: actor_name,
baseURI: self.baseURI,
parent: actors.script_to_actor(self.parent.clone()),
nodeType: self.nodeType,
namespaceURI: self.namespaceURI,
nodeName: self.nodeName,
numChildren: self.numChildren,
name: self.name,
publicId: self.publicId,
systemId: self.systemId,
attrs: self.attrs.into_iter().map(|attr| {
AttrMsg {
namespace: attr.namespace,
name: attr.name,
value: attr.value,
}
}).collect(),
pseudoClassLocks: vec!(), //TODO get this data from script
isDisplayed: display,
hasEventListeners: false, //TODO get this data from script
isDocumentElement: self.isDocumentElement,
shortValue: self.shortValue,
incompleteValue: self.incompleteValue,
}
}
}
struct WalkerActor {
name: String,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId,
}
#[derive(RustcEncodable)]
struct QuerySelectorReply {
from: String,
}
#[derive(RustcEncodable)]
struct DocumentElementReply {
from: String,
node: NodeActorMsg,
}
#[derive(RustcEncodable)]
struct ClearPseudoclassesReply {
from: String,
}
#[derive(RustcEncodable)]
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: &str,
msg: &json::Object,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"querySelector" => {
let msg = QuerySelectorReply {
from: self.name(),
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
"documentElement" => {
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetDocumentElement(self.pipeline, tx)).unwrap();
let doc_elem_info = rx.recv().unwrap();
let node = doc_elem_info.encode(registry, true, self.script_chan.clone(), self.pipeline);
let msg = DocumentElementReply {
from: self.name(),
node: node,
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
"clearPseudoClassLocks" => {
let msg = ClearPseudoclassesReply {
from: self.name(),
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
"children" => {
let target = msg.get("node").unwrap().as_string().unwrap();
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetChildren(self.pipeline,
registry.actor_to_script(target.to_owned()),
tx))
.unwrap();
let children = rx.recv().unwrap();
let msg = ChildrenReply {
hasFirst: true,
hasLast: true,
nodes: children.into_iter().map(|child| {
child.encode(registry, true, self.script_chan.clone(), self.pipeline)
}).collect(),
from: self.name(),
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
_ => ActorMessageStatus::Ignored,
})
}
}
#[derive(RustcEncodable)]
struct GetPageStyleReply {
from: String,
pageStyle: PageStyleMsg,
}
#[derive(RustcEncodable)]
struct PageStyleMsg {
actor: String,
}
struct PageStyleActor {
name: String,
script_chan: IpcSender<DevtoolScriptControlMsg>,
pipeline: PipelineId,
}
#[derive(RustcEncodable)]
struct GetAppliedReply {
entries: Vec<AppliedEntry>,
rules: Vec<AppliedRule>,
sheets: Vec<AppliedSheet>,
from: String,
}
#[derive(RustcEncodable)]
struct GetComputedReply {
computed: Vec<u32>, //XXX all css props
from: String,
}
#[derive(RustcEncodable)]
struct AppliedEntry {
rule: String,
pseudoElement: Json,
isSystem: bool,
matchedSelectors: Vec<String>,
}
#[derive(RustcEncodable)]
struct AppliedRule {
actor: String,
__type__: u32,
href: String,
cssText: String,
line: u32,
column: u32,
parentStyleSheet: String,
}
#[derive(RustcEncodable)]
struct AppliedSheet {
actor: String,
href: String,
nodeHref: String,
disabled: bool,
title: String,
system: bool,
styleSheetIndex: isize,
ruleCount: usize,
}
#[derive(RustcEncodable)]
struct GetLayoutReply {
width: i32,
height: i32,
autoMargins: Json,
from: String,
}
#[derive(RustcEncodable)]
#[allow(dead_code)]
struct AutoMargins {
top: String,
bottom: String,
left: String,
right: String,
}
impl Actor for PageStyleActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &json::Object,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"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);
ActorMessageStatus::Processed
}
"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);
ActorMessageStatus::Processed
}
//TODO: query script for box layout properties of node (msg.node)
"getLayout" => {
let target = msg.get("node").unwrap().as_string().unwrap();
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetLayout(self.pipeline,
registry.actor_to_script(target.to_owned()),
tx))
.unwrap();
let ComputedNodeLayout { width, height } = rx.recv().unwrap();
let auto_margins = msg.get("autoMargins")
.and_then(&Json::as_boolean).unwrap_or(false);
//TODO: the remaining layout properties (margin, border, padding, position)
// as specified in getLayout in
// http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/styles.js
let msg = GetLayoutReply {
width: width.round() as i32,
height: height.round() as i32,
autoMargins: if auto_margins {
//TODO: real values like processMargins in
// http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/styles.js
let mut m = BTreeMap::new();
m.insert("top".to_owned(), "auto".to_owned().to_json());
m.insert("bottom".to_owned(), "auto".to_owned().to_json());
m.insert("left".to_owned(), "auto".to_owned().to_json());
m.insert("right".to_owned(), "auto".to_owned().to_json());
Json::Object(m)
} else {
Json::Null
},
from: self.name(),
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
_ => ActorMessageStatus::Ignored,
})
}
}
impl Actor for InspectorActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
_msg: &json::Object,
stream: &mut TcpStream) -> Result<ActorMessageStatus, ()> {
Ok(match msg_type {
"getWalker" => {
if self.walker.borrow().is_none() {
let walker = WalkerActor {
name: registry.new_name("walker"),
script_chan: self.script_chan.clone(),
pipeline: self.pipeline,
};
let mut walker_name = self.walker.borrow_mut();
*walker_name = Some(walker.name());
registry.register_later(box walker);
}
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetRootNode(self.pipeline, tx)).unwrap();
let root_info = rx.recv().unwrap();
let node = root_info.encode(registry, false, self.script_chan.clone(), self.pipeline);
let msg = GetWalkerReply {
from: self.name(),
walker: WalkerMsg {
actor: self.walker.borrow().clone().unwrap(),
root: node,
}
};
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
}
"getPageStyle" => {
if self.pageStyle.borrow().is_none() {
let style = PageStyleActor {
name: registry.new_name("pageStyle"),
script_chan: self.script_chan.clone(),
pipeline: self.pipeline,
};
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);
ActorMessageStatus::Processed
}
//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);
ActorMessageStatus::Processed
}
_ => ActorMessageStatus::Ignored,
})
}
}