Skip to content

Commit

Permalink
Document consistent behaviour, and provide way of overriding suppress…
Browse files Browse the repository at this point in the history
…ion of output
  • Loading branch information
amodm committed Feb 19, 2022
1 parent bd8809a commit ecfbf66
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 65 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ if webbrowser::open("http://github.com").is_ok() {
| haiku | ✅ (experimental) | default only ||
| ios || default only ||

## Consistent Behaviour
`webbrowser` defines consistent behaviour on all platforms as follows:
* **Non-Blocking** for GUI based browsers (e.g. Firefox, Chrome etc.), while **Blocking** for text based browser (e.g. lynx etc.)
* **Suppressed output** by default for GUI based browsers, so that their stdout/stderr don't pollute the main program's output. This can be overridden by `webbrowser::open_browser_with_options`.

## Looking to contribute?

PRs invited for
Expand Down
6 changes: 3 additions & 3 deletions src/android.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{Browser, Error, ErrorKind, Result};
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
use jni::objects::JValue;
pub use std::os::unix::process::ExitStatusExt;
use std::process::ExitStatus;

/// Deal with opening of browsers on Android
/// Deal with opening of browsers on Android. BrowserOptions are ignored here.
#[inline]
pub fn open_browser_internal(_: Browser, url: &str) -> Result<()> {
pub fn open_browser_internal(_: Browser, url: &str, _: &BrowserOptions) -> Result<()> {
// Create a VM for executing Java calls
let native_activity = ndk_glue::native_activity();
let vm_ptr = native_activity.vm();
Expand Down
79 changes: 63 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@
//!
//! Inspired by the [webbrowser](https://docs.python.org/2/library/webbrowser.html) python library.
//!
//! ### Platform Support Status
//! ## Examples
//!
//! ```no_run
//! use webbrowser;
//!
//! if webbrowser::open("http://github.com").is_ok() {
//! // ...
//! }
//! ```
//!
//! ## Platform Support Status
//!
//! | Platform | Supported | Browsers | Test status |
//! |----------|-----------|----------|-------------|
Expand All @@ -14,20 +24,10 @@
//! | haiku | ✅ (experimental) | default only | ❌ |
//! | ios | ❌ | unsupported | ❌ |
//!
//! Important note:
//!
//! * This library requires availability of browsers and a graphical environment during runtime
//! * `cargo test` will actually open the browser locally.
//!
//! # Examples
//!
//! ```no_run
//! use webbrowser;
//!
//! if webbrowser::open("http://github.com").is_ok() {
//! // ...
//! }
//! ```
//! ## Consistent Behaviour
//! `webbrowser` defines consistent behaviour on all platforms as follows:
//! * **Non-Blocking** for GUI based browsers (e.g. Firefox, Chrome etc.), while **Blocking** for text based browser (e.g. lynx etc.)
//! * **Suppressed output** by default for GUI based browsers, so that their stdout/stderr don't pollute the main program's output. This can be overridden by `webbrowser::open_browser_with_options`.

#[cfg_attr(target_os = "macos", path = "macos.rs")]
#[cfg_attr(target_os = "android", path = "android.rs")]
Expand Down Expand Up @@ -141,6 +141,31 @@ impl FromStr for Browser {
}
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
/// BrowserOptions to override certain default behaviour
///
/// e.g. by default, we suppress stdout/stderr, but that behaviour can be overridden here
pub struct BrowserOptions {
pub suppress_output: bool,
}

impl fmt::Display for BrowserOptions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_fmt(format_args!(
"BrowserOptions(supress_output={})",
self.suppress_output
))
}
}

impl std::default::Default for BrowserOptions {
fn default() -> Self {
BrowserOptions {
suppress_output: true,
}
}
}

/// Opens the URL on the default browser of this platform
///
/// Returns Ok(..) so long as the browser invocation was successful. An Err(..) is returned only if
Expand Down Expand Up @@ -177,11 +202,33 @@ pub fn open(url: &str) -> Result<()> {
/// }
/// ```
pub fn open_browser(browser: Browser, url: &str) -> Result<()> {
open_browser_with_options(browser, url, &BrowserOptions::default())
}

/// Opens the specified URL on the specific browser (if available) requested, while overriding the
/// default options.
///
/// Return semantics are
/// the same as for [open](fn.open.html).
///
/// # Examples
/// ```no_run
/// use webbrowser::{open_browser_with_options, Browser, BrowserOptions};
///
/// if open_browser_with_options(Browser::Default, "http://github.com", &BrowserOptions { suppress_output: false }).is_ok() {
/// // ...
/// }
/// ```
pub fn open_browser_with_options(
browser: Browser,
url: &str,
options: &BrowserOptions,
) -> Result<()> {
let url_s: String = match url::Url::parse(url) {
Ok(u) => u.as_str().into(),
Err(_) => url.into(),
};
os::open_browser_internal(browser, &url_s)
os::open_browser_internal(browser, &url_s, options)
}

#[test]
Expand Down
23 changes: 18 additions & 5 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use crate::{Browser, Error, ErrorKind, Result};
use std::process::Command;
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
use std::process::{Command, Stdio};

mod common;
use common::from_status;

/// Deal with opening of browsers on Mac OS X, using `open` command
#[inline]
pub fn open_browser_internal(browser: Browser, url_raw: &str) -> Result<()> {
pub fn open_browser_internal(
browser: Browser,
url_raw: &str,
options: &BrowserOptions,
) -> Result<()> {
let url_s: String = match url::Url::parse(url_raw) {
Ok(u) => u.as_str().into(),
Err(_) => url_raw.into(),
};
let url = &url_s;
let mut cmd = Command::new("open");
match browser {
Browser::Default => from_status(cmd.arg(url).status()),
Browser::Default => run_command(cmd.arg(url), options),
_ => {
let app: Option<&str> = match browser {
Browser::Firefox => Some("Firefox"),
Expand All @@ -25,7 +29,7 @@ pub fn open_browser_internal(browser: Browser, url_raw: &str) -> Result<()> {
_ => None,
};
match app {
Some(name) => from_status(cmd.arg("-a").arg(name).arg(url).status()),
Some(name) => run_command(cmd.arg("-a").arg(name).arg(url), options),
None => Err(Error::new(
ErrorKind::NotFound,
format!("Unsupported browser {:?}", browser),
Expand All @@ -34,3 +38,12 @@ pub fn open_browser_internal(browser: Browser, url_raw: &str) -> Result<()> {
}
}
}

fn run_command(cmd: &mut Command, options: &BrowserOptions) -> Result<()> {
if options.suppress_output {
cmd.stdout(Stdio::null())
.stdin(Stdio::null())
.stderr(Stdio::null());
}
from_status(cmd.status())
}
68 changes: 37 additions & 31 deletions src/unix.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use crate::{Browser, Error, ErrorKind, Result};
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

macro_rules! try_browser {
( $name:expr, $( $arg:expr ),+ ) => {
( $options: expr, $name:expr, $( $arg:expr ),+ ) => {
for_matching_path($name, |pb| {
let mut cmd = Command::new(pb);
$(
cmd.arg($arg);
)+
run_command(&mut cmd, !is_text_browser(&pb))
run_command(&mut cmd, !is_text_browser(&pb), $options)
})
}
}
Expand All @@ -23,35 +23,35 @@ 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) -> Result<()> {
pub fn open_browser_internal(_: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
// we first try with the $BROWSER env
try_with_browser_env(url)
try_with_browser_env(url, options)
// allow for haiku's open specifically
.or_else(|_| try_haiku(url))
.or_else(|_| try_haiku(options, url))
// then we try with xdg-open
.or_else(|_| try_browser!("xdg-open", url))
.or_else(|_| try_browser!(options, "xdg-open", url))
// else do desktop specific stuff
.or_else(|r| match guess_desktop_env() {
"kde" => try_browser!("kde-open", url)
.or_else(|_| try_browser!("kde-open5", url))
.or_else(|_| try_browser!("kfmclient", "newTab", url)),
"kde" => try_browser!(options, "kde-open", url)
.or_else(|_| try_browser!(options, "kde-open5", url))
.or_else(|_| try_browser!(options, "kfmclient", "newTab", url)),

"gnome" => try_browser!("gio", "open", url)
.or_else(|_| try_browser!("gvfs-open", url))
.or_else(|_| try_browser!("gnome-open", url)),
"gnome" => try_browser!(options, "gio", "open", url)
.or_else(|_| try_browser!(options, "gvfs-open", url))
.or_else(|_| try_browser!(options, "gnome-open", url)),

"mate" => try_browser!("gio", "open", url)
.or_else(|_| try_browser!("gvfs-open", url))
.or_else(|_| try_browser!("mate-open", url)),
"mate" => try_browser!(options, "gio", "open", url)
.or_else(|_| try_browser!(options, "gvfs-open", url))
.or_else(|_| try_browser!(options, "mate-open", url)),

"xfce" => try_browser!("exo-open", url)
.or_else(|_| try_browser!("gio", "open", url))
.or_else(|_| try_browser!("gvfs-open", url)),
"xfce" => try_browser!(options, "exo-open", url)
.or_else(|_| try_browser!(options, "gio", "open", url))
.or_else(|_| try_browser!(options, "gvfs-open", url)),

_ => Err(r),
})
// at the end, we'll try x-www-browser and return the result as is
.or_else(|_| try_browser!("x-www-browser", url))
.or_else(|_| try_browser!(options, "x-www-browser", url))
// if all above failed, map error to not found
.map_err(|_| {
Error::new(
Expand All @@ -64,7 +64,7 @@ pub fn open_browser_internal(_: Browser, url: &str) -> Result<()> {
}

#[inline]
fn try_with_browser_env(url: &str) -> Result<()> {
fn try_with_browser_env(url: &str, options: &BrowserOptions) -> Result<()> {
// $BROWSER can contain ':' delimited options, each representing a potential browser command line
for browser in std::env::var("BROWSER")
.unwrap_or_else(|_| String::from(""))
Expand All @@ -88,7 +88,7 @@ fn try_with_browser_env(url: &str) -> Result<()> {
// append the url as an argument only if it was not already set via %s
cmd.arg(url);
}
run_command(&mut cmd, !is_text_browser(pb))
run_command(&mut cmd, !is_text_browser(pb), options)
});
if env_exit.is_ok() {
return Ok(());
Expand Down Expand Up @@ -136,9 +136,9 @@ fn guess_desktop_env() -> &'static str {
// Handle Haiku explicitly, as it uses an "open" command, similar to macos
// but on other Unixes, open ends up translating to shell open fd
#[inline]
fn try_haiku(url: &str) -> Result<()> {
fn try_haiku(options: &BrowserOptions, url: &str) -> Result<()> {
if cfg!(target_os = "haiku") {
try_browser!("open", url).map(|_| ())
try_browser!(options, "open", url).map(|_| ())
} else {
Err(Error::new(ErrorKind::NotFound, "Not on haiku"))
}
Expand Down Expand Up @@ -193,18 +193,24 @@ where

/// Run the specified command in foreground/background
#[inline]
fn run_command(cmd: &mut Command, background: bool) -> Result<()> {
fn run_command(cmd: &mut Command, background: bool, options: &BrowserOptions) -> Result<()> {
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.
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map(|_| ())
if options.suppress_output {
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
} else {
cmd
}
.spawn()
.map(|_| ())
} else {
// if we're in foreground, use status() instead of spawn(), as we'd like to wait
// till completion
// till completion.
// We also specifically don't supress anything here, because we're running here
// most likely because of a text browser
cmd.status().and_then(|status| {
if status.success() {
Ok(())
Expand Down
19 changes: 12 additions & 7 deletions src/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
use crate::{Browser, Error, ErrorKind, Result};
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};

/// Deal with opening a URL in wasm32. This implementation ignores the browser attribute
/// and always opens URLs in the same browser where wasm32 vm is running.
#[inline]
pub fn open_browser_internal(_: Browser, url: &str) -> Result<()> {
pub fn open_browser_internal(_: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
// we can override the target by the env var WEBBROWSER_WASM_TARGET at compile time
let configured_target = option_env!("WEBBROWSER_WASM_TARGET");
let window = web_sys::window();
match window {
Some(w) => {
let target = configured_target.unwrap_or_else(|| "_blank");
wasm_console_log(&format!("target for url {} detected as {}", url, target));
wasm_console_log(
&format!("target for url {} detected as {}", url, target),
options,
);

match w.open_with_url_and_target(url, target) {
Ok(x) => match x {
Some(_) => Ok(()),
None => {
wasm_console_log(POPUP_ERR_MSG);
wasm_console_log(POPUP_ERR_MSG, options);
Err(Error::new(ErrorKind::Other, POPUP_ERR_MSG))
}
},
Err(_) => {
wasm_console_log("window error while opening url");
wasm_console_log("window error while opening url", options);
Err(Error::new(ErrorKind::Other, "error opening url"))
}
}
Expand All @@ -34,9 +37,11 @@ pub fn open_browser_internal(_: Browser, url: &str) -> Result<()> {
}

/// Print to browser console
fn wasm_console_log(_msg: &str) {
fn wasm_console_log(_msg: &str, options: &BrowserOptions) {
#[cfg(all(debug_assertions, feature = "wasm-console"))]
web_sys::console::log_1(&format!("[webbrowser] {}", &_msg).into());
if !options.suppress_output {
web_sys::console::log_1(&format!("[webbrowser] {}", &_msg).into());
}
}

const POPUP_ERR_MSG: &'static str = "popup blocked? window detected, but open_url failed";
8 changes: 5 additions & 3 deletions src/windows.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
extern crate widestring;
extern crate winapi;

use crate::{Browser, Error, ErrorKind, Result};
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
pub use std::os::windows::process::ExitStatusExt;
use std::ptr;
use widestring::U16CString;

/// Deal with opening of browsers on Windows, using [`ShellExecuteW`](
/// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecutew)
/// fucntion.
/// function.
///
/// We ignore BrowserOptions on Windows.
#[inline]
pub fn open_browser_internal(browser: Browser, url: &str) -> Result<()> {
pub fn open_browser_internal(browser: Browser, url: &str, _: &BrowserOptions) -> Result<()> {
use winapi::shared::winerror::SUCCEEDED;
use winapi::um::combaseapi::{CoInitializeEx, CoUninitialize};
use winapi::um::objbase::{COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE};
Expand Down

0 comments on commit ecfbf66

Please sign in to comment.