From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- remote/startup/handler.rs | 242 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 remote/startup/handler.rs (limited to 'remote/startup/handler.rs') diff --git a/remote/startup/handler.rs b/remote/startup/handler.rs new file mode 100644 index 0000000000..f441b6100f --- /dev/null +++ b/remote/startup/handler.rs @@ -0,0 +1,242 @@ +// 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) { + if let Ok(handler) = RemoteAgentHandler::new() { + RefPtr::new(handler.coerce::()).forget(&mut *result); + } else { + *result = std::ptr::null(); + } +} + +#[derive(xpcom)] +#[xpimplements(nsICommandLineHandler)] +#[xpimplements(nsIObserver)] +#[refcnt = "atomic"] +struct InitRemoteAgentHandler { + agent: RemoteAgent, + observer: RefPtr, + address: RefCell, +} + +impl RemoteAgentHandler { + pub fn new() -> Result, 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_debugging_port = if flags.present("remote-debugging-port") { + Some(flags.opt_u16("remote-debugging-port")?) + } else { + None + }; + + let addr = match remote_debugging_port { + Some(Some(port)) => format!("{}:{}", DEFAULT_HOST, port), + Some(None) => format!("{}:{}", DEFAULT_HOST, DEFAULT_PORT), + None => return Ok(()), + }; + + *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 { + let help = format!( + r#" --remote-debugging-port [] Start the Firefox remote agent, + which is a low-level debugging interface based on the CDP protocol. + Defaults to listen on {}:{}. +"#, + DEFAULT_HOST, DEFAULT_PORT + ); + 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-debugging-port can be used both with and without a value. + fn opt_str(&self, name: &str) -> RemoteAgentResult> { + 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> { + Ok(if let Some(s) = self.opt_str(name)? { + Some(s.parse()?) + } else { + None + }) + } +} + +fn prog() -> Option { + 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 { + 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()) +} -- cgit v1.2.3