mqttmarionette/src/browser.rs

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);
}
}
}