/* 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::{ComputedNodeLayout, DevtoolScriptControlMsg, NodeInfo};
use devtools_traits::DevtoolScriptControlMsg::{GetChildren, GetDocumentElement, GetRootNode};
use devtools_traits::DevtoolScriptControlMsg::{GetLayout, ModifyAttribute};
use ipc_channel::ipc::{self, IpcSender};
use msg::constellation_msg::PipelineId;
use protocol::JsonPacketStream;
use serde_json::{self, Map, Value};
use std::cell::RefCell;
use std::net::TcpStream;
pub struct InspectorActor {
pub name: String,
pub walker: RefCell>,
pub pageStyle: RefCell >,
pub highlighter: RefCell >,
pub script_chan: IpcSender,
pub pipeline: PipelineId,
}
#[derive(Serialize)]
struct GetHighlighterReply {
highligter: HighlighterMsg, // sic.
from: String,
}
#[derive(Serialize)]
struct HighlighterMsg {
actor: String,
}
struct HighlighterActor {
name: String,
}
pub struct NodeActor {
pub name: String,
script_chan: IpcSender,
pipeline: PipelineId,
}
#[derive(Serialize)]
struct ShowBoxModelReply {
from: String,
}
#[derive(Serialize)]
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: &Map,
stream: &mut TcpStream) -> Result {
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(Serialize)]
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: &Map,
stream: &mut TcpStream) -> Result {
Ok(match msg_type {
"modifyAttributes" => {
let target = msg.get("to").unwrap().as_str().unwrap();
let mods = msg.get("modifications").unwrap().as_array().unwrap();
let modifications = mods.iter().map(|json_mod| {
serde_json::from_str(&serde_json::to_string(json_mod).unwrap()).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(Serialize)]
struct GetWalkerReply {
from: String,
walker: WalkerMsg,
}
#[derive(Serialize)]
struct WalkerMsg {
actor: String,
root: NodeActorMsg,
}
#[derive(Serialize)]
struct AttrMsg {
namespace: String,
name: String,
value: String,
}
#[derive(Serialize)]
struct NodeActorMsg {
actor: String,
baseURI: String,
parent: String,
nodeType: u16,
namespaceURI: String,
nodeName: String,
numChildren: usize,
name: String,
publicId: String,
systemId: String,
attrs: Vec,
pseudoClassLocks: Vec,
isDisplayed: bool,
hasEventListeners: bool,
isDocumentElement: bool,
shortValue: String,
incompleteValue: bool,
}
trait NodeInfoToProtocol {
fn encode(self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender,
pipeline: PipelineId) -> NodeActorMsg;
}
impl NodeInfoToProtocol for NodeInfo {
fn encode(self,
actors: &ActorRegistry,
display: bool,
script_chan: IpcSender,
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::new(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,
pipeline: PipelineId,
}
#[derive(Serialize)]
struct QuerySelectorReply {
from: String,
}
#[derive(Serialize)]
struct DocumentElementReply {
from: String,
node: NodeActorMsg,
}
#[derive(Serialize)]
struct ClearPseudoclassesReply {
from: String,
}
#[derive(Serialize)]
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: &str,
msg: &Map,
stream: &mut TcpStream) -> Result {
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().ok_or(())?;
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_str().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().ok_or(())?;
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(Serialize)]
struct GetPageStyleReply {
from: String,
pageStyle: PageStyleMsg,
}
#[derive(Serialize)]
struct PageStyleMsg {
actor: String,
}
struct PageStyleActor {
name: String,
script_chan: IpcSender,
pipeline: PipelineId,
}
#[derive(Serialize)]
struct GetAppliedReply {
entries: Vec,
rules: Vec,
sheets: Vec,
from: String,
}
#[derive(Serialize)]
struct GetComputedReply {
computed: Vec, //XXX all css props
from: String,
}
#[derive(Serialize)]
struct AppliedEntry {
rule: String,
pseudoElement: Value,
isSystem: bool,
matchedSelectors: Vec,
}
#[derive(Serialize)]
struct AppliedRule {
actor: String,
#[serde(rename = "type")]
type_: String,
href: String,
cssText: String,
line: u32,
column: u32,
parentStyleSheet: String,
}
#[derive(Serialize)]
struct AppliedSheet {
actor: String,
href: String,
nodeHref: String,
disabled: bool,
title: String,
system: bool,
styleSheetIndex: isize,
ruleCount: usize,
}
#[derive(Serialize)]
struct GetLayoutReply {
from: String,
display: String,
position: String,
#[serde(rename = "z-index")]
zIndex: String,
#[serde(rename = "box-sizing")]
boxSizing: String,
// Would be nice to use a proper struct, blocked by
// https://github.com/serde-rs/serde/issues/43
autoMargins: serde_json::value::Value,
#[serde(rename = "margin-top")]
marginTop: String,
#[serde(rename = "margin-right")]
marginRight: String,
#[serde(rename = "margin-bottom")]
marginBottom: String,
#[serde(rename = "margin-left")]
marginLeft: String,
#[serde(rename = "border-top-width")]
borderTopWidth: String,
#[serde(rename = "border-right-width")]
borderRightWidth: String,
#[serde(rename = "border-bottom-width")]
borderBottomWidth: String,
#[serde(rename = "border-left-width")]
borderLeftWidth: String,
#[serde(rename = "padding-top")]
paddingTop: String,
#[serde(rename = "padding-right")]
paddingRight: String,
#[serde(rename = "padding-bottom")]
paddingBottom: String,
#[serde(rename = "padding-left")]
paddingLeft: String,
width: f32,
height: f32,
}
impl Actor for PageStyleActor {
fn name(&self) -> String {
self.name.clone()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &str,
msg: &Map,
stream: &mut TcpStream) -> Result {
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_str().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 {
display, position, zIndex, boxSizing,
autoMargins, marginTop, marginRight, marginBottom, marginLeft,
borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth,
paddingTop, paddingRight, paddingBottom, paddingLeft,
width, height,
} = rx.recv().unwrap().ok_or(())?;
let auto_margins = msg.get("autoMargins")
.and_then(&Value::as_bool).unwrap_or(false);
// http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/styles.js
let msg = GetLayoutReply {
from: self.name(),
display: display,
position: position,
zIndex: zIndex,
boxSizing: boxSizing,
autoMargins: if auto_margins {
let mut m = Map::new();
let auto = serde_json::value::Value::String("auto".to_owned());
if autoMargins.top { m.insert("top".to_owned(), auto.clone()); }
if autoMargins.right { m.insert("right".to_owned(), auto.clone()); }
if autoMargins.bottom { m.insert("bottom".to_owned(), auto.clone()); }
if autoMargins.left { m.insert("left".to_owned(), auto.clone()); }
serde_json::value::Value::Object(m)
} else {
serde_json::value::Value::Null
},
marginTop: marginTop,
marginRight: marginRight,
marginBottom: marginBottom,
marginLeft: marginLeft,
borderTopWidth: borderTopWidth,
borderRightWidth: borderRightWidth,
borderBottomWidth: borderBottomWidth,
borderLeftWidth: borderLeftWidth,
paddingTop: paddingTop,
paddingRight: paddingRight,
paddingBottom: paddingBottom,
paddingLeft: paddingLeft,
width: width,
height: height,
};
let msg = serde_json::to_string(&msg).unwrap();
let msg = serde_json::from_str::(&msg).unwrap();
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: &Map,
stream: &mut TcpStream) -> Result {
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::new(walker));
}
let (tx, rx) = ipc::channel().unwrap();
self.script_chan.send(GetRootNode(self.pipeline, tx)).unwrap();
let root_info = rx.recv().unwrap().ok_or(())?;
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::new(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::new(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,
})
}
}