diff --git a/Cargo.lock b/Cargo.lock index 43c64bcd7e2..0252ff2c591 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,7 +3,6 @@ name = "servo" version = "0.0.1" dependencies = [ "compositing 0.0.1", - "devtools 0.0.1", "gfx 0.0.1", "layout 0.0.1", "msg 0.0.1", @@ -113,11 +112,15 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 0561ad480f0..2472f99be99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,8 +46,5 @@ path = "components/layout" [dependencies.gfx] path = "components/gfx" -[dependencies.devtools] -path = "components/devtools" - [dependencies.url] git = "https://github.com/servo/rust-url" diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index d27e6bfed5a..2984f9daf61 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -6,7 +6,9 @@ authors = ["The Servo Project Developers"] [lib] name = "devtools" path = "lib.rs" -crate-type = ["dylib"] [dependencies.devtools_traits] path = "../devtools_traits" + +[dependencies.msg] +path = "../msg" diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 991ddeb5376..d00544026e9 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -21,19 +21,26 @@ extern crate debug; extern crate std; extern crate serialize; extern crate sync; +extern crate servo_msg = "msg"; -use devtools_traits::{ServerExitMsg, DevtoolsControlMsg, NewGlobal}; +use devtools_traits::{ServerExitMsg, DevtoolsControlMsg, NewGlobal, DevtoolScriptControlMsg}; +use devtools_traits::{EvaluateJS, NullValue, VoidValue, NumberValue, StringValue, BooleanValue}; +use devtools_traits::ActorValue; +use servo_msg::constellation_msg::PipelineId; use collections::TreeMap; -use std::any::{Any, AnyRefExt}; +use std::any::{Any, AnyRefExt, AnyMutRefExt}; use std::collections::hashmap::HashMap; use std::comm; use std::comm::{Disconnected, Empty}; use std::io::{TcpListener, TcpStream}; use std::io::{Acceptor, Listener, EndOfFile, IoError, TimedOut}; +use std::mem::{transmute, transmute_copy}; use std::num; +use std::raw::TraitObject; use std::task::TaskBuilder; use serialize::{json, Encodable}; +use serialize::json::ToJson; use sync::{Arc, Mutex}; #[deriving(Encodable)] @@ -49,6 +56,7 @@ struct RootActorMsg { } struct RootActor { + next: u32, tabs: Vec, } @@ -74,6 +82,12 @@ struct TabActor { url: String, } +struct ConsoleActor { + name: String, + pipeline: PipelineId, + script_chan: Sender, +} + #[deriving(Encodable)] struct ListTabsReply { from: String, @@ -82,16 +96,14 @@ struct ListTabsReply { } #[deriving(Encodable)] -struct TabTraits { - reconfigure: bool, -} +struct TabTraits; #[deriving(Encodable)] struct TabAttachedReply { from: String, __type__: String, threadActor: String, - cacheEnabled: bool, + cacheDisabled: bool, javascriptEnabled: bool, traits: TabTraits, } @@ -102,11 +114,18 @@ struct TabDetachedReply { __type__: String, } + +#[deriving(Encodable)] +struct StartedListenersTraits { + customNetworkRequest: bool, +} + #[deriving(Encodable)] struct StartedListenersReply { from: String, nativeConsoleAPI: bool, startedListeners: Vec, + traits: StartedListenersTraits, } #[deriving(Encodable)] @@ -186,13 +205,24 @@ impl ActorRegistry { } } - fn register(&mut self, actor: Box) { + fn register(&mut self, actor: Box) { + /*{ + let actor2: &Actor+Send+Sized = actor; + assert!((actor2 as &Any).is::()); + };*/ self.actors.insert(actor.name().to_string(), actor); } fn find<'a, T: 'static>(&'a self, name: &str) -> &'a T { - let actor: &Actor+Send+Sized = *self.actors.find(&name.to_string()).unwrap(); - (actor as &Any).downcast_ref::().unwrap() + /*let actor: &Actor+Send+Sized = *self.actors.find(&name.to_string()).unwrap(); + (actor as &Any).downcast_ref::().unwrap()*/ + self.actors.find(&name.to_string()).unwrap().as_ref::().unwrap() + } + + fn find_mut<'a, T: 'static>(&'a mut self, name: &str) -> &'a mut T { + /*let actor: &mut Actor+Send+Sized = *self.actors.find_mut(&name.to_string()).unwrap(); + (actor as &mut Any).downcast_mut::().unwrap()*/ + self.actors.find_mut(&name.to_string()).unwrap().downcast_mut::().unwrap() } fn handle_message(&self, msg: &json::Object, stream: &mut TcpStream) { @@ -220,6 +250,45 @@ trait Actor: Any { fn name(&self) -> String; } +impl<'a> AnyMutRefExt<'a> for &'a mut Actor { + fn downcast_mut(self) -> Option<&'a mut T> { + if self.is::() { + 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(self) -> bool { + /*let t = TypeId::of::(); + let boxed = self.get_type_id(); + t == boxed*/ + true + } + + fn downcast_ref(self) -> Option<&'a T> { + if self.is::() { + 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 Actor for RootActor { fn name(&self) -> String { "root".to_string() @@ -268,6 +337,11 @@ impl RootActor { } } +#[deriving(Encodable)] +struct ReconfigureReply { + from: String +} + impl Actor for TabActor { fn name(&self) -> String { self.name.clone() @@ -276,19 +350,21 @@ impl Actor for TabActor { fn handle_message(&self, _registry: &ActorRegistry, msg_type: &String, - msg: &json::Object, + _msg: &json::Object, stream: &mut TcpStream) -> bool { match msg_type.as_slice() { + "reconfigure" => { + stream.write_json_packet(&ReconfigureReply { from: self.name() }); + true + } "attach" => { let msg = TabAttachedReply { from: self.name(), __type__: "tabAttached".to_string(), threadActor: self.name(), - cacheEnabled: false, + cacheDisabled: false, javascriptEnabled: true, - traits: TabTraits { - reconfigure: true, - }, + traits: TabTraits, }; stream.write_json_packet(&msg); true @@ -301,16 +377,34 @@ impl Actor for TabActor { stream.write_json_packet(&msg); true } - "startListeners" => { - let msg = StartedListenersReply { - from: self.name(), - nativeConsoleAPI: true, - startedListeners: - vec!("PageError".to_string(), "ConsoleAPI".to_string()), - }; - stream.write_json_packet(&msg); - true - } + _ => false + } + } +} + +impl TabActor { + fn encodable(&self) -> TabActorMsg { + TabActorMsg { + actor: self.name(), + title: self.title.clone(), + url: self.url.clone(), + outerWindowID: 0, + consoleActor: "console0".to_string(), + } + } +} + +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!(); @@ -356,6 +450,20 @@ impl Actor for TabActor { stream.write_json_packet(&msg); true } + "startListeners" => { + let msg = StartedListenersReply { + from: self.name(), + nativeConsoleAPI: true, + startedListeners: + vec!("PageError".to_string(), "ConsoleAPI".to_string(), + "NetworkActivity".to_string(), "FileActivity".to_string()), + traits: StartedListenersTraits { + customNetworkRequest: true, + } + }; + stream.write_json_packet(&msg); + true + } "stopListeners" => { let msg = StopListenersReply { from: self.name(), @@ -380,10 +488,60 @@ impl Actor for TabActor { 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)); + + 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) => { + 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) + } + }; + let msg = EvaluateJSReply { from: self.name(), - input: msg.find(&"text".to_string()).unwrap().as_string().unwrap().to_string(), - result: json::Object(TreeMap::new()), + input: input, + result: result, timestamp: 0, exception: json::Object(TreeMap::new()), exceptionMessage: "".to_string(), @@ -397,18 +555,6 @@ impl Actor for TabActor { } } -impl TabActor { - fn encodable(&self) -> TabActorMsg { - TabActorMsg { - actor: self.name(), - title: self.title.clone(), - url: self.url.clone(), - outerWindowID: 0, - consoleActor: self.name(), - } - } -} - trait JsonPacketSender { fn write_json_packet<'a, T: Encodable,IoError>>(&mut self, obj: &T); } @@ -442,18 +588,13 @@ fn run_server(port: Receiver) { let mut registry = ActorRegistry::new(); - let tab = box TabActor { - name: "tab1".to_string(), - title: "Performing Layout".to_string(), - url: "about-mozilla.html".to_string(), - }; - let root = box RootActor { - tabs: vec!(tab.name().to_string()), + next: 0, + tabs: vec!(), }; - registry.register(tab); - registry.register(root); + registry.register::(root); + registry.find::("root"); let actors = Arc::new(Mutex::new(registry)); @@ -496,13 +637,41 @@ fn run_server(port: Receiver) { } } + fn handle_new_global(actors: Arc>, + pipeline: PipelineId, + sender: Sender) { + { + let mut actors = actors.lock(); + + let (tab, console) = { + let root = actors.find_mut::("root"); + + let tab = TabActor { + name: format!("tab{}", root.next), + title: "".to_string(), + url: "about:blank".to_string(), + }; + let console = ConsoleActor { + name: format!("console{}", root.next), + script_chan: sender, + pipeline: pipeline, + }; + root.next += 1; + root.tabs.push(tab.name.clone()); + (tab, console) + }; + actors.register::(box tab); + actors.register::(box console); + } + } + // 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(_)) => { /*TODO*/ }, + Ok(NewGlobal(id, sender)) => handle_new_global(actors.clone(), id, sender), Err(Empty) => acceptor.set_timeout(Some(POLL_TIMEOUT)), } } diff --git a/components/devtools_traits/Cargo.toml b/components/devtools_traits/Cargo.toml index ee897936dbb..c3ea63b06fa 100644 --- a/components/devtools_traits/Cargo.toml +++ b/components/devtools_traits/Cargo.toml @@ -6,3 +6,6 @@ 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 index 823acd0ab8f..08a061708ee 100644 --- a/components/devtools_traits/lib.rs +++ b/components/devtools_traits/lib.rs @@ -8,24 +8,29 @@ #![comment = "The Servo Parallel Browser Project"] #![license = "MPL"] +extern crate servo_msg = "msg"; + +use servo_msg::constellation_msg::PipelineId; + pub type DevtoolsControlChan = Sender; pub type DevtoolsControlPort = Receiver; pub enum DevtoolsControlMsg { - NewGlobal(Sender), + NewGlobal(PipelineId, Sender), ServerExitMsg } pub enum EvaluateJSReply { VoidValue, NullValue, + BooleanValue(bool), NumberValue(f64), StringValue(String), ActorValue(String), } pub enum DevtoolScriptControlMsg { - EvaluateJS(String, Sender), + EvaluateJS(PipelineId, String, Sender), } pub enum ScriptDevtoolControlMsg { diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 20f5eb3ded5..02fa3d10921 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -6,6 +6,7 @@ //! and layout tasks. use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, EventCast}; +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,8 +32,9 @@ use layout_interface::ContentChangedDocumentDamage; use layout_interface; use page::{Page, IterablePage, Frame}; +use devtools_traits; use devtools_traits::{DevtoolsControlChan, DevtoolsControlPort, NewGlobal}; -use devtools_traits::{DevtoolScriptControlMsg, EvaluateJS}; +use devtools_traits::{DevtoolScriptControlMsg, EvaluateJS, EvaluateJSReply}; use script_traits::{CompositorEvent, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent}; use script_traits::{MouseMoveEvent, MouseUpEvent, ConstellationControlMsg, ScriptTaskFactory}; use script_traits::{ResizeMsg, AttachLayoutMsg, LoadMsg, SendEventMsg, ResizeInactiveMsg}; @@ -315,7 +317,7 @@ impl ScriptTask { //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(devtools_sender.clone())); + chan.send(NewGlobal(id, devtools_sender.clone())); }); Rc::new(ScriptTask { @@ -496,13 +498,36 @@ 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(_s, _reply)) => {/*TODO*/} + FromDevtools(EvaluateJS(id, s, reply)) => self.handle_evaluate_js(id, s, reply), } } true } + fn handle_evaluate_js(&self, pipeline: PipelineId, eval: String, reply: Sender) { + 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_new_layout(&self, new_layout_info: NewLayoutInfo) { debug!("Script: new layout: {:?}", new_layout_info); let NewLayoutInfo {