Skip to content

Commit

Permalink
feat: support webview proxy (#1006)
Browse files Browse the repository at this point in the history
* feat(macos): support proxy

* Added WebView2 implementation

* Update mod.rs

* Added Webkit2gtk implementation

* chore: update doc and format

* fix(macos): use TryFrom trait for nw_endpoint_t

* fix(macos): add `mac-proxy` feature flag to enable proxy feature for macOS 14.0+

* chore: fix clippy

* Change WebView2 argument construction to before async operation

* Update src/webview/mod.rs

Co-authored-by: Amr Bashir <[email protected]>

* Remove unused import

* update formatting

* Added documentation at with_additional_browser_args

* refactor: remove redundant proxy struct

* Remove redundant reference

* hide proxy module and export types needed

---------

Co-authored-by: DK Liao <[email protected]>
Co-authored-by: Amr Bashir <[email protected]>
  • Loading branch information
3 people authored Aug 28, 2023
1 parent 70d8ae0 commit 3cc4d79
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 26 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ devtools = [ ]
transparent = [ ]
fullscreen = [ ]
linux-body = [ "webkit2gtk/v2_40" ]
mac-proxy = [ ]

[dependencies]
libc = "0.2"
Expand Down
44 changes: 44 additions & 0 deletions examples/proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use wry::webview::{ProxyConfig, ProxyEndpoint};

fn main() -> wry::Result<()> {
use wry::{
application::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
},
webview::WebViewBuilder,
};

let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Proxy Test")
.build(&event_loop)?;

let http_proxy = ProxyConfig::Http(ProxyEndpoint {
host: "localhost".to_string(),
port: "3128".to_string(),
});

let _webview = WebViewBuilder::new(window)?
.with_proxy_config(http_proxy)
.with_url("https://www.myip.com/")?
.build()?;

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,6 @@ pub enum Error {
#[cfg(target_os = "android")]
#[error(transparent)]
JniError(#[from] tao::platform::android::ndk_glue::jni::errors::Error),
#[error("Failed to create proxy endpoint")]
ProxyEndpointCreationFailed,
}
22 changes: 21 additions & 1 deletion src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

//! [`WebView`] struct and associated types.

mod proxy;
mod web_context;

pub use web_context::WebContext;
Expand Down Expand Up @@ -50,6 +51,7 @@ use windows::{Win32::Foundation::HWND, Win32::UI::WindowsAndMessaging::DestroyWi

use std::{borrow::Cow, path::PathBuf, rc::Rc};

pub use proxy::{ProxyConfig, ProxyEndpoint};
pub use url::Url;

#[cfg(target_os = "windows")]
Expand Down Expand Up @@ -235,6 +237,12 @@ pub struct WebViewAttributes {

/// Set a handler closure to process page load events.
pub on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent, String)>>,

/// Set a proxy configuration for the webview. Supports HTTP CONNECT and SOCKSv5 proxies
///
/// - **macOS**: Requires macOS 14.0+ and the `mac-proxy` feature flag to be enabled.
/// - **Android / iOS:** Not supported.
pub proxy_config: Option<ProxyConfig>,
}

impl Default for WebViewAttributes {
Expand Down Expand Up @@ -267,6 +275,7 @@ impl Default for WebViewAttributes {
incognito: false,
autoplay: true,
on_page_load_handler: None,
proxy_config: None,
}
}
}
Expand Down Expand Up @@ -655,6 +664,16 @@ impl<'a> WebViewBuilder<'a> {
self
}

/// Set a proxy configuration for the webview.
///
/// - **macOS**: Requires macOS 14.0+ and the `mac-proxy` feature flag to be enabled. Supports HTTP CONNECT and SOCKSv5 proxies.
/// - **Windows / Linux**: Supports HTTP CONNECT and SOCKSv5 proxies.
/// - **Android / iOS:** Not supported.
pub fn with_proxy_config(mut self, configuration: ProxyConfig) -> Self {
self.webview.proxy_config = Some(configuration);
self
}

/// Consume the builder and create the [`WebView`].
///
/// Platform-specific behavior:
Expand Down Expand Up @@ -682,7 +701,8 @@ pub trait WebViewBuilderExtWindows {
/// ## Warning
///
/// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`
/// and `--autoplay-policy=no-user-gesture-required` if autoplay is enabled
/// `--autoplay-policy=no-user-gesture-required` if autoplay is enabled
/// and `--proxy-server=<scheme>://<host>:<port>` if a proxy is set.
/// so if you use this method, you have to add these arguments yourself if you want to keep the same behavior.
fn with_additional_browser_args<S: Into<String>>(self, additional_args: S) -> Self;

Expand Down
15 changes: 15 additions & 0 deletions src/webview/proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#[derive(Debug, Clone)]
pub struct ProxyEndpoint {
/// Proxy server host (e.g. 192.168.0.100, localhost, example.com, etc.)
pub host: String,
/// Proxy server port (e.g. 1080, 3128, etc.)
pub port: String,
}

#[derive(Debug, Clone)]
pub enum ProxyConfig {
/// Connect to proxy server via HTTP CONNECT
Http(ProxyEndpoint),
/// Connect to proxy server via SOCKSv5
Socks5(ProxyEndpoint),
}
23 changes: 18 additions & 5 deletions src/webview/webkitgtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use std::{
};
use url::Url;
use webkit2gtk::{
traits::*, AutoplayPolicy, LoadEvent, NavigationPolicyDecision, PolicyDecisionType, SettingsExt,
URIRequest, UserContentInjectedFrames, UserScript, UserScriptInjectionTime, WebView,
WebViewBuilder, WebsitePoliciesBuilder,
traits::*, AutoplayPolicy, LoadEvent, NavigationPolicyDecision, NetworkProxyMode,
NetworkProxySettings, PolicyDecisionType, SettingsExt, URIRequest, UserContentInjectedFrames,
UserScript, UserScriptInjectionTime, WebView, WebViewBuilder, WebsitePoliciesBuilder,
};
use webkit2gtk_sys::{
webkit_get_major_version, webkit_get_micro_version, webkit_get_minor_version,
Expand All @@ -29,7 +29,7 @@ pub use web_context::WebContextImpl;

use crate::{
application::{platform::unix::*, window::Window},
webview::{web_context::WebContext, PageLoadEvent, WebViewAttributes, RGBA},
webview::{proxy::ProxyConfig, web_context::WebContext, PageLoadEvent, WebViewAttributes, RGBA},
Error, Result,
};

Expand Down Expand Up @@ -71,7 +71,20 @@ impl InnerWebView {
}
}
};

if let Some(proxy_setting) = attributes.proxy_config {
let proxy_uri = match proxy_setting {
ProxyConfig::Http(endpoint) => format!("http://{}:{}", endpoint.host, endpoint.port),
ProxyConfig::Socks5(endpoint) => {
format!("socks5://{}:{}", endpoint.host, endpoint.port)
}
};
use webkit2gtk::WebContextExt;
if let Some(website_data_manager) = web_context.context().website_data_manager() {
let mut settings = NetworkProxySettings::new(Some(&proxy_uri.as_str()), &[]);
website_data_manager
.set_network_proxy_settings(NetworkProxyMode::Custom, Some(&mut settings));
}
}
let webview = {
let mut webview = WebViewBuilder::new();
webview = webview.user_content_manager(web_context.manager());
Expand Down
61 changes: 41 additions & 20 deletions src/webview/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@
mod file_drop;

use crate::{
webview::{PageLoadEvent, WebContext, WebViewAttributes, RGBA},
webview::{proxy::ProxyConfig, PageLoadEvent, WebContext, WebViewAttributes, RGBA},
Error, Result,
};

use file_drop::FileDropController;
use url::Url;

use std::{
collections::HashSet, fmt::Write, iter::once, mem::MaybeUninit, os::windows::prelude::OsStrExt,
path::PathBuf, rc::Rc, sync::mpsc, sync::Arc,
collections::HashSet,
fmt::Write,
iter::once,
mem::MaybeUninit,
os::windows::prelude::OsStrExt,
path::PathBuf,
rc::Rc,
sync::{mpsc, Arc},
};

use once_cell::unsync::OnceCell;
Expand Down Expand Up @@ -74,7 +80,7 @@ impl InnerWebView {
let file_drop_handler = attributes.file_drop_handler.take();
let file_drop_window = window.clone();

let env = Self::create_environment(&web_context, pl_attrs.clone(), attributes.autoplay)?;
let env = Self::create_environment(&web_context, pl_attrs.clone(), &attributes)?;
let controller = Self::create_controller(hwnd, &env, attributes.incognito)?;
let webview = Self::init_webview(window, hwnd, attributes, &env, &controller, pl_attrs)?;

Expand All @@ -95,7 +101,7 @@ impl InnerWebView {
fn create_environment(
web_context: &Option<&mut WebContext>,
pl_attrs: super::PlatformSpecificWebViewAttributes,
autoplay: bool,
attributes: &WebViewAttributes,
) -> webview2_com::Result<ICoreWebView2Environment> {
let (tx, rx) = mpsc::channel();

Expand All @@ -105,6 +111,35 @@ impl InnerWebView {
.and_then(|path| path.to_str())
.map(String::from);

let argument = PCWSTR::from_raw(
encode_wide(pl_attrs.additional_browser_args.unwrap_or_else(|| {
// remove "mini menu" - See https://github.com/tauri-apps/wry/issues/535
// and "smart screen" - See https://github.com/tauri-apps/tauri/issues/1345
format!(
"--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection{}{}",
if attributes.autoplay {
" --autoplay-policy=no-user-gesture-required"
} else {
""
},
if let Some(proxy_setting) = &attributes.proxy_config {
match proxy_setting {
ProxyConfig::Http(endpoint) => {
format!(" --proxy-server=http://{}:{}", endpoint.host, endpoint.port)
}
ProxyConfig::Socks5(endpoint) => format!(
" --proxy-server=socks5://{}:{}",
endpoint.host, endpoint.port
),
}
} else {
"".to_string()
}
)
}))
.as_ptr(),
);

CreateCoreWebView2EnvironmentCompletedHandler::wait_for_async_operation(
Box::new(move |environmentcreatedhandler| unsafe {
let options = {
Expand All @@ -126,21 +161,7 @@ impl InnerWebView {
options
};

let _ = options.SetAdditionalBrowserArguments(PCWSTR::from_raw(
encode_wide(pl_attrs.additional_browser_args.unwrap_or_else(|| {
// remove "mini menu" - See https://github.com/tauri-apps/wry/issues/535
// and "smart screen" - See https://github.com/tauri-apps/tauri/issues/1345
format!(
"--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection{}",
if autoplay {
" --autoplay-policy=no-user-gesture-required"
} else {
""
}
)
}))
.as_ptr(),
));
let _ = options.SetAdditionalBrowserArguments(argument);

if let Some(data_directory) = data_directory {
CreateCoreWebView2EnvironmentWithOptions(
Expand Down
35 changes: 35 additions & 0 deletions src/webview/wkwebview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod download;
#[cfg(target_os = "macos")]
mod file_drop;
mod navigation;
#[cfg(feature = "mac-proxy")]
mod proxy;
#[cfg(target_os = "macos")]
mod synthetic_mouse_events;

Expand Down Expand Up @@ -43,6 +45,14 @@ use file_drop::{add_file_drop_methods, set_file_drop_handler};
#[cfg(target_os = "ios")]
use crate::application::platform::ios::WindowExtIOS;

#[cfg(feature = "mac-proxy")]
use crate::webview::{
proxy::ProxyConfig,
wkwebview::proxy::{
nw_endpoint_t, nw_proxy_config_create_http_connect, nw_proxy_config_create_socksv5,
},
};

use crate::{
application::{
dpi::{LogicalSize, PhysicalSize},
Expand Down Expand Up @@ -314,6 +324,23 @@ impl InnerWebView {
let _preference: id = msg_send![config, preferences];
let _yes: id = msg_send![class!(NSNumber), numberWithBool:1];

#[cfg(feature = "mac-proxy")]
if let Some(proxy_config) = attributes.proxy_config {
let proxy_config = match proxy_config {
ProxyConfig::Http(endpoint) => {
let nw_endpoint = nw_endpoint_t::try_from(endpoint).unwrap();
nw_proxy_config_create_http_connect(nw_endpoint, nil)
}
ProxyConfig::Socks5(endpoint) => {
let nw_endpoint = nw_endpoint_t::try_from(endpoint).unwrap();
nw_proxy_config_create_socksv5(nw_endpoint)
}
};

let proxies: id = msg_send![class!(NSArray), arrayWithObject: proxy_config];
let () = msg_send![data_store, setProxyConfigurations: proxies];
}

#[cfg(target_os = "macos")]
(*webview).set_ivar(ACCEPT_FIRST_MOUSE, attributes.accept_first_mouse);

Expand Down Expand Up @@ -1123,6 +1150,14 @@ impl NSString {
}
}

#[allow(dead_code)] // only used when `mac-proxy` feature is enabled
fn to_cstr(&self) -> *const c_char {
unsafe {
let utf_8_string = msg_send![self.0, UTF8String];
utf_8_string
}
}

fn as_ptr(&self) -> id {
self.0
}
Expand Down
Loading

0 comments on commit 3cc4d79

Please sign in to comment.