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