servo: Merge #3172 - Dump initial prototype of devtools server into the build. Expect lies if (from jdm:devtools)

Source-Repo: https://github.com/servo/servo
Source-Revision: b82c0dced08ccda8c3c7f35643c3891bc45b058c
This commit is contained in:
Josh Matthews 2014-09-19 09:15:03 -04:00
parent 22d49834af
commit d3fc0a6b43
27 changed files with 1799 additions and 6 deletions

19
servo/Cargo.lock generated
View File

@ -60,6 +60,8 @@ dependencies = [
"azure 0.1.0 (git+https://github.com/servo/rust-azure#9c5567b79d8b87e8ef3b48c5842f453978035d21)",
"core_graphics 0.1.0 (git+https://github.com/servo/rust-core-graphics#04bd18a4eb83a645a1a32326a33149ba2d0e81be)",
"core_text 0.1.0 (git+https://github.com/servo/rust-core-text#e2280222889c030df27ded9a378c14a0e31ab463)",
"devtools 0.0.1",
"devtools_traits 0.0.1",
"geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)",
"gfx 0.0.1",
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#dd1a111c827994886d2cdebf91a1838603256390)",
@ -105,6 +107,21 @@ dependencies = [
"encoding 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding#12b6610adff6eddc060691888c36017cd3ad57f7)",
]
[[package]]
name = "devtools"
version = "0.0.1"
dependencies = [
"devtools_traits 0.0.1",
"msg 0.0.1",
]
[[package]]
name = "devtools_traits"
version = "0.0.1"
dependencies = [
"msg 0.0.1",
]
[[package]]
name = "egl"
version = "0.1.0"
@ -364,6 +381,7 @@ version = "0.0.1"
dependencies = [
"canvas 0.0.1",
"cssparser 0.1.0 (git+https://github.com/servo/rust-cssparser#42346400a6629b17a48d06f0a9b28ae498947c6f)",
"devtools_traits 0.0.1",
"encoding 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding#12b6610adff6eddc060691888c36017cd3ad57f7)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)",
"gfx 0.0.1",
@ -383,6 +401,7 @@ dependencies = [
name = "script_traits"
version = "0.0.1"
dependencies = [
"devtools_traits 0.0.1",
"geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)",
"msg 0.0.1",
"net 0.0.1",

View File

@ -25,6 +25,12 @@ path = "../net"
[dependencies.util]
path = "../util"
[dependencies.devtools]
path = "../devtools"
[dependencies.devtools_traits]
path = "../devtools_traits"
[dependencies.alert]
git = "https://github.com/servo/rust-alert"

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use compositor_task::{CompositorChan, LoadComplete, ShutdownComplete, SetLayerOrigin, SetIds};
use devtools_traits::DevtoolsControlChan;
use std::collections::hashmap::{HashMap, HashSet};
use geom::rect::{Rect, TypedRect};
use geom::scale_factor::ScaleFactor;
@ -41,6 +42,7 @@ pub struct Constellation<LTF, STF> {
pub compositor_chan: CompositorChan,
pub resource_task: ResourceTask,
pub image_cache_task: ImageCacheTask,
devtools_chan: Option<DevtoolsControlChan>,
pipelines: HashMap<PipelineId, Rc<Pipeline>>,
font_cache_task: FontCacheTask,
navigation_context: NavigationContext,
@ -244,7 +246,8 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
time_profiler_chan: TimeProfilerChan)
time_profiler_chan: TimeProfilerChan,
devtools_chan: Option<DevtoolsControlChan>)
-> ConstellationChan {
let (constellation_port, constellation_chan) = ConstellationChan::new();
let constellation_chan_clone = constellation_chan.clone();
@ -254,6 +257,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
chan: constellation_chan_clone,
request_port: constellation_port,
compositor_chan: compositor_chan,
devtools_chan: devtools_chan,
resource_task: resource_task,
image_cache_task: image_cache_task,
font_cache_task: font_cache_task,
@ -295,6 +299,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
subpage_id,
self.chan.clone(),
self.compositor_chan.clone(),
self.devtools_chan.clone(),
self.image_cache_task.clone(),
self.font_cache_task.clone(),
self.resource_task.clone(),

View File

@ -16,6 +16,7 @@ extern crate debug;
extern crate alert;
extern crate azure;
extern crate devtools_traits;
extern crate geom;
extern crate gfx;
#[cfg(not(target_os="android"))]

View File

@ -7,6 +7,7 @@ use layout_traits::{LayoutTaskFactory, LayoutControlChan};
use script_traits::{ScriptControlChan, ScriptTaskFactory};
use script_traits::{AttachLayoutMsg, LoadMsg, NewLayoutInfo, ExitPipelineMsg};
use devtools_traits::DevtoolsControlChan;
use gfx::render_task::{PaintPermissionGranted, PaintPermissionRevoked};
use gfx::render_task::{RenderChan, RenderTask};
use servo_msg::constellation_msg::{ConstellationChan, Failure, PipelineId, SubpageId};
@ -49,6 +50,7 @@ impl Pipeline {
subpage_id: Option<SubpageId>,
constellation_chan: ConstellationChan,
compositor_chan: CompositorChan,
devtools_chan: Option<DevtoolsControlChan>,
image_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
resource_task: ResourceTask,
@ -82,6 +84,7 @@ impl Pipeline {
failure.clone(),
resource_task.clone(),
image_cache_task.clone(),
devtools_chan,
window_size);
ScriptControlChan(script_chan)
}

View File

@ -0,0 +1,14 @@
[package]
name = "devtools"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "devtools"
path = "lib.rs"
[dependencies.devtools_traits]
path = "../devtools_traits"
[dependencies.msg]
path = "../msg"

View File

@ -0,0 +1,171 @@
/* 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/. */
/// General actor system infrastructure.
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;
use serialize::json;
/// A common trait for all devtools actors that encompasses an immutable name
/// and the ability to process messages that are directed to particular actors.
/// TODO: ensure the name is immutable
pub trait Actor: Any {
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &String,
msg: &json::Object,
stream: &mut TcpStream) -> bool;
fn name(&self) -> String;
}
impl<'a> AnyMutRefExt<'a> for &'a mut Actor {
fn downcast_mut<T: 'static>(self) -> Option<&'a mut T> {
if self.is::<T>() {
unsafe {
// Get the raw representation of the trait object
let to: TraitObject = transmute_copy(&self);
// Extract the data pointer
Some(transmute(to.data))
}
} else {
None
}
}
}
impl<'a> AnyRefExt<'a> for &'a Actor {
fn is<T: 'static>(self) -> bool {
//FIXME: This implementation is bogus since get_type_id is private now.
// However, this implementation is only needed so long as there's a Rust bug
// that prevents downcast_ref from giving realistic return values, and this is
// ok since we're careful with the types we pull out of the hashmap.
/*let t = TypeId::of::<T>();
let boxed = self.get_type_id();
t == boxed*/
true
}
fn downcast_ref<T: 'static>(self) -> Option<&'a T> {
if self.is::<T>() {
unsafe {
// Get the raw representation of the trait object
let to: TraitObject = transmute_copy(&self);
// Extract the data pointer
Some(transmute(to.data))
}
} else {
None
}
}
}
/// A list of known, owned actors.
pub struct ActorRegistry {
actors: HashMap<String, Box<Actor+Send+Sized>>,
new_actors: RefCell<Vec<Box<Actor+Send+Sized>>>,
script_actors: RefCell<HashMap<String, String>>,
next: Cell<u32>,
}
impl ActorRegistry {
/// Create an empty registry.
pub fn new() -> ActorRegistry {
ActorRegistry {
actors: HashMap::new(),
new_actors: RefCell::new(vec!()),
script_actors: RefCell::new(HashMap::new()),
next: Cell::new(0),
}
}
pub fn register_script_actor(&self, script_id: String, actor: String) {
println!("registering {:s} ({:s})", actor.as_slice(), script_id.as_slice());
let mut script_actors = self.script_actors.borrow_mut();
script_actors.insert(script_id, actor);
}
pub fn script_to_actor(&self, script_id: String) -> String {
if script_id.as_slice() == "" {
return "".to_string();
}
self.script_actors.borrow().find(&script_id).unwrap().to_string()
}
pub fn script_actor_registered(&self, script_id: String) -> bool {
self.script_actors.borrow().contains_key(&script_id)
}
pub fn actor_to_script(&self, actor: String) -> String {
for (key, value) in self.script_actors.borrow().iter() {
println!("checking {:s}", value.as_slice());
if value.as_slice() == actor.as_slice() {
return key.to_string();
}
}
fail!("couldn't find actor named {:s}", actor)
}
/// 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
// fails for unknown reasons.
/*let actor: &Actor+Send+Sized = *self.actors.find(&name.to_string()).unwrap();
(actor as &Any).downcast_ref::<T>().unwrap()*/
self.actors.find(&name.to_string()).unwrap().as_ref::<T>().unwrap()
}
/// Find an actor by registered name
pub fn find_mut<'a, T: 'static>(&'a mut self, name: &str) -> &'a mut T {
//FIXME: Rust bug forces us to implement bogus Any for Actor since downcast_ref currently
// fails for unknown reasons.
/*let actor: &mut Actor+Send+Sized = *self.actors.find_mut(&name.to_string()).unwrap();
(actor as &mut Any).downcast_mut::<T>().unwrap()*/
self.actors.find_mut(&name.to_string()).unwrap().downcast_mut::<T>().unwrap()
}
/// 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(&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),
Some(actor) => {
let msg_type = msg.find(&"type".to_string()).unwrap().as_string().unwrap();
if !actor.handle_message(self, &msg_type.to_string(), msg, stream) {
println!("unexpected message type \"{:s}\" found for actor \"{:s}\"",
msg_type, to);
}
}
}
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,284 @@
/* 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/webconsole.js).
/// Mediates interaction between the remote web console and equivalent functionality (object
/// inspection, JS evaluation, autocompletion) in Servo.
use actor::{Actor, ActorRegistry};
use protocol::JsonPacketSender;
use devtools_traits::{EvaluateJS, NullValue, VoidValue, NumberValue, StringValue, BooleanValue};
use devtools_traits::{ActorValue, DevtoolScriptControlMsg};
use servo_msg::constellation_msg::PipelineId;
use collections::TreeMap;
use serialize::json;
use serialize::json::ToJson;
use std::io::TcpStream;
#[deriving(Encodable)]
struct StartedListenersTraits {
customNetworkRequest: bool,
}
#[deriving(Encodable)]
struct StartedListenersReply {
from: String,
nativeConsoleAPI: bool,
startedListeners: Vec<String>,
traits: StartedListenersTraits,
}
#[deriving(Encodable)]
struct ConsoleAPIMessage {
_type: String, //FIXME: should this be __type__ instead?
}
#[deriving(Encodable)]
struct PageErrorMessage {
_type: String, //FIXME: should this be __type__ instead?
errorMessage: String,
sourceName: String,
lineText: String,
lineNumber: uint,
columnNumber: uint,
category: String,
timeStamp: uint,
warning: bool,
error: bool,
exception: bool,
strict: bool,
private: bool,
}
#[deriving(Encodable)]
struct LogMessage {
_type: String, //FIXME: should this be __type__ instead?
timeStamp: uint,
message: String,
}
#[deriving(Encodable)]
enum ConsoleMessageType {
ConsoleAPIType(ConsoleAPIMessage),
PageErrorType(PageErrorMessage),
LogMessageType(LogMessage),
}
#[deriving(Encodable)]
struct GetCachedMessagesReply {
from: String,
messages: Vec<json::Object>,
}
#[deriving(Encodable)]
struct StopListenersReply {
from: String,
stoppedListeners: Vec<String>,
}
#[deriving(Encodable)]
struct AutocompleteReply {
from: String,
matches: Vec<String>,
matchProp: String,
}
#[deriving(Encodable)]
struct EvaluateJSReply {
from: String,
input: String,
result: json::Json,
timestamp: uint,
exception: json::Json,
exceptionMessage: String,
helperResult: json::Json,
}
pub struct ConsoleActor {
pub name: String,
pub pipeline: PipelineId,
pub script_chan: Sender<DevtoolScriptControlMsg>,
}
impl Actor for ConsoleActor {
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() {
"getCachedMessages" => {
let types = msg.find(&"messageTypes".to_string()).unwrap().as_list().unwrap();
let /*mut*/ messages = vec!();
for msg_type in types.iter() {
let msg_type = msg_type.as_string().unwrap();
match msg_type.as_slice() {
"ConsoleAPI" => {
//TODO: figure out all consoleapi properties from FFOX source
}
"PageError" => {
//TODO: make script error reporter pass all reported errors
// to devtools and cache them for returning here.
/*let message = PageErrorMessage {
_type: msg_type.to_string(),
sourceName: "".to_string(),
lineText: "".to_string(),
lineNumber: 0,
columnNumber: 0,
category: "".to_string(),
warning: false,
error: true,
exception: false,
strict: false,
private: false,
timeStamp: 0,
errorMessage: "page error test".to_string(),
};
messages.push(json::from_str(json::encode(&message).as_slice()).unwrap().as_object().unwrap().clone());*/
}
"LogMessage" => {
//TODO: figure out when LogMessage is necessary
/*let message = LogMessage {
_type: msg_type.to_string(),
timeStamp: 0,
message: "log message test".to_string(),
};
messages.push(json::from_str(json::encode(&message).as_slice()).unwrap().as_object().unwrap().clone());*/
}
s => println!("unrecognized message type requested: \"{:s}\"", s),
}
}
let msg = GetCachedMessagesReply {
from: self.name(),
messages: messages,
};
stream.write_json_packet(&msg);
true
}
"startListeners" => {
//TODO: actually implement listener filters that support starting/stopping
let msg = StartedListenersReply {
from: self.name(),
nativeConsoleAPI: true,
startedListeners:
vec!("PageError".to_string(), "ConsoleAPI".to_string()),
traits: StartedListenersTraits {
customNetworkRequest: true,
}
};
stream.write_json_packet(&msg);
true
}
"stopListeners" => {
//TODO: actually implement listener filters that support starting/stopping
let msg = StopListenersReply {
from: self.name(),
stoppedListeners: msg.find(&"listeners".to_string())
.unwrap()
.as_list()
.unwrap_or(&vec!())
.iter()
.map(|listener| listener.as_string().unwrap().to_string())
.collect(),
};
stream.write_json_packet(&msg);
true
}
//TODO: implement autocompletion like onAutocomplete in
// http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js
"autocomplete" => {
let msg = AutocompleteReply {
from: self.name(),
matches: vec!(),
matchProp: "".to_string(),
};
stream.write_json_packet(&msg);
true
}
"evaluateJS" => {
let input = msg.find(&"text".to_string()).unwrap().as_string().unwrap().to_string();
let (chan, port) = channel();
self.script_chan.send(EvaluateJS(self.pipeline, input.clone(), chan));
//TODO: extract conversion into protocol module or some other useful place
let result = match port.recv() {
VoidValue => {
let mut m = TreeMap::new();
m.insert("type".to_string(), "undefined".to_string().to_json());
json::Object(m)
}
NullValue => {
let mut m = TreeMap::new();
m.insert("type".to_string(), "null".to_string().to_json());
json::Object(m)
}
BooleanValue(val) => val.to_json(),
NumberValue(val) => {
if val.is_nan() {
let mut m = TreeMap::new();
m.insert("type".to_string(), "NaN".to_string().to_json());
json::Object(m)
} else if val.is_infinite() {
let mut m = TreeMap::new();
if val < 0. {
m.insert("type".to_string(), "-Infinity".to_string().to_json());
} else {
m.insert("type".to_string(), "Infinity".to_string().to_json());
}
json::Object(m)
} else if val == Float::neg_zero() {
let mut m = TreeMap::new();
m.insert("type".to_string(), "-0".to_string().to_json());
json::Object(m)
} else {
val.to_json()
}
}
StringValue(s) => s.to_json(),
ActorValue(s) => {
//TODO: make initial ActorValue message include these properties.
let mut m = TreeMap::new();
m.insert("type".to_string(), "object".to_string().to_json());
m.insert("class".to_string(), "???".to_string().to_json());
m.insert("actor".to_string(), s.to_json());
m.insert("extensible".to_string(), true.to_json());
m.insert("frozen".to_string(), false.to_json());
m.insert("sealed".to_string(), false.to_json());
json::Object(m)
}
};
//TODO: catch and return exception values from JS evaluation
let msg = EvaluateJSReply {
from: self.name(),
input: input,
result: result,
timestamp: 0,
exception: json::Object(TreeMap::new()),
exceptionMessage: "".to_string(),
helperResult: json::Object(TreeMap::new()),
};
stream.write_json_packet(&msg);
true
}
_ => false
}
}
}

View File

@ -0,0 +1,516 @@
/* 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 devtools_traits::{GetRootNode, GetDocumentElement, GetChildren, DevtoolScriptControlMsg};
use devtools_traits::{GetLayout, NodeInfo};
use actor::{Actor, ActorRegistry};
use protocol::JsonPacketSender;
use collections::TreeMap;
use servo_msg::constellation_msg::PipelineId;
use serialize::json;
use serialize::json::ToJson;
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>>,
pub script_chan: Sender<DevtoolScriptControlMsg>,
pub pipeline: PipelineId,
}
#[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,
}
trait NodeInfoToProtocol {
fn encode(self, actors: &ActorRegistry, display: bool) -> NodeActorMsg;
}
impl NodeInfoToProtocol for NodeInfo {
fn encode(self, actors: &ActorRegistry, display: bool) -> NodeActorMsg {
let actor_name = if !actors.script_actor_registered(self.uniqueId.clone()) {
let name = actors.new_name("node");
actors.register_script_actor(self.uniqueId, name.clone());
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.move_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: Sender<DevtoolScriptControlMsg>,
pipeline: PipelineId,
}
#[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 (tx, rx) = channel();
self.script_chan.send(GetDocumentElement(self.pipeline, tx));
let doc_elem_info = rx.recv();
let node = doc_elem_info.encode(registry, true);
let msg = DocumentElementReply {
from: self.name(),
node: node,
};
stream.write_json_packet(&msg);
true
}
"clearPseudoClassLocks" => {
let msg = ClearPseudoclassesReply {
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
"children" => {
let target = msg.find(&"node".to_string()).unwrap().as_string().unwrap();
let (tx, rx) = channel();
self.script_chan.send(GetChildren(self.pipeline,
registry.actor_to_script(target.to_string()),
tx));
let children = rx.recv();
let msg = ChildrenReply {
hasFirst: true,
hasLast: true,
nodes: children.move_iter().map(|child| {
child.encode(registry, true)
}).collect(),
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
_ => false,
}
}
}
#[deriving(Encodable)]
struct GetPageStyleReply {
from: String,
pageStyle: PageStyleMsg,
}
#[deriving(Encodable)]
struct PageStyleMsg {
actor: String,
}
struct PageStyleActor {
name: String,
script_chan: Sender<DevtoolScriptControlMsg>,
pipeline: PipelineId,
}
#[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,
}
#[deriving(Encodable)]
struct GetLayoutReply {
width: int,
height: int,
autoMargins: json::Json,
from: String,
}
#[deriving(Encodable)]
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: &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" => {
let target = msg.find(&"node".to_string()).unwrap().as_string().unwrap();
let (tx, rx) = channel();
self.script_chan.send(GetLayout(self.pipeline,
registry.actor_to_script(target.to_string()),
tx));
let (width, height) = rx.recv();
let auto_margins = msg.find(&"autoMargins".to_string()).unwrap().as_boolean().unwrap();
//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 int,
height: height.round() as int,
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 = TreeMap::new();
m.insert("top".to_string(), "auto".to_string().to_json());
m.insert("bottom".to_string(), "auto".to_string().to_json());
m.insert("left".to_string(), "auto".to_string().to_json());
m.insert("right".to_string(), "auto".to_string().to_json());
json::Object(m)
} else {
json::Null
},
from: self.name(),
};
stream.write_json_packet(&msg);
true
}
_ => 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"),
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) = channel();
self.script_chan.send(GetRootNode(self.pipeline, tx));
let root_info = rx.recv();
let node = root_info.encode(registry, 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"),
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);
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

@ -0,0 +1,99 @@
/* 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/root.js).
/// Connection point for all new remote devtools interactions, providing lists of know actors
/// that perform more specific actions (tabs, addons, browser chrome, etc.)
use actor::{Actor, ActorRegistry};
use actors::tab::{TabActor, TabActorMsg};
use protocol::JsonPacketSender;
use serialize::json;
use std::io::TcpStream;
#[deriving(Encodable)]
struct ActorTraits {
sources: bool,
highlightable: bool,
customHighlighters: Vec<String>,
}
#[deriving(Encodable)]
struct ErrorReply {
from: String,
error: String,
message: String,
}
#[deriving(Encodable)]
struct ListTabsReply {
from: String,
selected: uint,
tabs: Vec<TabActorMsg>,
}
#[deriving(Encodable)]
struct RootActorMsg {
from: String,
applicationType: String,
traits: ActorTraits,
}
pub struct RootActor {
pub tabs: Vec<String>,
}
impl Actor for RootActor {
fn name(&self) -> String {
"root".to_string()
}
fn handle_message(&self,
registry: &ActorRegistry,
msg_type: &String,
_msg: &json::Object,
stream: &mut TcpStream) -> bool {
match msg_type.as_slice() {
"listAddons" => {
let actor = ErrorReply {
from: "root".to_string(),
error: "noAddons".to_string(),
message: "This root actor has no browser addons.".to_string(),
};
stream.write_json_packet(&actor);
true
}
//https://wiki.mozilla.org/Remote_Debugging_Protocol#Listing_Browser_Tabs
"listTabs" => {
let actor = ListTabsReply {
from: "root".to_string(),
selected: 0,
tabs: self.tabs.iter().map(|tab| {
registry.find::<TabActor>(tab.as_slice()).encodable()
}).collect()
};
stream.write_json_packet(&actor);
true
}
_ => false
}
}
}
impl RootActor {
pub fn encodable(&self) -> RootActorMsg {
RootActorMsg {
from: "root".to_string(),
applicationType: "browser".to_string(),
traits: ActorTraits {
sources: true,
highlightable: true,
customHighlighters: vec!("BoxModelHighlighter".to_string()),
},
}
}
}

View File

@ -0,0 +1,136 @@
/* 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/webbrowser.js).
/// Connection point for remote devtools that wish to investigate a particular tab's contents.
/// Supports dynamic attaching and detaching which control notifications of navigation, etc.
use actor::{Actor, ActorRegistry};
use protocol::JsonPacketSender;
use serialize::json;
use std::io::TcpStream;
#[deriving(Encodable)]
struct TabTraits;
#[deriving(Encodable)]
struct TabAttachedReply {
from: String,
__type__: String,
threadActor: String,
cacheDisabled: bool,
javascriptEnabled: bool,
traits: TabTraits,
}
#[deriving(Encodable)]
struct TabDetachedReply {
from: String,
__type__: String,
}
#[deriving(Encodable)]
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,
title: String,
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 {
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() {
"reconfigure" => {
stream.write_json_packet(&ReconfigureReply { from: self.name() });
true
}
// https://wiki.mozilla.org/Remote_Debugging_Protocol#Listing_Browser_Tabs
// (see "To attach to a _tabActor_")
"attach" => {
let msg = TabAttachedReply {
from: self.name(),
__type__: "tabAttached".to_string(),
threadActor: self.name(),
cacheDisabled: false,
javascriptEnabled: true,
traits: TabTraits,
};
stream.write_json_packet(&msg);
true
}
"detach" => {
let msg = TabDetachedReply {
from: self.name(),
__type__: "detached".to_string(),
};
stream.write_json_packet(&msg);
true
}
"listFrames" => {
let msg = ListFramesReply {
from: self.name(),
frames: vec!(),
};
stream.write_json_packet(&msg);
true
}
_ => false
}
}
}
impl TabActor {
pub fn encodable(&self) -> TabActorMsg {
TabActorMsg {
actor: self.name(),
title: self.title.clone(),
url: self.url.clone(),
outerWindowID: 0, //FIXME: this should probably be the pipeline id
consoleActor: self.console.clone(),
inspectorActor: self.inspector.clone(),
}
}
}

View File

@ -0,0 +1,203 @@
/* 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/. */
#![crate_name = "devtools"]
#![crate_type = "rlib"]
#![comment = "The Servo Parallel Browser Project"]
#![license = "MPL"]
#![feature(phase)]
#![feature(phase)]
#[phase(plugin, link)]
extern crate log;
/// An actor-based remote devtools server implementation. Only tested with nightly Firefox
/// versions at time of writing. Largely based on reverse-engineering of Firefox chrome
/// devtool logs and reading of [code](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/).
extern crate collections;
extern crate core;
extern crate devtools_traits;
extern crate debug;
extern crate std;
extern crate serialize;
extern crate sync;
extern crate servo_msg = "msg";
use actor::{Actor, ActorRegistry};
use actors::console::ConsoleActor;
use actors::inspector::InspectorActor;
use actors::root::RootActor;
use actors::tab::TabActor;
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};
use std::io::{Acceptor, Listener, EndOfFile, TimedOut};
use std::num;
use std::task::TaskBuilder;
use serialize::json;
use sync::{Arc, Mutex};
mod actor;
/// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/
mod actors {
pub mod console;
pub mod inspector;
pub mod root;
pub mod tab;
}
mod protocol;
/// Spin up a devtools server that listens for connections. Defaults to port 6000.
/// TODO: allow specifying a port
pub fn start_server() -> Sender<DevtoolsControlMsg> {
let (chan, port) = comm::channel();
TaskBuilder::new().named("devtools").spawn(proc() {
run_server(port)
});
chan
}
static POLL_TIMEOUT: u64 = 300;
fn run_server(port: Receiver<DevtoolsControlMsg>) {
let listener = TcpListener::bind("127.0.0.1", 6000);
// bind the listener to the specified address
let mut acceptor = listener.listen().unwrap();
acceptor.set_timeout(Some(POLL_TIMEOUT));
let mut registry = ActorRegistry::new();
let root = box RootActor {
tabs: vec!(),
};
registry.register(root);
registry.find::<RootActor>("root");
let actors = Arc::new(Mutex::new(registry));
/// Process the input from a single devtools client until EOF.
fn handle_client(actors: Arc<Mutex<ActorRegistry>>, mut stream: TcpStream) {
println!("connection established to {:?}", stream.peer_name().unwrap());
{
let mut actors = actors.lock();
let msg = actors.find::<RootActor>("root").encodable();
stream.write_json_packet(&msg);
}
// https://wiki.mozilla.org/Remote_Debugging_Protocol_Stream_Transport
// In short, each JSON packet is [ascii length]:[JSON data of given length]
// TODO: this really belongs in the protocol module.
'outer: loop {
let mut buffer = vec!();
loop {
let colon = ':' as u8;
match stream.read_byte() {
Ok(c) if c != colon => buffer.push(c as u8),
Ok(_) => {
let packet_len_str = String::from_utf8(buffer).unwrap();
let packet_len = num::from_str_radix(packet_len_str.as_slice(), 10).unwrap();
let packet_buf = stream.read_exact(packet_len).unwrap();
let packet = String::from_utf8(packet_buf).unwrap();
println!("{:s}", packet);
let json_packet = json::from_str(packet.as_slice()).unwrap();
actors.lock().handle_message(json_packet.as_object().unwrap(),
&mut stream);
break;
}
Err(ref e) if e.kind == EndOfFile => {
println!("\nEOF");
break 'outer;
},
_ => {
println!("\nconnection error");
break 'outer;
}
}
}
}
}
// We need separate actor representations for each script global that exists;
// clients can theoretically connect to multiple globals simultaneously.
// TODO: move this into the root or tab modules?
fn handle_new_global(actors: Arc<Mutex<ActorRegistry>>,
pipeline: PipelineId,
sender: Sender<DevtoolScriptControlMsg>) {
let mut actors = actors.lock();
//TODO: move all this actor creation into a constructor method on TabActor
let (tab, console, inspector) = {
let console = ConsoleActor {
name: actors.new_name("console"),
script_chan: sender.clone(),
pipeline: pipeline,
};
let inspector = InspectorActor {
name: actors.new_name("inspector"),
walker: RefCell::new(None),
pageStyle: RefCell::new(None),
highlighter: RefCell::new(None),
script_chan: sender,
pipeline: pipeline,
};
//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(),
};
let root = actors.find_mut::<RootActor>("root");
root.tabs.push(tab.name.clone());
(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,
// shut down existing ones at arbitrary times, and also watch for messages
// from multiple script tasks simultaneously. Polling for new connections
// for 300ms and then checking the receiver is not a good compromise
// (and makes Servo hang on exit if there's an open connection, no less).
//TODO: make constellation send ServerExitMsg on shutdown.
// accept connections and process them, spawning a new tasks for each one
for stream in acceptor.incoming() {
match stream {
Err(ref e) if e.kind == TimedOut => {
match port.try_recv() {
Ok(ServerExitMsg) | Err(Disconnected) => break,
Ok(NewGlobal(id, sender)) => handle_new_global(actors.clone(), id, sender),
Err(Empty) => acceptor.set_timeout(Some(POLL_TIMEOUT)),
}
}
Err(_e) => { /* connection failed */ }
Ok(stream) => {
let actors = actors.clone();
spawn(proc() {
// connection succeeded
handle_client(actors, stream.clone())
})
}
}
}
}

View File

@ -0,0 +1,22 @@
/* 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/. */
/// Low-level wire protocol implementation. Currently only supports [JSON packets](https://wiki.mozilla.org/Remote_Debugging_Protocol_Stream_Transport#JSON_Packets).
use serialize::{json, Encodable};
use std::io::{IoError, TcpStream};
pub trait JsonPacketSender {
fn write_json_packet<'a, T: Encodable<json::Encoder<'a>,IoError>>(&mut self, obj: &T);
}
impl JsonPacketSender for TcpStream {
fn write_json_packet<'a, T: Encodable<json::Encoder<'a>,IoError>>(&mut self, obj: &T) {
let s = json::encode(obj).replace("__type__", "type");
println!("<- {:s}", s);
self.write_str(s.len().to_string().as_slice()).unwrap();
self.write_u8(':' as u8).unwrap();
self.write_str(s.as_slice()).unwrap();
}
}

View File

@ -0,0 +1,11 @@
[package]
name = "devtools_traits"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "devtools_traits"
path = "lib.rs"
[dependencies.msg]
path = "../msg"

View File

@ -0,0 +1,81 @@
/* 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/. */
#![crate_name = "devtools_traits"]
#![crate_type = "rlib"]
#![comment = "The Servo Parallel Browser Project"]
#![license = "MPL"]
extern crate servo_msg = "msg";
/// This module contains shared types and messages for use by devtools/script.
/// The traits are here instead of in script so that the devtools crate can be
/// modified independently of the rest of Servo.
use servo_msg::constellation_msg::PipelineId;
pub type DevtoolsControlChan = Sender<DevtoolsControlMsg>;
pub type DevtoolsControlPort = Receiver<DevtoolScriptControlMsg>;
/// Messages to the instruct the devtools server to update its known actors/state
/// according to changes in the browser.
pub enum DevtoolsControlMsg {
NewGlobal(PipelineId, Sender<DevtoolScriptControlMsg>),
ServerExitMsg
}
/// Serialized JS return values
/// TODO: generalize this beyond the EvaluateJS message?
pub enum EvaluateJSReply {
VoidValue,
NullValue,
BooleanValue(bool),
NumberValue(f64),
StringValue(String),
ActorValue(String),
}
pub struct AttrInfo {
pub namespace: String,
pub name: String,
pub value: String,
}
pub struct NodeInfo {
pub uniqueId: String,
pub baseURI: String,
pub parent: String,
pub nodeType: uint,
pub namespaceURI: String,
pub nodeName: String,
pub numChildren: uint,
pub name: String,
pub publicId: String,
pub systemId: String,
pub attrs: Vec<AttrInfo>,
pub isDocumentElement: bool,
pub shortValue: String,
pub incompleteValue: bool,
}
/// Messages to process in a particular script task, as instructed by a devtools client.
pub enum DevtoolScriptControlMsg {
EvaluateJS(PipelineId, String, Sender<EvaluateJSReply>),
GetRootNode(PipelineId, Sender<NodeInfo>),
GetDocumentElement(PipelineId, Sender<NodeInfo>),
GetChildren(PipelineId, String, Sender<Vec<NodeInfo>>),
GetLayout(PipelineId, String, Sender<(f32, f32)>),
}
/// Messages to instruct devtools server to update its state relating to a particular
/// tab.
pub enum ScriptDevtoolControlMsg {
/// Report a new JS error message
ReportConsoleMsg(String),
}

View File

@ -24,6 +24,9 @@ path = "../net"
[dependencies.script_traits]
path = "../script_traits"
[dependencies.devtools_traits]
path = "../devtools_traits"
[dependencies.style]
path = "../style"

View File

@ -13,6 +13,8 @@ use dom::element::{Element, AttributeHandlers};
use dom::node::Node;
use dom::window::Window;
use dom::virtualmethods::vtable_for;
use devtools_traits::AttrInfo;
use servo_util::atom::Atom;
use servo_util::namespace;
use servo_util::namespace::Namespace;
@ -149,6 +151,7 @@ pub trait AttrHelpers {
fn set_value(&self, set_type: AttrSettingType, value: AttrValue);
fn value<'a>(&'a self) -> Ref<'a, AttrValue>;
fn local_name<'a>(&'a self) -> &'a Atom;
fn summarize(&self) -> AttrInfo;
}
impl<'a> AttrHelpers for JSRef<'a, Attr> {
@ -184,6 +187,14 @@ impl<'a> AttrHelpers for JSRef<'a, Attr> {
fn local_name<'a>(&'a self) -> &'a Atom {
&self.local_name
}
fn summarize(&self) -> AttrInfo {
AttrInfo {
namespace: self.namespace.to_str().to_string(),
name: self.Name(),
value: self.Value(),
}
}
}
pub trait AttrHelpersForLayout {

View File

@ -10,6 +10,7 @@ use dom::namednodemap::NamedNodeMap;
use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::ElementBinding;
use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods;
use dom::bindings::codegen::InheritTypes::{ElementDerived, NodeCast};
use dom::bindings::js::{JS, JSRef, Temporary, TemporaryPushable};
use dom::bindings::js::{OptionalSettable, OptionalRootable, Root};
@ -30,6 +31,7 @@ use dom::nodelist::NodeList;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use layout_interface::ContentChangedDocumentDamage;
use layout_interface::MatchSelectorsDocumentDamage;
use devtools_traits::AttrInfo;
use style::{matches, parse_selector_list_from_str};
use style;
use servo_util::atom::Atom;
@ -239,6 +241,7 @@ pub trait ElementHelpers {
fn html_element_in_html_document(&self) -> bool;
fn get_local_name<'a>(&'a self) -> &'a Atom;
fn get_namespace<'a>(&'a self) -> &'a Namespace;
fn summarize(&self) -> Vec<AttrInfo>;
}
impl<'a> ElementHelpers for JSRef<'a, Element> {
@ -254,6 +257,18 @@ impl<'a> ElementHelpers for JSRef<'a, Element> {
fn get_namespace<'a>(&'a self) -> &'a Namespace {
&self.deref().namespace
}
fn summarize(&self) -> Vec<AttrInfo> {
let attrs = self.Attributes().root();
let mut i = 0;
let mut summarized = vec!();
while i < attrs.Length() {
let attr = attrs.Item(i).unwrap().root();
summarized.push(attr.summarize());
i += 1;
}
summarized
}
}
pub trait AttributeHandlers {

View File

@ -9,7 +9,9 @@ use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods;
use dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast};
use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast, ElementDerived};
@ -36,7 +38,7 @@ use dom::element::{HTMLInputElementTypeId, HTMLSelectElementTypeId};
use dom::element::{HTMLTextAreaElementTypeId, HTMLOptGroupElementTypeId};
use dom::element::{HTMLOptionElementTypeId, HTMLFieldSetElementTypeId};
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::nodelist::{NodeList};
use dom::nodelist::NodeList;
use dom::processinginstruction::ProcessingInstruction;
use dom::text::Text;
use dom::virtualmethods::{VirtualMethods, vtable_for};
@ -45,6 +47,7 @@ use geom::rect::Rect;
use html::hubbub_html_parser::build_element_from_tag;
use layout_interface::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC,
LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress, UntrustedNodeAddress};
use devtools_traits::NodeInfo;
use servo_util::geometry::Au;
use servo_util::str::{DOMString, null_str_as_empty};
use style::{parse_selector_list_from_str, matches};
@ -59,6 +62,7 @@ use std::mem;
use style;
use style::ComputedValues;
use sync::Arc;
use uuid;
use serialize::{Encoder, Encodable};
@ -105,6 +109,8 @@ pub struct Node {
/// Must be sent back to the layout task to be destroyed when this
/// node is finalized.
pub layout_data: LayoutDataRef,
unique_id: RefCell<String>,
}
impl<S: Encoder<E>, E> Encodable<S, E> for LayoutDataRef {
@ -419,6 +425,9 @@ pub trait NodeHelpers<'m, 'n> {
fn query_selector_all(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>>;
fn remove_self(&self);
fn get_unique_id(&self) -> String;
fn summarize(&self) -> NodeInfo;
}
impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> {
@ -687,6 +696,48 @@ impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> {
None => ()
}
}
fn get_unique_id(&self) -> String {
self.unique_id.borrow().clone()
}
fn summarize(&self) -> NodeInfo {
if self.unique_id.borrow().is_empty() {
let mut unique_id = self.unique_id.borrow_mut();
*unique_id = uuid::Uuid::new_v4().to_simple_str();
}
NodeInfo {
uniqueId: self.unique_id.borrow().clone(),
baseURI: self.GetBaseURI().unwrap_or("".to_string()),
parent: self.GetParentNode().root().map(|node| node.get_unique_id()).unwrap_or("".to_string()),
nodeType: self.NodeType() as uint,
namespaceURI: "".to_string(), //FIXME
nodeName: self.NodeName(),
numChildren: self.ChildNodes().root().Length() as uint,
//FIXME doctype nodes only
name: "".to_string(),
publicId: "".to_string(),
systemId: "".to_string(),
attrs: if self.is_element() {
let elem: &JSRef<Element> = ElementCast::to_ref(self).unwrap();
elem.summarize()
} else {
vec!()
},
isDocumentElement:
self.owner_doc().root()
.GetDocumentElement()
.map(|elem| NodeCast::from_ref(&*elem.root()) == self)
.unwrap_or(false),
shortValue: self.GetNodeValue().unwrap_or("".to_string()), //FIXME: truncate
incompleteValue: false, //FIXME: reflect truncation
}
}
}
/// If the given untrusted node address represents a valid DOM node in the given runtime,
@ -991,6 +1042,8 @@ impl Node {
flags: Traceable::new(RefCell::new(NodeFlags::new(type_id))),
layout_data: LayoutDataRef::new(),
unique_id: RefCell::new("".to_string()),
}
}

View File

@ -16,6 +16,7 @@
extern crate log;
extern crate debug;
extern crate devtools_traits;
extern crate cssparser;
extern crate collections;
extern crate geom;
@ -39,6 +40,7 @@ extern crate style;
extern crate sync;
extern crate servo_msg = "msg";
extern crate url;
extern crate uuid;
pub mod cors;

View File

@ -5,7 +5,11 @@
//! The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing
//! and layout tasks.
use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, EventCast};
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, EventCast, ElementCast};
use dom::bindings::conversions;
use dom::bindings::conversions::{FromJSValConvertible, Empty};
use dom::bindings::global::Window;
use dom::bindings::js::{JS, JSRef, RootCollection, Temporary, OptionalSettable};
@ -31,6 +35,10 @@ use layout_interface::ContentChangedDocumentDamage;
use layout_interface;
use page::{Page, IterablePage, Frame};
use devtools_traits;
use devtools_traits::{DevtoolsControlChan, DevtoolsControlPort, NewGlobal, NodeInfo, GetRootNode};
use devtools_traits::{DevtoolScriptControlMsg, EvaluateJS, EvaluateJSReply, GetDocumentElement};
use devtools_traits::{GetChildren, GetLayout};
use script_traits::{CompositorEvent, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent};
use script_traits::{MouseMoveEvent, MouseUpEvent, ConstellationControlMsg, ScriptTaskFactory};
use script_traits::{ResizeMsg, AttachLayoutMsg, LoadMsg, SendEventMsg, ResizeInactiveMsg};
@ -157,6 +165,12 @@ pub struct ScriptTask {
/// A handle to the compositor for communicating ready state messages.
compositor: Box<ScriptListener>,
/// For providing instructions to an optional devtools server.
devtools_chan: Option<DevtoolsControlChan>,
/// For receiving commands from an optional devtools server. Will be ignored if
/// no such server exists.
devtools_port: DevtoolsControlPort,
/// The JavaScript runtime.
js_runtime: js::rust::rt,
/// The JSContext.
@ -240,6 +254,7 @@ impl ScriptTaskFactory for ScriptTask {
failure_msg: Failure,
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
devtools_chan: Option<DevtoolsControlChan>,
window_size: WindowSizeData) {
let ConstellationChan(const_chan) = constellation_chan.clone();
let (script_chan, script_port) = channel();
@ -255,6 +270,7 @@ impl ScriptTaskFactory for ScriptTask {
constellation_chan,
resource_task,
image_cache_task,
devtools_chan,
window_size);
let mut failsafe = ScriptMemoryFailsafe::new(&*script_task);
script_task.start();
@ -277,6 +293,7 @@ impl ScriptTask {
constellation_chan: ConstellationChan,
resource_task: ResourceTask,
img_cache_task: ImageCacheTask,
devtools_chan: Option<DevtoolsControlChan>,
window_size: WindowSizeData)
-> Rc<ScriptTask> {
let (js_runtime, js_context) = ScriptTask::new_rt_and_cx();
@ -299,6 +316,14 @@ impl ScriptTask {
resource_task.clone(),
constellation_chan.clone(),
js_context.clone());
// Notify devtools that a new script global exists.
//FIXME: Move this into handle_load after we create a window instead.
let (devtools_sender, devtools_receiver) = channel();
devtools_chan.as_ref().map(|chan| {
chan.send(NewGlobal(id, devtools_sender.clone()));
});
Rc::new(ScriptTask {
page: RefCell::new(Rc::new(page)),
@ -311,6 +336,8 @@ impl ScriptTask {
control_port: control_port,
constellation_chan: constellation_chan,
compositor: compositor,
devtools_chan: devtools_chan,
devtools_port: devtools_receiver,
js_runtime: js_runtime,
js_context: RefCell::new(Some(js_context)),
@ -392,6 +419,7 @@ impl ScriptTask {
enum MixedMessage {
FromConstellation(ConstellationControlMsg),
FromScript(ScriptMsg),
FromDevtools(DevtoolScriptControlMsg),
}
// Store new resizes, and gather all other events.
@ -402,20 +430,27 @@ impl ScriptTask {
let sel = Select::new();
let mut port1 = sel.handle(&self.port);
let mut port2 = sel.handle(&self.control_port);
let mut port3 = sel.handle(&self.devtools_port);
unsafe {
port1.add();
port2.add();
if self.devtools_chan.is_some() {
port3.add();
}
}
let ret = sel.wait();
if ret == port1.id() {
FromScript(self.port.recv())
} else if ret == port2.id() {
FromConstellation(self.control_port.recv())
} else if ret == port3.id() {
FromDevtools(self.devtools_port.recv())
} else {
fail!("unexpected select result")
}
};
// Squash any pending resize events in the queue.
loop {
match event {
// This has to be handled before the ResizeMsg below,
@ -434,9 +469,15 @@ impl ScriptTask {
}
}
// If any of our input sources has an event pending, we'll perform another iteration
// and check for more resize events. If there are no events pending, we'll move
// on and execute the sequential non-resize events we've seen.
match self.control_port.try_recv() {
Err(_) => match self.port.try_recv() {
Err(_) => break,
Err(_) => match self.devtools_port.try_recv() {
Err(_) => break,
Ok(ev) => event = FromDevtools(ev),
},
Ok(ev) => event = FromScript(ev),
},
Ok(ev) => event = FromConstellation(ev),
@ -463,12 +504,87 @@ impl ScriptTask {
FromScript(DOMMessage(..)) => fail!("unexpected message"),
FromScript(WorkerPostMessage(addr, data, nbytes)) => Worker::handle_message(addr, data, nbytes),
FromScript(WorkerRelease(addr)) => Worker::handle_release(addr),
FromDevtools(EvaluateJS(id, s, reply)) => self.handle_evaluate_js(id, s, reply),
FromDevtools(GetRootNode(id, reply)) => self.handle_get_root_node(id, reply),
FromDevtools(GetDocumentElement(id, reply)) => self.handle_get_document_element(id, reply),
FromDevtools(GetChildren(id, node_id, reply)) => self.handle_get_children(id, node_id, reply),
FromDevtools(GetLayout(id, node_id, reply)) => self.handle_get_layout(id, node_id, reply),
}
}
true
}
fn handle_evaluate_js(&self, pipeline: PipelineId, eval: String, reply: Sender<EvaluateJSReply>) {
let page = get_page(&*self.page.borrow(), pipeline);
let frame = page.frame();
let window = frame.get_ref().window.root();
let cx = window.get_cx();
let rval = window.evaluate_js_with_result(eval.as_slice());
reply.send(if rval.is_undefined() {
devtools_traits::VoidValue
} else if rval.is_boolean() {
devtools_traits::BooleanValue(rval.to_boolean())
} else if rval.is_double() {
devtools_traits::NumberValue(FromJSValConvertible::from_jsval(cx, rval, ()).unwrap())
} else if rval.is_string() {
//FIXME: use jsstring_to_str when jsval grows to_jsstring
devtools_traits::StringValue(FromJSValConvertible::from_jsval(cx, rval, conversions::Default).unwrap())
} else {
//FIXME: jsvals don't have an is_int32/is_number yet
assert!(rval.is_object_or_null());
fail!("object values unimplemented")
});
}
fn handle_get_root_node(&self, pipeline: PipelineId, reply: Sender<NodeInfo>) {
let page = get_page(&*self.page.borrow(), pipeline);
let frame = page.frame();
let document = frame.get_ref().document.root();
let node: &JSRef<Node> = NodeCast::from_ref(&*document);
reply.send(node.summarize());
}
fn handle_get_document_element(&self, pipeline: PipelineId, reply: Sender<NodeInfo>) {
let page = get_page(&*self.page.borrow(), pipeline);
let frame = page.frame();
let document = frame.get_ref().document.root();
let document_element = document.GetDocumentElement().root().unwrap();
let node: &JSRef<Node> = NodeCast::from_ref(&*document_element);
reply.send(node.summarize());
}
fn find_node_by_unique_id(&self, pipeline: PipelineId, node_id: String) -> Temporary<Node> {
let page = get_page(&*self.page.borrow(), pipeline);
let frame = page.frame();
let document = frame.get_ref().document.root();
let node: &JSRef<Node> = NodeCast::from_ref(&*document);
for candidate in node.traverse_preorder() {
if candidate.get_unique_id().as_slice() == node_id.as_slice() {
return Temporary::from_rooted(&candidate);
}
}
fail!("couldn't find node with unique id {:s}", node_id)
}
fn handle_get_children(&self, pipeline: PipelineId, node_id: String, reply: Sender<Vec<NodeInfo>>) {
let parent = self.find_node_by_unique_id(pipeline, node_id).root();
let children = parent.children().map(|child| child.summarize()).collect();
reply.send(children);
}
fn handle_get_layout(&self, pipeline: PipelineId, node_id: String, reply: Sender<(f32, f32)>) {
let node = self.find_node_by_unique_id(pipeline, node_id).root();
let elem: &JSRef<Element> = ElementCast::to_ref(&*node).expect("should be getting layout of element");
let rect = elem.GetBoundingClientRect().root();
reply.send((rect.Width(), rect.Height()));
}
fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {
debug!("Script: new layout: {:?}", new_layout_info);
let NewLayoutInfo {

View File

@ -13,6 +13,9 @@ path = "../msg"
[dependencies.net]
path = "../net"
[dependencies.devtools_traits]
path = "../devtools_traits"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"

View File

@ -7,6 +7,7 @@
#![deny(unused_imports, unused_variable)]
extern crate devtools_traits;
extern crate geom;
extern crate servo_msg = "msg";
extern crate servo_net = "net";
@ -16,9 +17,10 @@ extern crate serialize;
// This module contains traits in script used generically
// in the rest of Servo.
// The traits are here instead of in layout so
// The traits are here instead of in script so
// that these modules won't have to depend on script.
use devtools_traits::DevtoolsControlChan;
use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, WindowSizeData};
use servo_msg::constellation_msg::SubpageId;
use servo_msg::compositor_msg::ScriptListener;
@ -91,6 +93,7 @@ pub trait ScriptTaskFactory {
failure_msg: Failure,
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
devtools_chan: Option<DevtoolsControlChan>,
window_size: WindowSizeData);
fn create_layout_channel(_phantom: Option<&mut Self>) -> OpaqueScriptLayoutChannel;
fn clone_layout_channel(_phantom: Option<&mut Self>, pair: &OpaqueScriptLayoutChannel) -> Box<Any+Send>;

View File

@ -83,6 +83,9 @@ pub struct Opts {
/// for debugging purposes. Settings this implies sequential layout
/// and render.
pub trace_layout: bool,
/// True if we should start a server to listen to remote Firefox devtools connections.
pub devtools_server: bool,
}
fn print_usage(app: &str, opts: &[getopts::OptGroup]) {
@ -117,6 +120,7 @@ pub fn from_cmdline_args(args: &[String]) -> Option<Opts> {
getopts::optflag("", "show-debug-borders", "Show debugging borders on layers and tiles."),
getopts::optflag("", "disable-text-aa", "Disable antialiasing for text rendering."),
getopts::optflag("", "trace-layout", "Write layout trace to external file for debugging."),
getopts::optflag("", "devtools", "Start remote devtools server"),
getopts::optflag("h", "help", "Print this message")
);
@ -217,6 +221,7 @@ pub fn from_cmdline_args(args: &[String]) -> Option<Opts> {
show_debug_borders: opt_match.opt_present("show-debug-borders"),
enable_text_antialiasing: !opt_match.opt_present("disable-text-aa"),
trace_layout: trace_layout,
devtools_server: opt_match.opt_present("devtools"),
})
}

View File

@ -32,6 +32,9 @@ path = "../../components/util"
[dependencies.style]
path = "../../components/style"
[dependencies.devtools]
path = "../../components/devtools"
[dependencies.azure]
git = "https://github.com/servo/rust-azure"

View File

@ -67,6 +67,7 @@ pub extern "C" fn cef_run_message_loop() {
show_debug_borders: false,
enable_text_antialiasing: true,
trace_layout: false,
devtools_server: false,
};
native::start(0, 0 as *const *const u8, proc() {
servo::run(opts);

View File

@ -15,6 +15,7 @@ extern crate log;
extern crate debug;
extern crate compositing;
extern crate devtools;
extern crate rustuv;
extern crate servo_net = "net";
extern crate servo_msg = "msg";
@ -94,6 +95,11 @@ pub fn run(opts: opts::Opts) {
let (compositor_port, compositor_chan) = CompositorChan::new();
let time_profiler_chan = TimeProfiler::create(opts.time_profiler_period);
let memory_profiler_chan = MemoryProfiler::create(opts.memory_profiler_period);
let devtools_chan = if opts.devtools_server {
Some(devtools::start_server())
} else {
None
};
let opts_clone = opts.clone();
let time_profiler_chan_clone = time_profiler_chan.clone();
@ -121,7 +127,8 @@ pub fn run(opts: opts::Opts) {
resource_task,
image_cache_task,
font_cache_task,
time_profiler_chan_clone);
time_profiler_chan_clone,
devtools_chan);
// Send the URL command to the constellation.
let cwd = os::getcwd();