mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
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:
parent
22d49834af
commit
d3fc0a6b43
19
servo/Cargo.lock
generated
19
servo/Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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"))]
|
||||
|
@ -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)
|
||||
}
|
||||
|
14
servo/components/devtools/Cargo.toml
Normal file
14
servo/components/devtools/Cargo.toml
Normal 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"
|
171
servo/components/devtools/actor.rs
Normal file
171
servo/components/devtools/actor.rs
Normal 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();
|
||||
}
|
||||
}
|
284
servo/components/devtools/actors/console.rs
Normal file
284
servo/components/devtools/actors/console.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
516
servo/components/devtools/actors/inspector.rs
Normal file
516
servo/components/devtools/actors/inspector.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
99
servo/components/devtools/actors/root.rs
Normal file
99
servo/components/devtools/actors/root.rs
Normal 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()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
136
servo/components/devtools/actors/tab.rs
Normal file
136
servo/components/devtools/actors/tab.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
203
servo/components/devtools/lib.rs
Normal file
203
servo/components/devtools/lib.rs
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
servo/components/devtools/protocol.rs
Normal file
22
servo/components/devtools/protocol.rs
Normal 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();
|
||||
}
|
||||
}
|
11
servo/components/devtools_traits/Cargo.toml
Normal file
11
servo/components/devtools_traits/Cargo.toml
Normal 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"
|
81
servo/components/devtools_traits/lib.rs
Normal file
81
servo/components/devtools_traits/lib.rs
Normal 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),
|
||||
}
|
@ -24,6 +24,9 @@ path = "../net"
|
||||
[dependencies.script_traits]
|
||||
path = "../script_traits"
|
||||
|
||||
[dependencies.devtools_traits]
|
||||
path = "../devtools_traits"
|
||||
|
||||
[dependencies.style]
|
||||
path = "../style"
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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>;
|
||||
|
@ -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"),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user