feat: add use_https_scheme for Windows and Android (#11477)

* feat: add `use_https_scheme` for Windows and Android

closes #11252

* fix compilation

* Apply suggestions from code review

Co-authored-by: Fabian-Lars <github@fabianlars.de>

* change wording

* add migrations

* migrate `dangerousUseHttpScheme`

* fmt

* infer AssetResolver::get https scheme config

* fix tests

---------

Co-authored-by: Fabian-Lars <github@fabianlars.de>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Amr Bashir 2024-11-05 14:48:59 +02:00 committed by GitHub
parent 058c0db72f
commit f37e97d410
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 358 additions and 79 deletions

View File

@ -0,0 +1,6 @@
---
"tauri": "minor:feat"
"tauri-utils": "minor:feat"
---
Add `app > windows > useHttpsScheme` config option to choose whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android

View File

@ -0,0 +1,7 @@
---
"tauri": "minor:feat"
"tauri-runtime": "minor:feat"
"tauri-runtime-wry": "minor:feat"
---
Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android

View File

@ -486,6 +486,11 @@
"description": "Whether browser extensions can be installed for the webview process\n\n ## Platform-specific:\n\n - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n - **MacOS / Linux / iOS / Android** - Unsupported.",
"default": false,
"type": "boolean"
},
"useHttpsScheme": {
"description": "Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n\n ## Note\n\n Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n\n ## Warning\n\n Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.",
"default": false,
"type": "boolean"
}
},
"additionalProperties": false

View File

@ -87,6 +87,28 @@ fn migrate_config(config: &mut Value) -> Result<MigratedConfig> {
migrated.permissions = permissions;
}
// dangerousUseHttpScheme/useHttpsScheme
let dangerouse_use_http = tauri_config
.get("security")
.and_then(|w| w.as_object())
.and_then(|w| {
w.get("dangerousUseHttpScheme")
.or_else(|| w.get("dangerous-use-http-scheme"))
})
.and_then(|v| v.as_bool())
.unwrap_or_default();
if let Some(windows) = tauri_config
.get_mut("windows")
.and_then(|w| w.as_array_mut())
{
for window in windows {
if let Some(window) = window.as_object_mut() {
window.insert("useHttpsScheme".to_string(), (!dangerouse_use_http).into());
}
}
}
// security
if let Some(security) = tauri_config
.get_mut("security")
@ -802,7 +824,8 @@ mod test {
"pattern": { "use": "brownfield" },
"security": {
"csp": "default-src 'self' tauri:"
}
},
"windows": [{}]
}
});
@ -907,6 +930,8 @@ mod test {
migrated["app"]["withGlobalTauri"],
original["build"]["withGlobalTauri"]
);
assert_eq!(migrated["app"]["windows"][0]["useHttpsScheme"], true);
}
#[test]
@ -941,6 +966,28 @@ mod test {
);
}
#[test]
fn migrate_dangerous_use_http_scheme() {
let original = serde_json::json!({
"tauri": {
"windows": [{}],
"security": {
"dangerousUseHttpScheme": true,
}
}
});
let migrated = migrate(&original);
assert_eq!(
!migrated["app"]["windows"][0]["useHttpsScheme"]
.as_bool()
.unwrap(),
original["tauri"]["security"]["dangerousUseHttpScheme"]
.as_bool()
.unwrap()
);
}
#[test]
fn can_migrate_default_config() {
let original = serde_json::to_value(tauri_utils_v1::config::Config::default()).unwrap();

View File

@ -22,8 +22,8 @@ use tauri_runtime::{
monitor::Monitor,
webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler},
window::{
CursorIcon, DetachedWindow, DragDropEvent, PendingWindow, RawWindow, WebviewEvent,
WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints,
CursorIcon, DetachedWindow, DetachedWindowWebview, DragDropEvent, PendingWindow, RawWindow,
WebviewEvent, WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints,
},
DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, ProgressBarState,
ProgressBarStatus, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType,
@ -276,7 +276,16 @@ impl<T: UserEvent> Context<T> {
let label = pending.label.clone();
let context = self.clone();
let window_id = self.next_window_id();
let webview_id = pending.webview.as_ref().map(|_| context.next_webview_id());
let (webview_id, use_https_scheme) = pending
.webview
.as_ref()
.map(|w| {
(
Some(context.next_webview_id()),
w.webview_attributes.use_https_scheme,
)
})
.unwrap_or((None, false));
send_user_message(
self,
@ -300,13 +309,19 @@ impl<T: UserEvent> Context<T> {
context: self.clone(),
};
let detached_webview = webview_id.map(|id| DetachedWebview {
label: label.clone(),
dispatcher: WryWebviewDispatcher {
window_id: Arc::new(Mutex::new(window_id)),
webview_id: id,
context: self.clone(),
},
let detached_webview = webview_id.map(|id| {
let webview = DetachedWebview {
label: label.clone(),
dispatcher: WryWebviewDispatcher {
window_id: Arc::new(Mutex::new(window_id)),
webview_id: id,
context: self.clone(),
},
};
DetachedWindowWebview {
webview,
use_https_scheme,
}
});
Ok(DetachedWindow {
@ -746,6 +761,8 @@ impl WindowBuilder for WindowBuilderWrapper {
builder = builder.title_bar_style(TitleBarStyle::Visible);
}
builder = builder.title("Tauri App");
builder
}
@ -2497,10 +2514,16 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
) -> Result<DetachedWindow<T, Self>> {
let label = pending.label.clone();
let window_id = self.context.next_window_id();
let webview_id = pending
let (webview_id, use_https_scheme) = pending
.webview
.as_ref()
.map(|_| self.context.next_webview_id());
.map(|w| {
(
Some(self.context.next_webview_id()),
w.webview_attributes.use_https_scheme,
)
})
.unwrap_or((None, false));
let window = create_window(
window_id,
@ -2524,13 +2547,19 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
.borrow_mut()
.insert(window_id, window);
let detached_webview = webview_id.map(|id| DetachedWebview {
label: label.clone(),
dispatcher: WryWebviewDispatcher {
window_id: Arc::new(Mutex::new(window_id)),
webview_id: id,
context: self.context.clone(),
},
let detached_webview = webview_id.map(|id| {
let webview = DetachedWebview {
label: label.clone(),
dispatcher: WryWebviewDispatcher {
window_id: Arc::new(Mutex::new(window_id)),
webview_id: id,
context: self.context.clone(),
},
};
DetachedWindowWebview {
webview,
use_https_scheme,
}
});
Ok(DetachedWindow {
@ -4026,6 +4055,11 @@ fn create_webview<T: UserEvent>(
.with_clipboard(webview_attributes.clipboard)
.with_hotkeys_zoom(webview_attributes.zoom_hotkeys_enabled);
#[cfg(any(target_os = "windows", target_os = "android"))]
{
webview_builder = webview_builder.with_https_scheme(webview_attributes.use_https_scheme);
}
if webview_attributes.drag_drop_handler_enabled {
let proxy = context.proxy.clone();
let window_id_ = window_id.clone();
@ -4168,11 +4202,6 @@ fn create_webview<T: UserEvent>(
});
}
#[cfg(windows)]
{
webview_builder = webview_builder.with_https_scheme(false);
}
#[cfg(windows)]
{
webview_builder = webview_builder
@ -4282,7 +4311,7 @@ fn create_webview<T: UserEvent>(
builder
}
}
.map_err(|e| Error::CreateWebview(Box::new(dbg!(e))))?;
.map_err(|e| Error::CreateWebview(Box::new(e)))?;
if kind == WebviewKind::WindowContent {
#[cfg(any(

View File

@ -210,6 +210,7 @@ pub struct WebviewAttributes {
pub proxy_url: Option<Url>,
pub zoom_hotkeys_enabled: bool,
pub browser_extensions_enabled: bool,
pub use_https_scheme: bool,
}
impl From<&WindowConfig> for WebviewAttributes {
@ -218,6 +219,7 @@ impl From<&WindowConfig> for WebviewAttributes {
.incognito(config.incognito)
.focused(config.focus)
.zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
.use_https_scheme(config.use_https_scheme)
.browser_extensions_enabled(config.browser_extensions_enabled);
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
{
@ -264,6 +266,7 @@ impl WebviewAttributes {
proxy_url: None,
zoom_hotkeys_enabled: false,
browser_extensions_enabled: false,
use_https_scheme: false,
}
}
@ -388,6 +391,21 @@ impl WebviewAttributes {
self.browser_extensions_enabled = enabled;
self
}
/// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
///
/// ## Note
///
/// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
///
/// ## Warning
///
/// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
#[must_use]
pub fn use_https_scheme(mut self, enabled: bool) -> Self {
self.use_https_scheme = enabled;
self
}
}
/// IPC handler.

View File

@ -512,7 +512,23 @@ pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
pub dispatcher: R::WindowDispatcher,
/// The webview dispatcher in case this window has an attached webview.
pub webview: Option<DetachedWebview<T, R>>,
pub webview: Option<DetachedWindowWebview<T, R>>,
}
/// A detached webview associated with a window.
#[derive(Debug)]
pub struct DetachedWindowWebview<T: UserEvent, R: Runtime<T>> {
pub webview: DetachedWebview<T, R>,
pub use_https_scheme: bool,
}
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindowWebview<T, R> {
fn clone(&self) -> Self {
Self {
webview: self.webview.clone(),
use_https_scheme: self.use_https_scheme,
}
}
}
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {

View File

@ -486,6 +486,11 @@
"description": "Whether browser extensions can be installed for the webview process\n\n ## Platform-specific:\n\n - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n - **MacOS / Linux / iOS / Android** - Unsupported.",
"default": false,
"type": "boolean"
},
"useHttpsScheme": {
"description": "Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.\n\n ## Note\n\n Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.\n\n ## Warning\n\n Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.",
"default": false,
"type": "boolean"
}
},
"additionalProperties": false

View File

@ -1519,6 +1519,18 @@ pub struct WindowConfig {
/// - **MacOS / Linux / iOS / Android** - Unsupported.
#[serde(default)]
pub browser_extensions_enabled: bool,
/// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
///
/// ## Note
///
/// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
///
/// ## Warning
///
/// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
#[serde(default, alias = "use-https-scheme")]
pub use_https_scheme: bool,
}
impl Default for WindowConfig {
@ -1567,6 +1579,7 @@ impl Default for WindowConfig {
proxy_url: None,
zoom_hotkeys_enabled: false,
browser_extensions_enabled: false,
use_https_scheme: false,
}
}
}
@ -2538,6 +2551,7 @@ mod build {
let parent = opt_str_lit(self.parent.as_ref());
let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
let browser_extensions_enabled = self.browser_extensions_enabled;
let use_https_scheme = self.use_https_scheme;
literal_struct!(
tokens,
@ -2584,7 +2598,8 @@ mod build {
incognito,
parent,
zoom_hotkeys_enabled,
browser_extensions_enabled
browser_extensions_enabled,
use_https_scheme
);
}
}

View File

@ -8,12 +8,13 @@
}
const osName = __TEMPLATE_os_name__
const protocolScheme = __TEMPLATE_protocol_scheme__
Object.defineProperty(window.__TAURI_INTERNALS__, 'convertFileSrc', {
value: function (filePath, protocol = 'asset') {
const path = encodeURIComponent(filePath)
return osName === 'windows' || osName === 'android'
? `http://${protocol}.localhost/${path}`
? `${protocolScheme}://${protocol}.localhost/${path}`
: `${protocol}://localhost/${path}`
}
})

View File

@ -268,6 +268,10 @@ pub struct AssetResolver<R: Runtime> {
impl<R: Runtime> AssetResolver<R> {
/// Gets the app asset associated with the given path.
///
/// By default it tries to infer your application's URL scheme in production by checking if all webviews
/// were configured with [`crate::webview::WebviewBuilder::use_https_scheme`] or `tauri.conf.json > app > windows > useHttpsScheme`.
/// If you are resolving an asset for a webview with a more dynamic configuration, see [`AssetResolver::get_for_scheme`].
///
/// Resolves to the embedded asset that is part of the app
/// in dev when [`devUrl`](https://v2.tauri.app/reference/config/#devurl) points to a folder in your filesystem
/// or in production when [`frontendDist`](https://v2.tauri.app/reference/config/#frontenddist)
@ -276,6 +280,19 @@ impl<R: Runtime> AssetResolver<R> {
/// Fallbacks to reading the asset from the [distDir] folder so the behavior is consistent in development.
/// Note that the dist directory must exist so you might need to build your frontend assets first.
pub fn get(&self, path: String) -> Option<Asset> {
let use_https_scheme = self
.manager
.webviews()
.values()
.all(|webview| webview.use_https_scheme());
self.get_for_scheme(path, use_https_scheme)
}
/// Same as [AssetResolver::get] but resolves the custom protocol scheme based on a parameter.
///
/// - `use_https_scheme`: If `true` when using [`Pattern::Isolation`](tauri::Pattern::Isolation),
/// the csp header will contain `https://tauri.localhost` instead of `http://tauri.localhost`
pub fn get_for_scheme(&self, path: String, use_https_scheme: bool) -> Option<Asset> {
#[cfg(dev)]
{
// on dev if the devPath is a path to a directory we have the embedded assets
@ -306,7 +323,7 @@ impl<R: Runtime> AssetResolver<R> {
}
}
self.manager.get_asset(path).ok()
self.manager.get_asset(path, use_https_scheme).ok()
}
/// Iterate on all assets.

View File

@ -340,9 +340,10 @@ impl<R: Runtime> AppManager<R> {
self.config.build.dev_url.as_ref()
}
pub(crate) fn protocol_url(&self) -> Cow<'_, Url> {
pub(crate) fn protocol_url(&self, https: bool) -> Cow<'_, Url> {
if cfg!(windows) || cfg!(target_os = "android") {
Cow::Owned(Url::parse("http://tauri.localhost").unwrap())
let scheme = if https { "https" } else { "http" };
Cow::Owned(Url::parse(&format!("{scheme}://tauri.localhost")).unwrap())
} else {
Cow::Owned(Url::parse("tauri://localhost").unwrap())
}
@ -351,10 +352,10 @@ impl<R: Runtime> AppManager<R> {
/// Get the base URL to use for webview requests.
///
/// In dev mode, this will be based on the `devUrl` configuration value.
pub(crate) fn get_url(&self) -> Cow<'_, Url> {
pub(crate) fn get_url(&self, https: bool) -> Cow<'_, Url> {
match self.base_path() {
Some(url) => Cow::Borrowed(url),
_ => self.protocol_url(),
_ => self.protocol_url(https),
}
}
@ -372,7 +373,11 @@ impl<R: Runtime> AppManager<R> {
}
}
pub fn get_asset(&self, mut path: String) -> Result<Asset, Box<dyn std::error::Error>> {
pub fn get_asset(
&self,
mut path: String,
use_https_schema: bool,
) -> Result<Asset, Box<dyn std::error::Error>> {
let assets = &self.assets;
if path.ends_with('/') {
path.pop();
@ -435,7 +440,7 @@ impl<R: Runtime> AppManager<R> {
let default_src = csp_map
.entry("default-src".into())
.or_insert_with(Default::default);
default_src.push(crate::pattern::format_real_schema(schema));
default_src.push(crate::pattern::format_real_schema(schema, use_https_schema));
}
csp_header.replace(Csp::DirectiveMap(csp_map).to_string());
@ -771,17 +776,25 @@ mod test {
#[cfg(custom_protocol)]
{
assert_eq!(
manager.get_url().to_string(),
manager.get_url(false).to_string(),
if cfg!(windows) || cfg!(target_os = "android") {
"http://tauri.localhost/"
} else {
"tauri://localhost"
}
);
assert_eq!(
manager.get_url(true).to_string(),
if cfg!(windows) || cfg!(target_os = "android") {
"https://tauri.localhost/"
} else {
"tauri://localhost"
}
);
}
#[cfg(dev)]
assert_eq!(manager.get_url().to_string(), "http://localhost:4000/");
assert_eq!(manager.get_url(false).to_string(), "http://localhost:4000/");
}
struct EventSetup {

View File

@ -137,10 +137,14 @@ impl<R: Runtime> WebviewManager<R> {
let mut webview_attributes = pending.webview_attributes;
let use_https_scheme = webview_attributes.use_https_scheme;
let ipc_init = IpcJavascript {
isolation_origin: &match &*app_manager.pattern {
#[cfg(feature = "isolation")]
crate::Pattern::Isolation { schema, .. } => crate::pattern::format_real_schema(schema),
crate::Pattern::Isolation { schema, .. } => {
crate::pattern::format_real_schema(schema, use_https_scheme)
}
_ => "".to_string(),
},
}
@ -180,6 +184,7 @@ impl<R: Runtime> WebviewManager<R> {
&ipc_init.into_string(),
&pattern_init.into_string(),
is_init_global,
use_https_scheme,
)?);
for plugin_init_script in plugin_init_scripts {
@ -190,7 +195,7 @@ impl<R: Runtime> WebviewManager<R> {
if let crate::Pattern::Isolation { schema, .. } = &*app_manager.pattern {
webview_attributes = webview_attributes.initialization_script(
&IsolationJavascript {
isolation_src: &crate::pattern::format_real_schema(schema),
isolation_src: &crate::pattern::format_real_schema(schema, use_https_scheme),
style: tauri_utils::pattern::isolation::IFRAME_STYLE,
}
.render_default(&Default::default())?
@ -232,7 +237,8 @@ impl<R: Runtime> WebviewManager<R> {
&& window_url.scheme() != "http"
&& window_url.scheme() != "https"
{
format!("http://{}.localhost", window_url.scheme())
let https = if use_https_scheme { "https" } else { "http" };
format!("{https}://{}.localhost", window_url.scheme())
} else if let Some(host) = window_url.host() {
format!(
"{}://{}{}",
@ -320,6 +326,7 @@ impl<R: Runtime> WebviewManager<R> {
assets.clone(),
*crypto_keys.aes_gcm().raw(),
window_origin,
use_https_scheme,
);
pending.register_uri_scheme_protocol(schema, move |webview_id, request, responder| {
protocol(webview_id, request, UriSchemeResponder(responder))
@ -335,6 +342,7 @@ impl<R: Runtime> WebviewManager<R> {
ipc_script: &str,
pattern_script: &str,
with_global_tauri: bool,
use_https_scheme: bool,
) -> crate::Result<String> {
#[derive(Template)]
#[default_template("../../scripts/init.js")]
@ -357,6 +365,7 @@ impl<R: Runtime> WebviewManager<R> {
#[default_template("../../scripts/core.js")]
struct CoreJavascript<'a> {
os_name: &'a str,
protocol_scheme: &'a str,
invoke_key: &'a str,
}
@ -378,6 +387,7 @@ impl<R: Runtime> WebviewManager<R> {
bundle_script,
core_script: &CoreJavascript {
os_name: std::env::consts::OS,
protocol_scheme: if use_https_scheme { "https" } else { "http" },
invoke_key: self.invoke_key(),
}
.render_default(&Default::default())?
@ -411,7 +421,7 @@ impl<R: Runtime> WebviewManager<R> {
let url = if PROXY_DEV_SERVER {
Cow::Owned(Url::parse("tauri://localhost").unwrap())
} else {
app_manager.get_url()
app_manager.get_url(pending.webview_attributes.use_https_scheme)
};
// ignore "index.html" just to simplify the url
if path.to_str() != Some("index.html") {
@ -425,7 +435,7 @@ impl<R: Runtime> WebviewManager<R> {
}
}
WebviewUrl::External(url) => {
let config_url = app_manager.get_url();
let config_url = app_manager.get_url(pending.webview_attributes.use_https_scheme);
let is_local = config_url.make_relative(url).is_some();
let mut url = url.clone();
if is_local && PROXY_DEV_SERVER {
@ -572,8 +582,9 @@ impl<R: Runtime> WebviewManager<R> {
&self,
window: Window<R>,
webview: DetachedWebview<EventLoopMessage, R>,
use_https_scheme: bool,
) -> Webview<R> {
let webview = Webview::new(window, webview);
let webview = Webview::new(window, webview, use_https_scheme);
let webview_event_listeners = self.event_listeners.clone();
let webview_ = webview.clone();

View File

@ -85,9 +85,10 @@ pub(crate) struct PatternJavascript {
}
#[allow(dead_code)]
pub(crate) fn format_real_schema(schema: &str) -> String {
pub(crate) fn format_real_schema(schema: &str, https: bool) -> String {
if cfg!(windows) || cfg!(target_os = "android") {
format!("http://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}")
let scheme = if https { "https" } else { "http" };
format!("{scheme}://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}")
} else {
format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}")
}

View File

@ -21,9 +21,11 @@ pub fn get<R: Runtime>(
assets: Arc<EmbeddedAssets>,
aes_gcm_key: [u8; 32],
window_origin: String,
use_https_scheme: bool,
) -> UriSchemeProtocolHandler {
let frame_src = if cfg!(any(windows, target_os = "android")) {
format!("http://{schema}.localhost")
let https = if use_https_scheme { "https" } else { "http" };
format!("{https}://{schema}.localhost")
} else {
format!("{schema}:")
};

View File

@ -30,7 +30,10 @@ pub fn get<R: Runtime>(
) -> UriSchemeProtocolHandler {
#[cfg(all(dev, mobile))]
let url = {
let mut url = manager.get_url().as_str().to_string();
let mut url = manager
.get_url(window_origin.starts_with("https"))
.as_str()
.to_string();
if url.ends_with('/') {
url.pop();
}
@ -152,7 +155,8 @@ fn get_response<R: Runtime>(
#[cfg(not(all(dev, mobile)))]
let mut response = {
let asset = manager.get_asset(path)?;
let use_https_scheme = request.uri().scheme() == Some(&http::uri::Scheme::HTTPS);
let asset = manager.get_asset(path, use_https_scheme)?;
builder = builder.header(CONTENT_TYPE, &asset.mime_type);
if let Some(csp) = &asset.csp_header {
builder = builder.header("Content-Security-Policy", csp);

View File

@ -9,8 +9,10 @@ use tauri_runtime::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
monitor::Monitor,
webview::{DetachedWebview, PendingWebview},
window::{CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, WindowId},
window::{WindowBuilder, WindowBuilderBase},
window::{
CursorIcon, DetachedWindow, DetachedWindowWebview, PendingWindow, RawWindow, WindowBuilder,
WindowBuilderBase, WindowEvent, WindowId,
},
DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, ProgressBarState,
Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent,
WebviewDispatch, WindowDispatch, WindowEventId,
@ -158,14 +160,17 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
},
);
let webview = webview_id.map(|id| DetachedWebview {
label: pending.label.clone(),
dispatcher: MockWebviewDispatcher {
id,
context: self.context.clone(),
url: Arc::new(Mutex::new(pending.webview.unwrap().url)),
last_evaluated_script: Default::default(),
let webview = webview_id.map(|id| DetachedWindowWebview {
webview: DetachedWebview {
label: pending.label.clone(),
dispatcher: MockWebviewDispatcher {
id,
context: self.context.clone(),
url: Arc::new(Mutex::new(pending.webview.unwrap().url)),
last_evaluated_script: Default::default(),
},
},
use_https_scheme: false,
});
Ok(DetachedWindow {
@ -773,14 +778,17 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
},
);
let webview = webview_id.map(|id| DetachedWebview {
label: pending.label.clone(),
dispatcher: MockWebviewDispatcher {
id,
context: self.context.clone(),
url: Arc::new(Mutex::new(pending.webview.unwrap().url)),
last_evaluated_script: Default::default(),
let webview = webview_id.map(|id| DetachedWindowWebview {
webview: DetachedWebview {
label: pending.label.clone(),
dispatcher: MockWebviewDispatcher {
id,
context: self.context.clone(),
url: Arc::new(Mutex::new(pending.webview.unwrap().url)),
last_evaluated_script: Default::default(),
},
},
use_https_scheme: false,
});
Ok(DetachedWindow {
@ -1065,14 +1073,17 @@ impl<T: UserEvent> Runtime<T> for MockRuntime {
},
);
let webview = webview_id.map(|id| DetachedWebview {
label: pending.label.clone(),
dispatcher: MockWebviewDispatcher {
id,
context: self.context.clone(),
url: Arc::new(Mutex::new(pending.webview.unwrap().url)),
last_evaluated_script: Default::default(),
let webview = webview_id.map(|id| DetachedWindowWebview {
webview: DetachedWebview {
label: pending.label.clone(),
dispatcher: MockWebviewDispatcher {
id,
context: self.context.clone(),
url: Arc::new(Mutex::new(pending.webview.unwrap().url)),
last_evaluated_script: Default::default(),
},
},
use_https_scheme: false,
});
Ok(DetachedWindow {

View File

@ -605,11 +605,17 @@ tauri::Builder::default()
pending.webview_attributes.bounds = Some(tauri_runtime::Rect { size, position });
let use_https_scheme = pending.webview_attributes.use_https_scheme;
let webview = match &mut window.runtime() {
RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_webview(pending),
_ => unimplemented!(),
}
.map(|webview| app_manager.webview.attach_webview(window.clone(), webview))?;
.map(|webview| {
app_manager
.webview
.attach_webview(window.clone(), webview, use_https_scheme)
})?;
Ok(webview)
}
@ -794,6 +800,21 @@ fn main() {
self.webview_attributes.browser_extensions_enabled = enabled;
self
}
/// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
///
/// ## Note
///
/// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
///
/// ## Warning
///
/// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
#[must_use]
pub fn use_https_scheme(mut self, enabled: bool) -> Self {
self.webview_attributes.use_https_scheme = enabled;
self
}
}
/// Webview.
@ -806,6 +827,7 @@ pub struct Webview<R: Runtime> {
pub(crate) manager: Arc<AppManager<R>>,
pub(crate) app_handle: AppHandle<R>,
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
use_https_scheme: bool,
}
impl<R: Runtime> std::fmt::Debug for Webview<R> {
@ -813,6 +835,7 @@ impl<R: Runtime> std::fmt::Debug for Webview<R> {
f.debug_struct("Window")
.field("window", &self.window.lock().unwrap())
.field("webview", &self.webview)
.field("use_https_scheme", &self.use_https_scheme)
.finish()
}
}
@ -825,6 +848,7 @@ impl<R: Runtime> Clone for Webview<R> {
manager: self.manager.clone(),
app_handle: self.app_handle.clone(),
resources_table: self.resources_table.clone(),
use_https_scheme: self.use_https_scheme,
}
}
}
@ -847,13 +871,18 @@ impl<R: Runtime> PartialEq for Webview<R> {
/// Base webview functions.
impl<R: Runtime> Webview<R> {
/// Create a new webview that is attached to the window.
pub(crate) fn new(window: Window<R>, webview: DetachedWebview<EventLoopMessage, R>) -> Self {
pub(crate) fn new(
window: Window<R>,
webview: DetachedWebview<EventLoopMessage, R>,
use_https_scheme: bool,
) -> Self {
Self {
manager: window.manager.clone(),
app_handle: window.app_handle.clone(),
window: Arc::new(Mutex::new(window)),
webview,
resources_table: Default::default(),
use_https_scheme,
}
}
@ -880,6 +909,11 @@ impl<R: Runtime> Webview<R> {
&self.webview.label
}
/// Whether the webview was configured to use the HTTPS scheme or not.
pub(crate) fn use_https_scheme(&self) -> bool {
self.use_https_scheme
}
/// Registers a window event listener.
pub fn on_webview_event<F: Fn(&WebviewEvent) + Send + 'static>(&self, f: F) {
self
@ -1180,9 +1214,11 @@ fn main() {
}
fn is_local_url(&self, current_url: &Url) -> bool {
let uses_https = current_url.scheme() == "https";
// if from `tauri://` custom protocol
({
let protocol_url = self.manager().protocol_url();
let protocol_url = self.manager().protocol_url(uses_https);
current_url.scheme() == protocol_url.scheme()
&& current_url.domain() == protocol_url.domain()
}) ||
@ -1190,7 +1226,7 @@ fn main() {
// or if relative to `devUrl` or `frontendDist`
self
.manager()
.get_url()
.get_url(uses_https)
.make_relative(current_url)
.is_some()
@ -1206,7 +1242,7 @@ fn main() {
// so we check using the first part of the domain
#[cfg(any(windows, target_os = "android"))]
let local = {
let protocol_url = self.manager().protocol_url();
let protocol_url = self.manager().protocol_url(uses_https);
let maybe_protocol = current_url
.domain()
.and_then(|d| d .split_once('.'))

View File

@ -898,6 +898,21 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
self.webview_builder = self.webview_builder.browser_extensions_enabled(enabled);
self
}
/// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
///
/// ## Note
///
/// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
///
/// ## Warning
///
/// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
#[must_use]
pub fn use_https_scheme(mut self, enabled: bool) -> Self {
self.webview_builder = self.webview_builder.use_https_scheme(enabled);
self
}
}
/// A type that wraps a [`Window`] together with a [`Webview`].

View File

@ -381,7 +381,11 @@ tauri::Builder::default()
);
if let Some(webview) = detached_window.webview {
app_manager.webview.attach_webview(window.clone(), webview);
app_manager.webview.attach_webview(
window.clone(),
webview.webview,
webview.use_https_scheme,
);
}
window

View File

@ -66,7 +66,8 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
.build()?,
));
let mut window_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default());
let mut window_builder =
WebviewWindowBuilder::new(app, "main", WebviewUrl::default()).use_https_scheme(true);
#[cfg(all(desktop, not(test)))]
{

View File

@ -734,6 +734,21 @@ interface WebviewOptions {
* - **Android / iOS**: Unsupported.
*/
zoomHotkeysEnabled?: boolean
/**
* Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
*
* #### Note
*
* Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
*
* #### Warning
*
* Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access them.
*
* @since 2.1.0
*/
useHttpsScheme?: boolean
}
export { Webview, getCurrentWebview, getAllWebviews }