Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Browser::is_available() to fix #46 #47

Merged
merged 1 commit into from Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/android.rs
Expand Up @@ -3,9 +3,27 @@ use jni::objects::JValue;
pub use std::os::unix::process::ExitStatusExt;
use std::process::ExitStatus;

/// Deal with opening of browsers on Android. BrowserOptions are ignored here.
/// Deal with opening of browsers on Android. Only [Browser::Default] is supported, and
/// in options, only [BrowserOptions::dry_run] is honoured.
#[inline]
pub fn open_browser_internal(_: Browser, url: &str, _: &BrowserOptions) -> Result<()> {
pub fn open_browser_internal(browser: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
match browser {
Browser::Default => open_browser_default(url, options),
_ => Err(Error::new(
ErrorKind::NotFound,
"only default browser supported",
)),
}
}

/// Open the default browser
#[inline]
pub fn open_browser_default(url: &str, options: &BrowserOptions) -> Result<()> {
// always return true for a dry run
if options.dry_run {
return Ok(());
}

// Create a VM for executing Java calls
let native_activity = ndk_glue::native_activity();
let vm_ptr = native_activity.vm();
Expand Down
45 changes: 35 additions & 10 deletions src/lib.rs
Expand Up @@ -88,6 +88,23 @@ pub enum Browser {
WebPositive,
}

impl Browser {
/// Returns true if there is likely a browser detected in the system
pub fn is_available() -> bool {
Browser::Default.exists()
}

/// Returns true if this specific browser is detected in the system
pub fn exists(&self) -> bool {
open_browser_with_options(
*self,
"https://rootnet.in",
BrowserOptions::new().with_dry_run(true),
)
.is_ok()
}
}

///The Error type for parsing a string into a Browser.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct ParseBrowserError;
Expand Down Expand Up @@ -147,21 +164,16 @@ impl FromStr for Browser {
///
/// e.g. by default, we suppress stdout/stderr, but that behaviour can be overridden here
pub struct BrowserOptions {
/// Determines whether stdout/stderr of the appropriate browser command is suppressed
/// or not
pub suppress_output: bool,

/// Hint to the browser to open the url in the corresponding
/// [target](https://www.w3schools.com/tags/att_a_target.asp). Note that this is just
/// a hint, it may or may not be honoured (currently guaranteed only in wasm).
pub target_hint: String,
suppress_output: bool,
target_hint: String,
dry_run: bool,
}

impl fmt::Display for BrowserOptions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_fmt(format_args!(
"BrowserOptions(supress_output={}, target_hint={})",
self.suppress_output, self.target_hint,
"BrowserOptions(supress_output={}, target_hint={}, dry_run={})",
self.suppress_output, self.target_hint, self.dry_run
))
}
}
Expand All @@ -172,6 +184,7 @@ impl std::default::Default for BrowserOptions {
BrowserOptions {
suppress_output: true,
target_hint,
dry_run: false,
}
}
}
Expand All @@ -181,15 +194,27 @@ impl BrowserOptions {
Self::default()
}

/// Determines whether stdout/stderr of the appropriate browser command is suppressed
/// or not
pub fn with_suppress_output(&mut self, suppress_output: bool) -> &mut Self {
self.suppress_output = suppress_output;
self
}

/// Hint to the browser to open the url in the corresponding
/// [target](https://www.w3schools.com/tags/att_a_target.asp). Note that this is just
/// a hint, it may or may not be honoured (currently guaranteed only in wasm).
pub fn with_target_hint(&mut self, target_hint: &str) -> &mut Self {
self.target_hint = target_hint.to_owned();
self
}

/// Do not do an actual execution, just return true if this would've likely
/// succeeded. Note the "likely" here - it's still indicative than guaranteed.
pub fn with_dry_run(&mut self, dry_run: bool) -> &mut Self {
self.dry_run = dry_run;
self
}
}

/// Opens the URL on the default browser of this platform
Expand Down
27 changes: 25 additions & 2 deletions src/macos.rs
Expand Up @@ -18,7 +18,14 @@ pub fn open_browser_internal(
let url = &url_s;
let mut cmd = Command::new("open");
match browser {
Browser::Default => run_command(cmd.arg(url), options),
Browser::Default => {
// always return true for a dry run for default browser
if options.dry_run {
return Ok(());
}

run_command(cmd.arg(url), options)
}
_ => {
let app: Option<&str> = match browser {
Browser::Firefox => Some("Firefox"),
Expand All @@ -29,7 +36,23 @@ pub fn open_browser_internal(
_ => None,
};
match app {
Some(name) => run_command(cmd.arg("-a").arg(name).arg(url), options),
Some(name) => {
if options.dry_run {
// in case of a dry run, we just check for the existence of the app dir
let md = std::fs::metadata(format!("/Applications/{}.app", name));
if md.map(|x| x.is_dir()).unwrap_or(false) {
Ok(())
} else {
Err(Error::new(
ErrorKind::NotFound,
format!("Browser {} not available", name),
))
}
} else {
// run the command only if not dry_run
run_command(cmd.arg("-a").arg(name).arg(url), options)
}
}
None => Err(Error::new(
ErrorKind::NotFound,
format!("Unsupported browser {:?}", browser),
Expand Down
23 changes: 22 additions & 1 deletion src/unix.rs
Expand Up @@ -23,7 +23,22 @@ macro_rules! try_browser {
/// 3. Attempt to use window manager specific commands, like gnome-open, kde-open etc.
/// 4. Fallback to x-www-browser
#[inline]
pub fn open_browser_internal(_: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
pub fn open_browser_internal(browser: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
match browser {
Browser::Default => open_browser_default(url, options),
_ => Err(Error::new(
ErrorKind::NotFound,
"only default browser supported",
)),
}
}

/// Open the default browser.
///
/// [BrowserOptions::dry_run] is handled inside [run_command], as all execution paths eventually
/// rely on it to execute.
#[inline]
fn open_browser_default(url: &str, options: &BrowserOptions) -> Result<()> {
// we first try with the $BROWSER env
try_with_browser_env(url, options)
// allow for haiku's open specifically
Expand Down Expand Up @@ -194,6 +209,12 @@ where
/// Run the specified command in foreground/background
#[inline]
fn run_command(cmd: &mut Command, background: bool, options: &BrowserOptions) -> Result<()> {
// if dry_run, we return a true, as executable existence check has
// already been done
if options.dry_run {
return Ok(());
}

if background {
// if we're in background, set stdin/stdout to null and spawn a child, as we're
// not supposed to have any interaction.
Expand Down
5 changes: 5 additions & 0 deletions src/wasm.rs
Expand Up @@ -4,6 +4,11 @@ use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
/// and always opens URLs in the same browser where wasm32 vm is running.
#[inline]
pub fn open_browser_internal(_: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
// always return true for a dry run
if options.dry_run {
return Ok(());
}

let window = web_sys::window();
match window {
Some(w) => match w.open_with_url_and_target(url, &options.target_hint) {
Expand Down
9 changes: 7 additions & 2 deletions src/windows.rs
Expand Up @@ -10,16 +10,21 @@ use widestring::U16CString;
/// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecutew)
/// function.
///
/// We ignore BrowserOptions on Windows.
/// We ignore BrowserOptions on Windows, except for honouring [BrowserOptions::dry_run]
#[inline]
pub fn open_browser_internal(browser: Browser, url: &str, _: &BrowserOptions) -> Result<()> {
pub fn open_browser_internal(browser: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
use winapi::shared::winerror::SUCCEEDED;
use winapi::um::combaseapi::{CoInitializeEx, CoUninitialize};
use winapi::um::objbase::{COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE};
use winapi::um::shellapi::ShellExecuteW;
use winapi::um::winuser::SW_SHOWNORMAL;
match browser {
Browser::Default => {
// always return true for a dry run for default browser
if options.dry_run {
return Ok(());
}

static OPEN: &[u16] = &['o' as u16, 'p' as u16, 'e' as u16, 'n' as u16, 0x0000];
let url =
U16CString::from_str(url).map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
Expand Down
11 changes: 11 additions & 0 deletions tests/test_android.rs
Expand Up @@ -9,6 +9,7 @@ mod tests {
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use webbrowser::Browser;

// to run this test, run it as:
// cargo test --test test_android -- --ignored
Expand Down Expand Up @@ -74,4 +75,14 @@ mod tests {
.expect("no ip address found")
.into()
}

#[test]
fn test_existence_default() {
assert!(Browser::is_available(), "should have found a browser");
}

#[test]
fn test_non_existence_safari() {
assert!(!Browser::Safari.exists(), "should not have found Safari");
}
}
18 changes: 18 additions & 0 deletions tests/test_macos.rs
Expand Up @@ -29,4 +29,22 @@ mod tests {
async fn test_open_chrome() {
check_browser(Browser::Chrome, TEST_PLATFORM).await;
}

#[test]
fn test_existence_default() {
assert!(Browser::is_available(), "should have found a browser");
}

#[test]
fn test_existence_safari() {
assert!(Browser::Safari.exists(), "should have found Safari");
}

#[test]
fn test_non_existence_webpositive() {
assert!(
!Browser::WebPositive.exists(),
"should not have found WebPositive",
);
}
}
10 changes: 10 additions & 0 deletions tests/test_unix.rs
Expand Up @@ -12,4 +12,14 @@ mod tests {
async fn test_open_default() {
check_browser(Browser::Default, TEST_PLATFORM).await;
}

#[test]
fn test_existence_default() {
assert!(Browser::is_available(), "should have found a browser");
}

#[test]
fn test_non_existence_safari() {
assert!(!Browser::Safari.exists(), "should not have found Safari");
}
}
11 changes: 11 additions & 0 deletions tests/test_wasm.rs
Expand Up @@ -8,6 +8,7 @@ mod tests {
use super::common::check_request_received_using;
use std::fs;
use std::path::PathBuf;
use webbrowser::Browser;

// to run this test, run it as:
// cargo test --test test_wasm32 -- --ignored
Expand Down Expand Up @@ -44,4 +45,14 @@ mod tests {
})
.await;
}

#[test]
fn test_existence_default() {
assert!(Browser::is_available(), "should've found a browser");
}

#[test]
fn test_non_existence_safari() {
assert!(!Browser::Safari.exists(), "should've not found Safari");
}
}
10 changes: 10 additions & 0 deletions tests/test_windows.rs
Expand Up @@ -18,4 +18,14 @@ mod tests {
async fn test_open_internet_explorer() {
check_browser(Browser::InternetExplorer, TEST_PLATFORM).await;
}

#[test]
fn test_existence_default() {
assert!(Browser::is_available(), "should have found a browser");
}

#[test]
fn test_non_existence_safari() {
assert!(!Browser::Safari.exists(), "should not have found Safari");
}
}