aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosh Matthews <josh@joshmatthews.net>2014-09-19 09:15:03 -0400
committerJosh Matthews <josh@joshmatthews.net>2014-09-19 09:15:03 -0400
commitb82c0dced08ccda8c3c7f35643c3891bc45b058c (patch)
tree4b392e6e26e6d2d4433875cbb51c90237e351f70
parent2652d223f50d53ee5a8a07ff4a3d6a25b510d1f1 (diff)
parentfae7ce3c1dbcbf90460c3fba683c162b6c742cc7 (diff)
downloadservo-b82c0dced08ccda8c3c7f35643c3891bc45b058c.tar.gz
servo-b82c0dced08ccda8c3c7f35643c3891bc45b058c.zip
Merge pull request #3172 from jdm/devtools
Dump initial prototype of devtools server into the build. Expect lies if...
-rw-r--r--Cargo.lock19
-rw-r--r--components/compositing/Cargo.toml6
-rw-r--r--components/compositing/constellation.rs7
-rw-r--r--components/compositing/lib.rs1
-rw-r--r--components/compositing/pipeline.rs3
-rw-r--r--components/devtools/Cargo.toml14
-rw-r--r--components/devtools/actor.rs171
-rw-r--r--components/devtools/actors/console.rs284
-rw-r--r--components/devtools/actors/inspector.rs516
-rw-r--r--components/devtools/actors/root.rs99
-rw-r--r--components/devtools/actors/tab.rs136
-rw-r--r--components/devtools/lib.rs203
-rw-r--r--components/devtools/protocol.rs22
-rw-r--r--components/devtools_traits/Cargo.toml11
-rw-r--r--components/devtools_traits/lib.rs81
-rw-r--r--components/script/Cargo.toml3
-rw-r--r--components/script/dom/attr.rs11
-rw-r--r--components/script/dom/element.rs15
-rw-r--r--components/script/dom/node.rs55
-rw-r--r--components/script/lib.rs2
-rw-r--r--components/script/script_task.rs120
-rw-r--r--components/script_traits/Cargo.toml3
-rw-r--r--components/script_traits/lib.rs5
-rw-r--r--components/util/opts.rs5
-rw-r--r--ports/cef/Cargo.toml3
-rw-r--r--ports/cef/core.rs1
-rw-r--r--src/lib.rs9
27 files changed, 1799 insertions, 6 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a7baeb33a8e..0252ff2c591 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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)",
@@ -106,6 +108,21 @@ dependencies = [
]
[[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"
source = "git+https://github.com/servo/rust-egl#48b85e30d557ab2ee536730a73dd86a8160d618b"
@@ -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",
diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml
index d9b6adc57d2..e306ae8fd94 100644
--- a/components/compositing/Cargo.toml
+++ b/components/compositing/Cargo.toml
@@ -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"
diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs
index add70aaf39c..70b7e387773 100644
--- a/components/compositing/constellation.rs
+++ b/components/compositing/constellation.rs
@@ -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(),
diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs
index f7644e8fc7f..83f9a9b61d2 100644
--- a/components/compositing/lib.rs
+++ b/components/compositing/lib.rs
@@ -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"))]
diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs
index 8dd8c1f0de7..41ae22b13f7 100644
--- a/components/compositing/pipeline.rs
+++ b/components/compositing/pipeline.rs
@@ -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)
}
diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml
new file mode 100644
index 00000000000..2984f9daf61
--- /dev/null
+++ b/components/devtools/Cargo.toml
@@ -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"
diff --git a/components/devtools/actor.rs b/components/devtools/actor.rs
new file mode 100644
index 00000000000..fae6c8864cf
--- /dev/null
+++ b/components/devtools/actor.rs
@@ -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();
+ }
+}
diff --git a/components/devtools/actors/console.rs b/components/devtools/actors/console.rs
new file mode 100644
index 00000000000..c58d7f6373f
--- /dev/null
+++ b/components/devtools/actors/console.rs
@@ -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
+ }
+ }
+}
diff --git a/components/devtools/actors/inspector.rs b/components/devtools/actors/inspector.rs
new file mode 100644
index 00000000000..5d401e4ea7a
--- /dev/null
+++ b/components/devtools/actors/inspector.rs
@@ -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,
+ }
+ }
+}
diff --git a/components/devtools/actors/root.rs b/components/devtools/actors/root.rs
new file mode 100644
index 00000000000..8ebd79e4016
--- /dev/null
+++ b/components/devtools/actors/root.rs
@@ -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()),
+ },
+ }
+ }
+}
diff --git a/components/devtools/actors/tab.rs b/components/devtools/actors/tab.rs
new file mode 100644
index 00000000000..c9cc3481264
--- /dev/null
+++ b/components/devtools/actors/tab.rs
@@ -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(),
+ }
+ }
+}
diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs
new file mode 100644
index 00000000000..71fd82f5369
--- /dev/null
+++ b/components/devtools/lib.rs
@@ -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())
+ })
+ }
+ }
+ }
+}
diff --git a/components/devtools/protocol.rs b/components/devtools/protocol.rs
new file mode 100644
index 00000000000..728e593040e
--- /dev/null
+++ b/components/devtools/protocol.rs
@@ -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();
+ }
+}
diff --git a/components/devtools_traits/Cargo.toml b/components/devtools_traits/Cargo.toml
new file mode 100644
index 00000000000..c3ea63b06fa
--- /dev/null
+++ b/components/devtools_traits/Cargo.toml
@@ -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"
diff --git a/components/devtools_traits/lib.rs b/components/devtools_traits/lib.rs
new file mode 100644
index 00000000000..6bba96d81b3
--- /dev/null
+++ b/components/devtools_traits/lib.rs
@@ -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),
+}
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
index 1748a57956d..8bf75b57b20 100644
--- a/components/script/Cargo.toml
+++ b/components/script/Cargo.toml
@@ -24,6 +24,9 @@ path = "../net"
[dependencies.script_traits]
path = "../script_traits"
+[dependencies.devtools_traits]
+path = "../devtools_traits"
+
[dependencies.style]
path = "../style"
diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs
index be419eb2a61..61f520821a1 100644
--- a/components/script/dom/attr.rs
+++ b/components/script/dom/attr.rs
@@ -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 {
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
index 953036788f4..ab61fc47136 100644
--- a/components/script/dom/element.rs
+++ b/components/script/dom/element.rs
@@ -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 {
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index e72e95052a6..608fdcad118 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -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()),
}
}
diff --git a/components/script/lib.rs b/components/script/lib.rs
index 9f3effaa368..4318f4b3f21 100644
--- a/components/script/lib.rs
+++ b/components/script/lib.rs
@@ -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;
diff --git a/components/script/script_task.rs b/components/script/script_task.rs
index a1f2cd27b22..1dd63abeadd 100644
--- a/components/script/script_task.rs
+++ b/components/script/script_task.rs
@@ -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 {
diff --git a/components/script_traits/Cargo.toml b/components/script_traits/Cargo.toml
index 3bc1beda99f..4183b3909de 100644
--- a/components/script_traits/Cargo.toml
+++ b/components/script_traits/Cargo.toml
@@ -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"
diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs
index 4cead079ff1..fa85bb7c1bd 100644
--- a/components/script_traits/lib.rs
+++ b/components/script_traits/lib.rs
@@ -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>;
diff --git a/components/util/opts.rs b/components/util/opts.rs
index 40da68632d8..2145e83af81 100644
--- a/components/util/opts.rs
+++ b/components/util/opts.rs
@@ -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"),
})
}
diff --git a/ports/cef/Cargo.toml b/ports/cef/Cargo.toml
index d88ceb24405..5063307fe3f 100644
--- a/ports/cef/Cargo.toml
+++ b/ports/cef/Cargo.toml
@@ -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"
diff --git a/ports/cef/core.rs b/ports/cef/core.rs
index 9ff237f2d82..b6415d27f12 100644
--- a/ports/cef/core.rs
+++ b/ports/cef/core.rs
@@ -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);
diff --git a/src/lib.rs b/src/lib.rs
index a00d935ade3..8a8fb3217e8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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();