Skip to content

Commit ecfbf66

Browse files
committed
Document consistent behaviour, and provide way of overriding suppression of output
1 parent bd8809a commit ecfbf66

File tree

7 files changed

+143
-65
lines changed

7 files changed

+143
-65
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ if webbrowser::open("http://github.com").is_ok() {
4141
| haiku | ✅ (experimental) | default only ||
4242
| ios || default only ||
4343

44+
## Consistent Behaviour
45+
`webbrowser` defines consistent behaviour on all platforms as follows:
46+
* **Non-Blocking** for GUI based browsers (e.g. Firefox, Chrome etc.), while **Blocking** for text based browser (e.g. lynx etc.)
47+
* **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`.
48+
4449
## Looking to contribute?
4550

4651
PRs invited for

src/android.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use crate::{Browser, Error, ErrorKind, Result};
1+
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
22
use jni::objects::JValue;
33
pub use std::os::unix::process::ExitStatusExt;
44
use std::process::ExitStatus;
55

6-
/// Deal with opening of browsers on Android
6+
/// Deal with opening of browsers on Android. BrowserOptions are ignored here.
77
#[inline]
8-
pub fn open_browser_internal(_: Browser, url: &str) -> Result<()> {
8+
pub fn open_browser_internal(_: Browser, url: &str, _: &BrowserOptions) -> Result<()> {
99
// Create a VM for executing Java calls
1010
let native_activity = ndk_glue::native_activity();
1111
let vm_ptr = native_activity.vm();

src/lib.rs

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22
//!
33
//! Inspired by the [webbrowser](https://docs.python.org/2/library/webbrowser.html) python library.
44
//!
5-
//! ### Platform Support Status
5+
//! ## Examples
6+
//!
7+
//! ```no_run
8+
//! use webbrowser;
9+
//!
10+
//! if webbrowser::open("http://github.com").is_ok() {
11+
//! // ...
12+
//! }
13+
//! ```
14+
//!
15+
//! ## Platform Support Status
616
//!
717
//! | Platform | Supported | Browsers | Test status |
818
//! |----------|-----------|----------|-------------|
@@ -14,20 +24,10 @@
1424
//! | haiku | ✅ (experimental) | default only | ❌ |
1525
//! | ios | ❌ | unsupported | ❌ |
1626
//!
17-
//! Important note:
18-
//!
19-
//! * This library requires availability of browsers and a graphical environment during runtime
20-
//! * `cargo test` will actually open the browser locally.
21-
//!
22-
//! # Examples
23-
//!
24-
//! ```no_run
25-
//! use webbrowser;
26-
//!
27-
//! if webbrowser::open("http://github.com").is_ok() {
28-
//! // ...
29-
//! }
30-
//! ```
27+
//! ## Consistent Behaviour
28+
//! `webbrowser` defines consistent behaviour on all platforms as follows:
29+
//! * **Non-Blocking** for GUI based browsers (e.g. Firefox, Chrome etc.), while **Blocking** for text based browser (e.g. lynx etc.)
30+
//! * **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`.
3131
3232
#[cfg_attr(target_os = "macos", path = "macos.rs")]
3333
#[cfg_attr(target_os = "android", path = "android.rs")]
@@ -141,6 +141,31 @@ impl FromStr for Browser {
141141
}
142142
}
143143

144+
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
145+
/// BrowserOptions to override certain default behaviour
146+
///
147+
/// e.g. by default, we suppress stdout/stderr, but that behaviour can be overridden here
148+
pub struct BrowserOptions {
149+
pub suppress_output: bool,
150+
}
151+
152+
impl fmt::Display for BrowserOptions {
153+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154+
f.write_fmt(format_args!(
155+
"BrowserOptions(supress_output={})",
156+
self.suppress_output
157+
))
158+
}
159+
}
160+
161+
impl std::default::Default for BrowserOptions {
162+
fn default() -> Self {
163+
BrowserOptions {
164+
suppress_output: true,
165+
}
166+
}
167+
}
168+
144169
/// Opens the URL on the default browser of this platform
145170
///
146171
/// Returns Ok(..) so long as the browser invocation was successful. An Err(..) is returned only if
@@ -177,11 +202,33 @@ pub fn open(url: &str) -> Result<()> {
177202
/// }
178203
/// ```
179204
pub fn open_browser(browser: Browser, url: &str) -> Result<()> {
205+
open_browser_with_options(browser, url, &BrowserOptions::default())
206+
}
207+
208+
/// Opens the specified URL on the specific browser (if available) requested, while overriding the
209+
/// default options.
210+
///
211+
/// Return semantics are
212+
/// the same as for [open](fn.open.html).
213+
///
214+
/// # Examples
215+
/// ```no_run
216+
/// use webbrowser::{open_browser_with_options, Browser, BrowserOptions};
217+
///
218+
/// if open_browser_with_options(Browser::Default, "http://github.com", &BrowserOptions { suppress_output: false }).is_ok() {
219+
/// // ...
220+
/// }
221+
/// ```
222+
pub fn open_browser_with_options(
223+
browser: Browser,
224+
url: &str,
225+
options: &BrowserOptions,
226+
) -> Result<()> {
180227
let url_s: String = match url::Url::parse(url) {
181228
Ok(u) => u.as_str().into(),
182229
Err(_) => url.into(),
183230
};
184-
os::open_browser_internal(browser, &url_s)
231+
os::open_browser_internal(browser, &url_s, options)
185232
}
186233

187234
#[test]

src/macos.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
use crate::{Browser, Error, ErrorKind, Result};
2-
use std::process::Command;
1+
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
2+
use std::process::{Command, Stdio};
33

44
mod common;
55
use common::from_status;
66

77
/// Deal with opening of browsers on Mac OS X, using `open` command
88
#[inline]
9-
pub fn open_browser_internal(browser: Browser, url_raw: &str) -> Result<()> {
9+
pub fn open_browser_internal(
10+
browser: Browser,
11+
url_raw: &str,
12+
options: &BrowserOptions,
13+
) -> Result<()> {
1014
let url_s: String = match url::Url::parse(url_raw) {
1115
Ok(u) => u.as_str().into(),
1216
Err(_) => url_raw.into(),
1317
};
1418
let url = &url_s;
1519
let mut cmd = Command::new("open");
1620
match browser {
17-
Browser::Default => from_status(cmd.arg(url).status()),
21+
Browser::Default => run_command(cmd.arg(url), options),
1822
_ => {
1923
let app: Option<&str> = match browser {
2024
Browser::Firefox => Some("Firefox"),
@@ -25,7 +29,7 @@ pub fn open_browser_internal(browser: Browser, url_raw: &str) -> Result<()> {
2529
_ => None,
2630
};
2731
match app {
28-
Some(name) => from_status(cmd.arg("-a").arg(name).arg(url).status()),
32+
Some(name) => run_command(cmd.arg("-a").arg(name).arg(url), options),
2933
None => Err(Error::new(
3034
ErrorKind::NotFound,
3135
format!("Unsupported browser {:?}", browser),
@@ -34,3 +38,12 @@ pub fn open_browser_internal(browser: Browser, url_raw: &str) -> Result<()> {
3438
}
3539
}
3640
}
41+
42+
fn run_command(cmd: &mut Command, options: &BrowserOptions) -> Result<()> {
43+
if options.suppress_output {
44+
cmd.stdout(Stdio::null())
45+
.stdin(Stdio::null())
46+
.stderr(Stdio::null());
47+
}
48+
from_status(cmd.status())
49+
}

src/unix.rs

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
use crate::{Browser, Error, ErrorKind, Result};
1+
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
22
use std::os::unix::fs::PermissionsExt;
33
use std::path::{Path, PathBuf};
44
use std::process::{Command, Stdio};
55

66
macro_rules! try_browser {
7-
( $name:expr, $( $arg:expr ),+ ) => {
7+
( $options: expr, $name:expr, $( $arg:expr ),+ ) => {
88
for_matching_path($name, |pb| {
99
let mut cmd = Command::new(pb);
1010
$(
1111
cmd.arg($arg);
1212
)+
13-
run_command(&mut cmd, !is_text_browser(&pb))
13+
run_command(&mut cmd, !is_text_browser(&pb), $options)
1414
})
1515
}
1616
}
@@ -23,35 +23,35 @@ macro_rules! try_browser {
2323
/// 3. Attempt to use window manager specific commands, like gnome-open, kde-open etc.
2424
/// 4. Fallback to x-www-browser
2525
#[inline]
26-
pub fn open_browser_internal(_: Browser, url: &str) -> Result<()> {
26+
pub fn open_browser_internal(_: Browser, url: &str, options: &BrowserOptions) -> Result<()> {
2727
// we first try with the $BROWSER env
28-
try_with_browser_env(url)
28+
try_with_browser_env(url, options)
2929
// allow for haiku's open specifically
30-
.or_else(|_| try_haiku(url))
30+
.or_else(|_| try_haiku(options, url))
3131
// then we try with xdg-open
32-
.or_else(|_| try_browser!("xdg-open", url))
32+
.or_else(|_| try_browser!(options, "xdg-open", url))
3333
// else do desktop specific stuff
3434
.or_else(|r| match guess_desktop_env() {
35-
"kde" => try_browser!("kde-open", url)
36-
.or_else(|_| try_browser!("kde-open5", url))
37-
.or_else(|_| try_browser!("kfmclient", "newTab", url)),
35+
"kde" => try_browser!(options, "kde-open", url)
36+
.or_else(|_| try_browser!(options, "kde-open5", url))
37+
.or_else(|_| try_browser!(options, "kfmclient", "newTab", url)),
3838

39-
"gnome" => try_browser!("gio", "open", url)
40-
.or_else(|_| try_browser!("gvfs-open", url))
41-
.or_else(|_| try_browser!("gnome-open", url)),
39+
"gnome" => try_browser!(options, "gio", "open", url)
40+
.or_else(|_| try_browser!(options, "gvfs-open", url))
41+
.or_else(|_| try_browser!(options, "gnome-open", url)),
4242

43-
"mate" => try_browser!("gio", "open", url)
44-
.or_else(|_| try_browser!("gvfs-open", url))
45-
.or_else(|_| try_browser!("mate-open", url)),
43+
"mate" => try_browser!(options, "gio", "open", url)
44+
.or_else(|_| try_browser!(options, "gvfs-open", url))
45+
.or_else(|_| try_browser!(options, "mate-open", url)),
4646

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

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

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

194194
/// Run the specified command in foreground/background
195195
#[inline]
196-
fn run_command(cmd: &mut Command, background: bool) -> Result<()> {
196+
fn run_command(cmd: &mut Command, background: bool, options: &BrowserOptions) -> Result<()> {
197197
if background {
198198
// if we're in background, set stdin/stdout to null and spawn a child, as we're
199199
// not supposed to have any interaction.
200-
cmd.stdin(Stdio::null())
201-
.stdout(Stdio::null())
202-
.stderr(Stdio::null())
203-
.spawn()
204-
.map(|_| ())
200+
if options.suppress_output {
201+
cmd.stdin(Stdio::null())
202+
.stdout(Stdio::null())
203+
.stderr(Stdio::null())
204+
} else {
205+
cmd
206+
}
207+
.spawn()
208+
.map(|_| ())
205209
} else {
206210
// if we're in foreground, use status() instead of spawn(), as we'd like to wait
207-
// till completion
211+
// till completion.
212+
// We also specifically don't supress anything here, because we're running here
213+
// most likely because of a text browser
208214
cmd.status().and_then(|status| {
209215
if status.success() {
210216
Ok(())

src/wasm.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
1-
use crate::{Browser, Error, ErrorKind, Result};
1+
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
22

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

1518
match w.open_with_url_and_target(url, target) {
1619
Ok(x) => match x {
1720
Some(_) => Ok(()),
1821
None => {
19-
wasm_console_log(POPUP_ERR_MSG);
22+
wasm_console_log(POPUP_ERR_MSG, options);
2023
Err(Error::new(ErrorKind::Other, POPUP_ERR_MSG))
2124
}
2225
},
2326
Err(_) => {
24-
wasm_console_log("window error while opening url");
27+
wasm_console_log("window error while opening url", options);
2528
Err(Error::new(ErrorKind::Other, "error opening url"))
2629
}
2730
}
@@ -34,9 +37,11 @@ pub fn open_browser_internal(_: Browser, url: &str) -> Result<()> {
3437
}
3538

3639
/// Print to browser console
37-
fn wasm_console_log(_msg: &str) {
40+
fn wasm_console_log(_msg: &str, options: &BrowserOptions) {
3841
#[cfg(all(debug_assertions, feature = "wasm-console"))]
39-
web_sys::console::log_1(&format!("[webbrowser] {}", &_msg).into());
42+
if !options.suppress_output {
43+
web_sys::console::log_1(&format!("[webbrowser] {}", &_msg).into());
44+
}
4045
}
4146

4247
const POPUP_ERR_MSG: &'static str = "popup blocked? window detected, but open_url failed";

src/windows.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
extern crate widestring;
22
extern crate winapi;
33

4-
use crate::{Browser, Error, ErrorKind, Result};
4+
use crate::{Browser, BrowserOptions, Error, ErrorKind, Result};
55
pub use std::os::windows::process::ExitStatusExt;
66
use std::ptr;
77
use widestring::U16CString;
88

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

0 commit comments

Comments
 (0)