gecko-dev/remote/startup/handler.rs

250 строки
8.0 KiB
Rust

// 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/.
use std::cell::RefCell;
use std::ffi::{CStr, CString, NulError};
use std::slice;
use libc::c_char;
use log::*;
use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_ILLEGAL_VALUE, NS_ERROR_INVALID_ARG, NS_OK};
use nsstring::{nsACString, nsCString, nsString};
use xpcom::interfaces::{nsICommandLine, nsICommandLineHandler, nsIObserverService, nsISupports};
use xpcom::{xpcom, xpcom_method, RefPtr};
use crate::{
RemoteAgent,
RemoteAgentError::{self, *},
RemoteAgentResult, DEFAULT_HOST, DEFAULT_PORT,
};
macro_rules! fatalln {
($($arg:tt)*) => ({
let p = prog().unwrap_or("gecko".to_string());
eprintln!("{}: {}", p, format_args!($($arg)*));
panic!();
})
}
#[no_mangle]
pub unsafe extern "C" fn new_remote_agent_handler(result: *mut *const nsICommandLineHandler) {
let handler: RefPtr<RemoteAgentHandler> = RemoteAgentHandler::new().unwrap();
RefPtr::new(handler.coerce::<nsICommandLineHandler>()).forget(&mut *result);
}
#[derive(xpcom)]
#[xpimplements(nsICommandLineHandler)]
#[xpimplements(nsIObserver)]
#[refcnt = "atomic"]
struct InitRemoteAgentHandler {
agent: RemoteAgent,
observer: RefPtr<nsIObserverService>,
address: RefCell<String>,
}
impl RemoteAgentHandler {
pub fn new() -> Result<RefPtr<Self>, RemoteAgentError> {
let agent = RemoteAgent::get()?;
let observer = xpcom::services::get_ObserverService().ok_or(Unavailable)?;
Ok(Self::allocate(InitRemoteAgentHandler {
agent,
observer,
address: RefCell::new(String::new()),
}))
}
xpcom_method!(handle => Handle(command_line: *const nsICommandLine));
fn handle(&self, command_line: &nsICommandLine) -> Result<(), nsresult> {
match self.handle_inner(&command_line) {
Ok(_) => Ok(()),
Err(err) => fatalln!("{}", err),
}
}
fn handle_inner(&self, command_line: &nsICommandLine) -> RemoteAgentResult<()> {
let flags = CommandLine::new(command_line);
let remote_debugger = if flags.present("remote-debugger") {
Some(flags.opt_str("remote-debugger")?)
} else {
None
};
let remote_debugging_port = if flags.present("remote-debugging-port") {
Some(flags.opt_u16("remote-debugging-port")?)
} else {
None
};
let addr = match (remote_debugger, remote_debugging_port) {
(Some(_), Some(_)) => return Err(FlagConflict),
(None, None) => return Ok(()),
// --remote-debugger [<host>][:<port>]
(Some(Some(spec)), _) => spec,
(Some(None), _) => format!("{}:{}", DEFAULT_HOST, DEFAULT_PORT),
// --remote-debugging-port <port>
(None, Some(Some(port))) => format!("{}:{}", DEFAULT_HOST, port),
(None, Some(None)) => return Err(MissingPort),
};
*self.address.borrow_mut() = addr.to_string();
// When remote-startup-requested fires, it takes care of
// asking the remote agent to listen for incoming connections.
// Because the remote agent starts asynchronously, we wait
// until we receive remote-listening before we declare to the
// world that we are ready to accept connections.
self.add_observer("remote-listening")?;
self.add_observer("remote-startup-requested")?;
Ok(())
}
fn add_observer(&self, topic: &str) -> RemoteAgentResult<()> {
let topic = CString::new(topic).unwrap();
unsafe {
self.observer
.AddObserver(self.coerce(), topic.as_ptr(), false)
}
.to_result()?;
Ok(())
}
xpcom_method!(help_info => GetHelpInfo() -> nsACString);
fn help_info(&self) -> Result<nsCString, nsresult> {
let help = r#" --remote-debugger [<host>][:<port>]
--remote-debugging-port <port> Start the Firefox remote agent, which is
a low-level debugging interface based on the CDP protocol.
Defaults to listen on localhost:9222.
"#;
Ok(nsCString::from(help))
}
xpcom_method!(observe => Observe(_subject: *const nsISupports, topic: string, data: wstring));
fn observe(
&self,
_subject: *const nsISupports,
topic: string,
data: wstring,
) -> Result<(), nsresult> {
let topic = unsafe { CStr::from_ptr(topic) }.to_str().unwrap();
match topic {
"remote-startup-requested" => {
if let Err(err) = self.agent.listen(&self.address.borrow()) {
fatalln!("unable to start remote agent: {}", err);
}
}
"remote-listening" => {
let url = unsafe { wstring_to_cstring(data) }.map_err(|_| NS_ERROR_FAILURE)?;
eprintln!("DevTools listening on {}", url.to_string_lossy());
}
s => warn!("unknown system notification: {}", s),
}
Ok(())
}
}
// Rust wrapper for nsICommandLine.
struct CommandLine<'a> {
inner: &'a nsICommandLine,
}
impl<'a> CommandLine<'a> {
const CASE_SENSITIVE: bool = true;
fn new(inner: &'a nsICommandLine) -> Self {
Self { inner }
}
fn position(&self, name: &str) -> i32 {
let flag = nsString::from(name);
let mut result: i32 = 0;
unsafe {
self.inner
.FindFlag(&*flag, Self::CASE_SENSITIVE, &mut result)
}
.to_result()
.map_err(|err| error!("FindFlag: {}", err))
.unwrap();
result
}
fn present(&self, name: &str) -> bool {
self.position(name) >= 0
}
// nsICommandLine.handleFlagWithParam has the following possible return values:
//
// - an AString value representing the argument value if it exists
// - NS_ERROR_INVALID_ARG if the flag was defined, but without a value
// - a null pointer if the flag was not defined
// - possibly any other NS exception
//
// This means we need to treat NS_ERROR_INVALID_ARG with special care
// because --remote-debugger can be used both with and without a value.
fn opt_str(&self, name: &str) -> RemoteAgentResult<Option<String>> {
if self.present(name) {
let flag = nsString::from(name);
let mut val = nsString::new();
let result = unsafe {
self.inner
.HandleFlagWithParam(&*flag, Self::CASE_SENSITIVE, &mut *val)
}
.to_result();
match result {
Ok(_) => Ok(Some(val.to_string())),
Err(NS_ERROR_INVALID_ARG) => Ok(None),
Err(err) => Err(RemoteAgentError::XpCom(err)),
}
} else {
Err(RemoteAgentError::XpCom(NS_ERROR_ILLEGAL_VALUE))
}
}
fn opt_u16(&self, name: &str) -> RemoteAgentResult<Option<u16>> {
Ok(if let Some(s) = self.opt_str(name)? {
Some(s.parse()?)
} else {
None
})
}
}
fn prog() -> Option<String> {
std::env::current_exe()
.ok()?
.file_name()?
.to_str()?
.to_owned()
.into()
}
// Arcane XPIDL types for raw character pointers
// to ASCII (7-bit) and UTF-16 strings, respectively.
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Guide/Internal_strings#IDL
#[allow(non_camel_case_types)]
type string = *const c_char;
#[allow(non_camel_case_types)]
type wstring = *const i16;
// Convert wstring to a CString (via nsCString's UTF-16 to UTF-8 conversion).
// But first, say three Hail Marys.
unsafe fn wstring_to_cstring(ws: wstring) -> Result<CString, NulError> {
let mut len: usize = 0;
while (*(ws.offset(len as isize))) != 0 {
len += 1;
}
let ss = slice::from_raw_parts(ws as *const u16, len);
let mut s = nsCString::new();
s.assign_utf16_to_utf8(ss);
CString::new(s.as_str_unchecked())
}