136 lines
3.7 KiB
Rust
136 lines
3.7 KiB
Rust
use std::path::{Path, PathBuf};
|
|
|
|
use inotify::{Inotify, WatchMask};
|
|
use mozprofile::preferences::Pref;
|
|
use mozprofile::profile::Profile;
|
|
use mozrunner::runner::{
|
|
FirefoxProcess, FirefoxRunner, Runner, RunnerError, RunnerProcess,
|
|
};
|
|
use tokio_stream::StreamExt;
|
|
use tracing::{debug, error, trace, warn};
|
|
|
|
#[derive(Debug)]
|
|
pub enum BrowserError {
|
|
Io(std::io::Error),
|
|
Runner(RunnerError),
|
|
}
|
|
|
|
impl std::fmt::Display for BrowserError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
Self::Io(e) => write!(f, "{}", e),
|
|
Self::Runner(e) => write!(f, "{}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for BrowserError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
match self {
|
|
Self::Io(e) => Some(e),
|
|
Self::Runner(e) => Some(e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<std::io::Error> for BrowserError {
|
|
fn from(e: std::io::Error) -> Self {
|
|
Self::Io(e)
|
|
}
|
|
}
|
|
|
|
impl From<RunnerError> for BrowserError {
|
|
fn from(e: RunnerError) -> Self {
|
|
Self::Runner(e)
|
|
}
|
|
}
|
|
|
|
pub struct Browser {
|
|
profile: PathBuf,
|
|
process: FirefoxProcess,
|
|
}
|
|
|
|
impl Browser {
|
|
pub fn launch() -> Result<Self, BrowserError> {
|
|
Self::launch_with_binary(None)
|
|
}
|
|
|
|
pub fn launch_with_binary(
|
|
binary: Option<&Path>,
|
|
) -> Result<Self, BrowserError> {
|
|
let mut profile = Profile::new(None)?;
|
|
match profile.user_prefs() {
|
|
Ok(prefs) => {
|
|
prefs.insert("marionette.port", Pref::new(0));
|
|
}
|
|
Err(e) => {
|
|
error!("Could not get profile user prefs: {}", e);
|
|
}
|
|
}
|
|
let binary = match binary {
|
|
Some(b) => b,
|
|
None => Path::new("firefox"),
|
|
};
|
|
let profile_path = profile.path.clone();
|
|
let mut runner = FirefoxRunner::new(binary, Some(profile));
|
|
runner.arg("--marionette");
|
|
let process = runner.start()?;
|
|
Ok(Self {
|
|
profile: profile_path,
|
|
process,
|
|
})
|
|
}
|
|
|
|
pub fn marionette_port(&self) -> Option<u16> {
|
|
let mut path = self.profile.clone();
|
|
path.push("MarionetteActivePort");
|
|
let port = match std::fs::read_to_string(path) {
|
|
Ok(v) => match v.parse() {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
warn!("Failed to parse Marionette port: {}", e);
|
|
return None;
|
|
}
|
|
},
|
|
Err(e) => {
|
|
warn!("Could not read active Marionette port: {}", e);
|
|
return None;
|
|
}
|
|
};
|
|
Some(port)
|
|
}
|
|
|
|
pub async fn wait_ready(&self) -> Result<(), std::io::Error> {
|
|
if self.marionette_port().is_some() {
|
|
debug!("Marionette port has already been assigned");
|
|
return Ok(());
|
|
}
|
|
let mut inotify = Inotify::init()?;
|
|
debug!("Starting inotify watch for {}", self.profile.display());
|
|
inotify.add_watch(self.profile.clone(), WatchMask::CREATE)?;
|
|
let mut buffer = [0; 1024];
|
|
let mut stream = inotify.event_stream(&mut buffer)?;
|
|
while let Some(evt) = stream.next().await {
|
|
trace!("inotify event: {:?}", evt);
|
|
if let Some(name) = evt?.name {
|
|
if name == "MarionetteActivePort" {
|
|
debug!("Marionette port has been assigned");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Drop for Browser {
|
|
fn drop(&mut self) {
|
|
debug!("Stopping browser process");
|
|
if let Err(e) =
|
|
self.process.wait(std::time::Duration::from_millis(500))
|
|
{
|
|
error!("Error stopping browser process: {}", e);
|
|
}
|
|
}
|
|
}
|