#[cfg_attr(
any(target_os = "ios", target_os = "tvos", target_os = "visionos"),
path = "ios.rs"
)]
#[cfg_attr(target_os = "macos", path = "macos.rs")]
#[cfg_attr(target_os = "android", path = "android.rs")]
#[cfg_attr(target_family = "wasm", path = "wasm.rs")]
#[cfg_attr(windows, path = "windows.rs")]
#[cfg_attr(
all(
unix,
not(any(
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_os = "macos",
target_os = "android",
target_family = "wasm",
windows,
)),
),
path = "unix.rs"
)]
mod os;
#[cfg(any(
windows,
all(
unix,
not(any(
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_os = "macos",
target_os = "android",
target_family = "wasm",
)),
),
))]
pub(crate) mod common;
use std::fmt::Display;
use std::io::{Error, ErrorKind, Result};
use std::ops::Deref;
use std::str::FromStr;
use std::{error, fmt};
#[derive(Debug, Default, Eq, PartialEq, Copy, Clone, Hash)]
pub enum Browser {
#[default]
Default,
Firefox,
InternetExplorer,
Chrome,
Opera,
Safari,
WebPositive,
}
impl Browser {
pub fn is_available() -> bool {
Browser::Default.exists()
}
pub fn exists(&self) -> bool {
open_browser_with_options(
*self,
"https://rootnet.in",
BrowserOptions::new().with_dry_run(true),
)
.is_ok()
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct ParseBrowserError;
impl fmt::Display for ParseBrowserError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Invalid browser given")
}
}
impl error::Error for ParseBrowserError {
fn description(&self) -> &str {
"invalid browser"
}
}
impl fmt::Display for Browser {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Browser::Default => f.write_str("Default"),
Browser::Firefox => f.write_str("Firefox"),
Browser::InternetExplorer => f.write_str("Internet Explorer"),
Browser::Chrome => f.write_str("Chrome"),
Browser::Opera => f.write_str("Opera"),
Browser::Safari => f.write_str("Safari"),
Browser::WebPositive => f.write_str("WebPositive"),
}
}
}
impl FromStr for Browser {
type Err = ParseBrowserError;
fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
match s {
"firefox" => Ok(Browser::Firefox),
"default" => Ok(Browser::Default),
"ie" | "internet explorer" | "internetexplorer" => Ok(Browser::InternetExplorer),
"chrome" => Ok(Browser::Chrome),
"opera" => Ok(Browser::Opera),
"safari" => Ok(Browser::Safari),
"webpositive" => Ok(Browser::WebPositive),
_ => Err(ParseBrowserError),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct BrowserOptions {
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={}, dry_run={})",
self.suppress_output, self.target_hint, self.dry_run
))
}
}
impl std::default::Default for BrowserOptions {
fn default() -> Self {
let target_hint = String::from(option_env!("WEBBROWSER_WASM_TARGET").unwrap_or("_blank"));
BrowserOptions {
suppress_output: true,
target_hint,
dry_run: false,
}
}
}
impl BrowserOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_suppress_output(&mut self, suppress_output: bool) -> &mut Self {
self.suppress_output = suppress_output;
self
}
#[allow(clippy::all)]
pub fn with_target_hint(&mut self, target_hint: &str) -> &mut Self {
self.target_hint = target_hint.to_owned();
self
}
pub fn with_dry_run(&mut self, dry_run: bool) -> &mut Self {
self.dry_run = dry_run;
self
}
}
pub fn open(url: &str) -> Result<()> {
open_browser(Browser::Default, url)
}
pub fn open_browser(browser: Browser, url: &str) -> Result<()> {
open_browser_with_options(browser, url, &BrowserOptions::default())
}
pub fn open_browser_with_options(
browser: Browser,
url: &str,
options: &BrowserOptions,
) -> Result<()> {
let target = TargetType::try_from(url)?;
#[cfg(feature = "hardened")]
if !target.is_http() {
return Err(Error::new(
ErrorKind::InvalidInput,
"only http/https urls allowed",
));
}
if cfg!(any(
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_os = "macos",
target_os = "android",
target_family = "wasm",
windows,
unix,
)) {
os::open_browser_internal(browser, &target, options)
} else {
Err(Error::new(ErrorKind::NotFound, "unsupported platform"))
}
}
struct TargetType(url::Url);
impl TargetType {
#[cfg(any(
feature = "hardened",
target_os = "android",
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_family = "wasm"
))]
fn is_http(&self) -> bool {
matches!(self.0.scheme(), "http" | "https")
}
#[cfg(any(
target_os = "android",
target_os = "ios",
target_os = "tvos",
target_os = "visionos",
target_family = "wasm"
))]
fn get_http_url(&self) -> Result<&str> {
if self.is_http() {
Ok(self.0.as_str())
} else {
Err(Error::new(ErrorKind::InvalidInput, "not an http url"))
}
}
#[cfg(not(target_family = "wasm"))]
fn from_file_path(value: &str) -> Result<Self> {
let pb = std::path::PathBuf::from(value);
let url = url::Url::from_file_path(if pb.is_relative() {
std::env::current_dir()?.join(pb)
} else {
pb
})
.map_err(|_| Error::new(ErrorKind::InvalidInput, "failed to convert path to url"))?;
Ok(Self(url))
}
}
impl Deref for TargetType {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_str()
}
}
impl Display for TargetType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(self as &str).fmt(f)
}
}
impl std::convert::TryFrom<&str> for TargetType {
type Error = Error;
#[cfg(target_family = "wasm")]
fn try_from(value: &str) -> Result<Self> {
url::Url::parse(value)
.map(|u| Ok(Self(u)))
.map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid url for wasm"))?
}
#[cfg(not(target_family = "wasm"))]
fn try_from(value: &str) -> Result<Self> {
match url::Url::parse(value) {
Ok(u) => {
if u.scheme().len() == 1 && cfg!(windows) {
Self::from_file_path(value)
} else {
Ok(Self(u))
}
}
Err(_) => Self::from_file_path(value),
}
}
}
#[test]
#[ignore]
fn test_open_firefox() {
assert!(open_browser(Browser::Firefox, "http://github.com").is_ok());
}
#[test]
#[ignore]
fn test_open_chrome() {
assert!(open_browser(Browser::Chrome, "http://github.com").is_ok());
}
#[test]
#[ignore]
fn test_open_safari() {
assert!(open_browser(Browser::Safari, "http://github.com").is_ok());
}
#[test]
#[ignore]
fn test_open_webpositive() {
assert!(open_browser(Browser::WebPositive, "http://github.com").is_ok());
}