mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Break the association between pipelines and browsing context actors. Now there is one browsing context actor per actual browsing context, and individual actors keep track of known pipelines as necessary. There is also one console/performance/timeline/inspector/etc. actor per browsing context. This also centralizes more information in the browsing context actor. Rather than duplicating state for the active pipeline in actors that need to use it, each actor now remembers the name of its associated browsing context actor and obtains that state whenever it's necessary.
680 lines
20 KiB
Rust
680 lines
20 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 https://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 crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
|
|
use crate::actors::browsing_context::BrowsingContextActor;
|
|
use crate::protocol::JsonPacketStream;
|
|
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 serde_json::{self, Map, Value};
|
|
use std::cell::RefCell;
|
|
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 browsing_context: String,
|
|
}
|
|
|
|
#[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<DevtoolScriptControlMsg>,
|
|
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<String, Value>,
|
|
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(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<String, Value>,
|
|
stream: &mut TcpStream,
|
|
) -> Result<ActorMessageStatus, ()> {
|
|
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<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::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<DevtoolScriptControlMsg>,
|
|
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<NodeActorMsg>,
|
|
from: String,
|
|
}
|
|
|
|
impl Actor for WalkerActor {
|
|
fn name(&self) -> String {
|
|
self.name.clone()
|
|
}
|
|
|
|
fn handle_message(
|
|
&self,
|
|
registry: &ActorRegistry,
|
|
msg_type: &str,
|
|
msg: &Map<String, Value>,
|
|
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().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<DevtoolScriptControlMsg>,
|
|
pipeline: PipelineId,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct GetAppliedReply {
|
|
entries: Vec<AppliedEntry>,
|
|
rules: Vec<AppliedRule>,
|
|
sheets: Vec<AppliedSheet>,
|
|
from: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct GetComputedReply {
|
|
computed: Vec<u32>, //XXX all css props
|
|
from: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct AppliedEntry {
|
|
rule: String,
|
|
pseudoElement: Value,
|
|
isSystem: bool,
|
|
matchedSelectors: Vec<String>,
|
|
}
|
|
|
|
#[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<String, Value>,
|
|
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_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);
|
|
}
|
|
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::<Value>(&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<String, Value>,
|
|
stream: &mut TcpStream,
|
|
) -> Result<ActorMessageStatus, ()> {
|
|
let browsing_context = registry.find::<BrowsingContextActor>(&self.browsing_context);
|
|
let pipeline = browsing_context.active_pipeline.get();
|
|
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: 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(pipeline, tx)).unwrap();
|
|
let root_info = rx.recv().unwrap().ok_or(())?;
|
|
|
|
let node = root_info.encode(registry, false, self.script_chan.clone(), 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: 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,
|
|
})
|
|
}
|
|
}
|